Skip to content

Commit

Permalink
Merge pull request #4 from Termina/claude
Browse files Browse the repository at this point in the history
a dirty support for Claude AI
  • Loading branch information
NoEgAm authored Oct 12, 2024
2 parents ae11a0e + f4741c8 commit 9c84262
Show file tree
Hide file tree
Showing 6 changed files with 1,619 additions and 642 deletions.
1,786 changes: 1,323 additions & 463 deletions calcit.cirru

Large diffs are not rendered by default.

247 changes: 182 additions & 65 deletions compact.cirru
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,152 @@
:defs $ {}
|*abort-control $ %{} :CodeEntry (:doc |)
:code $ quote (defatom *abort-control nil)
|call-anthropic-msg! $ %{} :CodeEntry (:doc |)
:code $ quote
defn call-anthropic-msg! (cursor state prompt-text d!) (hint-fn async)
if-let
abort $ deref *abort-control
do (js/console.warn "\"Aborting prev") (.!abort abort)
d! $ :: :states cursor
-> state (assoc :answer nil) (assoc :loading? true)
d! $ :: :change-model
let
selected $ js-await (get-selected)
content $ .replace prompt-text "\"{{selected}}" (or selected "\"<未找到内容>")
result $ js-await
.!post axios (str "\"https://sa.chenyong.life/v1/messages")
js-object
:model $ get-env "\"claude-model" "\"claude-3-5-sonnet-20240620"
:max_tokens 1024
:stream true
:messages $ js-array
js-object (:role "\"user") (:content content)
js-object
:params $ js-object
:headers $ js-object (; :Accept "\"text/event-stream") (; :Content-Type "\"application/json")
"\"x-api-key" $ get-anthropic-key!
"\"anthropic-version" "\"2023-06-01"
"\"anthropic-dangerous-direct-browser-access" true
:responseType "\"stream"
:adapter "\"fetch"
:signal $ let
abort $ new js/AbortController
reset! *abort-control abort
.-signal abort
stream $ .-data result
reader $ ->
.!pipeThrough stream $ new js/TextDecoderStream
.!getReader
*text $ atom (str "\"Claude AI:" &newline &newline)
; reading $ js-await (.!read reader)
; answer $ -> result .-data .-candidates .-0 .-content .-parts .-0 .-text
; d! $ :: :states cursor
-> state
assoc :answer $ w-log answer
assoc :loading? false
apply-args () $ fn () (hint-fn async)
let
info $ js-await (.!read reader)
value $ wo-js-log (.-value info)
done? $ .-done info
; js/console.log "\"VALUE" info
if (wo-log done?) (:: :unit)
do (println "\"processing")
let
events $ -> value .split-lines
filter $ fn (s) (.starts-with? s "\"data: ")
map $ fn (s)
-> (.strip-prefix s "\"data: ") js/JSON.parse to-calcit-data
apply-args (events)
fn (xs)
list-match xs
() $ println "\"no thing to handle in this Loop"
(x0 xss)
let
stop? $ = (get x0 "\"type") "\"message_stop"
wo-js-log x0
if stop?
d! $ :: :states cursor
-> state (assoc :answer @*text) (assoc :loading? false) (assoc :done? true)
let
content $ get-in x0 ([] "\"delta" "\"text")
if (nil? content)
do
;nil d! $ :: :states cursor
-> state
assoc :answer $ str @*text &newline "\"[STOPPED: " (.-finishReason candidate0) "\"]"
assoc :loading? false
assoc :done? true
println "\"content is nil"
recur xss
let () (swap! *text str content)
d! $ :: :states cursor
-> state (assoc :answer @*text) (assoc :loading? false) (assoc :done? false)
recur xss
recur
|call-gemini-msg! $ %{} :CodeEntry (:doc |)
:code $ quote
defn call-gemini-msg! (cursor state prompt-text d!) (hint-fn async)
if-let
abort $ deref *abort-control
do (js/console.warn "\"Aborting prev") (.!abort abort)
d! $ :: :states cursor
-> state (assoc :answer nil) (assoc :loading? true)
let
selected $ js-await (get-selected)
content $ .replace prompt-text "\"{{selected}}" (or selected "\"<未找到内容>")
result $ js-await
.!post axios
str "\"https://sf.chenyong.life/v1beta/models/" (pick-model) "\":streamGenerateContent"
js-object $ :contents
js-array $ js-object
:parts $ js-array
js-object $ :text content
js-object
:params $ js-object
:key $ get-gemini-key!
:alt "\"sse"
:headers $ js-object (:Accept "\"text/event-stream") (; :Content-Type "\"application/json")
:responseType "\"stream"
:adapter "\"fetch"
:signal $ let
abort $ new js/AbortController
reset! *abort-control abort
.-signal abort
stream $ .-data result
reader $ ->
.!pipeThrough stream $ new js/TextDecoderStream
.!getReader
*text $ atom "\""
; reading $ js-await (.!read reader)
; answer $ -> result .-data .-candidates .-0 .-content .-parts .-0 .-text
; d! $ :: :states cursor
-> state
assoc :answer $ w-log answer
assoc :loading? false
apply-args () $ fn () (hint-fn async)
let
info $ js-await (.!read reader)
value $ .-value info
done? $ .-done info
if done?
d! $ :: :states cursor
-> state (assoc :answer @*text) (assoc :loading? false) (assoc :done? true)
let
candidate0 $ -> (.!slice value 6) (.!trim) (first-line) (js/JSON.parse) .-candidates .-0
content $ .-content candidate0
if (nil? content)
d! $ :: :states cursor
-> state
assoc :answer $ str @*text &newline "\"[STOPPED: " (.-finishReason candidate0) "\"]"
assoc :loading? false
assoc :done? true
let
content $ -> candidate0 .-content .-parts .-0 .-text
swap! *text str content
d! $ :: :states cursor
-> state (assoc :answer @*text) (assoc :loading? false) (assoc :done? false)
recur
|comp-container $ %{} :CodeEntry (:doc |)
:code $ quote
defcomp comp-container (reel)
Expand All @@ -17,12 +163,19 @@
cursor $ or (:cursor states) ([])
state $ or (:data states)
{} (:answer nil) (:loading? false) (:done? false)
model $ either (:model store) :gemini
div
{} $ :class-name (str-spaced css/preset css/global css/column css/fullscreen css/gap8 style-app-global)
div
{} $ :class-name (str-spaced css/expand style-message-area)
div
{} $ :class-name (str-spaced style-message-list)
a $ {} (:inner-text "\"A")
:class-name $ str-spaced style-a-toggler css/font-fancy
:style $ {}
:opacity $ if (= model :anthropic) 1 0.3
:on-click $ fn (e d!)
d! $ :: :change-model
if (:loading? state)
div ({}) (<> "\"loading..." css/font-fancy)
if
Expand All @@ -44,7 +197,7 @@
<> "\"Streaming..." $ str-spaced css/font-fancy
=< nil 200
comp-message-box (>> states :message-box)
fn (text d!) (submit-message! cursor state text d!)
fn (text d!) (submit-message! cursor state text model d!)
if dev? $ comp-reel (>> states :reel) reel ({})
if dev? $ comp-inspect "\"Store" store nil
|comp-message-box $ %{} :CodeEntry (:doc |)
Expand Down Expand Up @@ -96,6 +249,18 @@
> (.-length lines) 1
js/console.warn "\"Droping some unexpected lines:" $ .!slice lines 1
.-0 lines
|get-anthropic-key! $ %{} :CodeEntry (:doc |)
:code $ quote
defn get-anthropic-key! () $ let
key $ js/localStorage.getItem "\"claude-key"
if (blank? key)
let
v $ js/prompt "\"Required claude-key in localStorage"
if (blank? v)
raise $ new js/Error "\"key is empty"
js/localStorage.setItem "\"claude-key" v
, v
, key
|get-gemini-key! $ %{} :CodeEntry (:doc |)
:code $ quote
defn get-gemini-key! () $ let
Expand All @@ -114,6 +279,10 @@
|pick-model $ %{} :CodeEntry (:doc |)
:code $ quote
defn pick-model () $ get-env "\"model" "\"gemini-1.5-flash"
|style-a-toggler $ %{} :CodeEntry (:doc |)
:code $ quote
defstyle style-a-toggler $ {}
"\"&" $ {} (:position :absolute) (:right 16) (:top 12) (:cursor :pointer)
|style-app-global $ %{} :CodeEntry (:doc |)
:code $ quote
defstyle style-app-global $ {}
Expand Down Expand Up @@ -143,7 +312,7 @@
|style-message-list $ %{} :CodeEntry (:doc |)
:code $ quote
defstyle style-message-list $ {}
"\"&" $ {} (:flex 2) (:padding "\"40px 16px 200px 16px") (:width "\"100%") (:max-width 1200) (:margin :auto)
"\"&" $ {} (:flex 2) (:padding "\"40px 16px 200px 16px") (:width "\"100%") (:max-width 1200) (:margin :auto) (:position :relative)
|style-more $ %{} :CodeEntry (:doc |)
:code $ quote
defstyle style-more $ {}
Expand All @@ -163,73 +332,14 @@
"\"&:focus-within" $ {} (:height "\"260px")
|submit-message! $ %{} :CodeEntry (:doc |)
:code $ quote
defn submit-message! (cursor state prompt-text d!) (hint-fn async)
if-let
abort $ deref *abort-control
do (js/console.warn "\"Aborting prev") (.!abort abort)
d! $ :: :states cursor
-> state (assoc :answer nil) (assoc :loading? true)
let
selected $ js-await (get-selected)
content $ .replace prompt-text "\"{{selected}}" (or selected "\"<未找到内容>")
result $ js-await
.!post axios
str "\"https://sf.chenyong.life/v1beta/models/" (pick-model) "\":streamGenerateContent"
js-object $ :contents
js-array $ js-object
:parts $ js-array
js-object $ :text content
js-object
:params $ js-object
:key $ get-gemini-key!
:alt "\"sse"
:headers $ js-object (:Accept "\"text/event-stream") (; :Content-Type "\"application/json")
:responseType "\"stream"
:adapter "\"fetch"
:signal $ let
abort $ new js/AbortController
reset! *abort-control abort
.-signal abort
stream $ .-data result
reader $ ->
.!pipeThrough stream $ new js/TextDecoderStream
.!getReader
*text $ atom "\""
; reading $ js-await (.!read reader)
; answer $ -> result .-data .-candidates .-0 .-content .-parts .-0 .-text
; d! $ :: :states cursor
-> state
assoc :answer $ w-log answer
assoc :loading? false
apply-args () $ fn () (hint-fn async)
let
info $ js-await (.!read reader)
value $ .-value info
done? $ .-done info
if done?
d! $ :: :states cursor
-> state (assoc :answer @*text) (assoc :loading? false) (assoc :done? true)
let
candidate0 $ -> (.!slice value 6) (.!trim) (first-line) (js/JSON.parse) .-candidates .-0
content $ .-content candidate0
if (nil? content)
d! $ :: :states cursor
-> state
assoc :answer $ str @*text &newline "\"[STOPPED: " (.-finishReason candidate0) "\"]"
assoc :loading? false
assoc :done? true
let
content $ -> candidate0 .-content .-parts .-0 .-text
swap! *text str content
d! $ :: :states cursor
-> state (assoc :answer @*text) (assoc :loading? false) (assoc :done? false)
recur
defn submit-message! (cursor state prompt-text model d!) (hint-fn async)
if (= model :anthropic) (call-anthropic-msg! cursor state prompt-text d!) (call-gemini-msg! cursor state prompt-text d!)
:ns $ %{} :CodeEntry (:doc |)
:code $ quote
ns app.comp.container $ :require (respo-ui.css :as css)
respo.css :refer $ defstyle
respo.util.format :refer $ hsl
respo.core :refer $ defcomp defeffect <> >> div button textarea span input
respo.core :refer $ defcomp defeffect <> >> div button textarea span input a
respo.comp.space :refer $ =<
respo.comp.inspect :refer $ comp-inspect
reel.comp.reel :refer $ comp-reel
Expand Down Expand Up @@ -274,7 +384,8 @@
store $ :store @*reel
cursor $ []
state0 $ get-in store ([] :states :data)
submit-message! cursor state0 content dispatch!
model $ either (:model store) :gemini
submit-message! cursor state0 content model dispatch!
js/chrome.runtime.connect $ js-object (:name |mySidepanel)
|main! $ %{} :CodeEntry (:doc |)
:code $ quote
Expand Down Expand Up @@ -334,6 +445,7 @@
def store $ {}
:states $ {}
:cursor $ []
:model nil
:ns $ %{} :CodeEntry (:doc |)
:code $ quote (ns app.schema)
|app.updater $ %{} :FileEntry
Expand All @@ -345,6 +457,11 @@
:states cursor s
update-states store cursor s
(:hydrate-storage data) data
(:change-model)
if
= (:model store) :anthropic
assoc store :model :gemini
assoc store :model :anthropic
_ $ do (eprintln "\"unknown op:" op) store
:ns $ %{} :CodeEntry (:doc |)
:code $ quote
Expand Down
2 changes: 1 addition & 1 deletion deps.cirru
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
:dependencies $ {}
|calcit-lang/lilac |main
|calcit-lang/memof |main
|Respo/respo.calcit |main
|Respo/respo.calcit |0.16.12
|Respo/respo-ui.calcit |main
|Respo/reel.calcit |main
|Respo/respo-markdown.calcit |0.4.2
2 changes: 1 addition & 1 deletion extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"commands": {
"_execute_action": {
"suggested_key": {
"mac": "MacCtrl+Command+G"
"mac": "Command + Ctrl + G"
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"dependencies": {
"@calcit/procs": "^0.9.5",
"@google/generative-ai": "^0.20.0",
"@google/generative-ai": "^0.21.0",
"axios": "^1.7.7",
"cirru-color": "^0.2.4",
"copy-text-to-clipboard": "^3.2.0"
Expand Down
Loading

0 comments on commit 9c84262

Please sign in to comment.