diff --git a/.gitignore b/.gitignore index 83448ac..db7e734 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ extension/dist .yarn .calcit-snippets/ + +.copilot-tmp/ \ No newline at end of file diff --git a/Agents.md b/Agents.md index a78cf91..2339244 100644 --- a/Agents.md +++ b/Agents.md @@ -1,6 +1,6 @@ -Developer runs `cr js` to watch build JavaScript, and `yarn vite` to start a local server. LLMs edits program by running `cr` commands, and then triggers re-compiling. +Developer runs `cr js` to build JavaScript, and `yarn vite` to start a local server. LLMs edits program by running `cr` commands, and then triggers re-compiling. -要求查看 Calcit 命令行工具的用法: +**必须**查看 Calcit 命令行工具的用法: ```bash cr docs agents --full diff --git a/compact.cirru b/compact.cirru index a7f98ba..b5d39a1 100644 --- a/compact.cirru +++ b/compact.cirru @@ -1,33 +1,34 @@ {} (:about "|file is generated - never edit directly; learn cr edit/tree workflows before changing") (:package |app) :configs $ {} (:init-fn |app.main/main!) (:reload-fn |app.main/reload!) (:version |0.0.1) - :modules $ [] |respo.calcit/ |lilac/ |memof/ |respo-ui.calcit/ |reel.calcit/ |respo-markdown.calcit/ |alerts.calcit/ |respo-feather.calcit/ |genai.calcit/ + :modules $ [] |respo.calcit/ |memof/ |respo-ui.calcit/ |reel.calcit/ |respo-markdown.calcit/ |alerts.calcit/ |respo-feather.calcit/ |genai.calcit/ :entries $ {} :files $ {} |app.comp.container $ %{} :FileEntry :defs $ {} - |*abort-control $ %{} :CodeEntry (:doc |) + |*abort-control $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote (defatom *abort-control nil) :examples $ [] - |*gen-ai-new $ %{} :CodeEntry (:doc |) + |*gen-ai-new $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote (defatom *gen-ai-new nil) :examples $ [] - |*image-cache $ %{} :CodeEntry (:doc |) + |*image-cache $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote (defatom *image-cache nil) :examples $ [] - |*openai $ %{} :CodeEntry (:doc "|called openai sdk, but actually for openrouter") + |*openai $ %{} :CodeEntry (:doc "|called openai sdk, but actually for openrouter") (:schema nil) :code $ quote (defatom *openai nil) :examples $ [] - |append-user-message $ %{} :CodeEntry (:doc |) + |append-user-message $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn append-user-message (messages content) let messages0 $ if (some? messages) messages ([]) conj messages0 $ {} (:role :user) (:content content) :examples $ [] - |call-anthropic-msg! $ %{} :CodeEntry (:doc |) + |call-anthropic-msg! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn call-anthropic-msg! (cursor state prompt-text model thinking? d!) (hint-fn async) + defn call-anthropic-msg! (cursor state prompt-text model thinking? d!) + hint-fn $ {} (:async true) if-let abort $ deref *abort-control do (js/console.warn "\"Aborting prev") (.!abort abort) @@ -67,7 +68,8 @@ js/setTimeout $ fn () d! $ :: :states-merge cursor state {} (:answer nil) (:thinking nil) (:loading? true) (:done? false) (:messages messages1) - apply-args () $ fn () (hint-fn async) + apply-args () $ fn () + hint-fn $ {} (:async true) let info $ js-await (.!read reader) value $ wo-js-log (.-value info) @@ -109,11 +111,16 @@ recur xss recur :examples $ [] - |call-flash-imagen-msg! $ %{} :CodeEntry (:doc |) + |call-flash-imagen-msg! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn call-flash-imagen-msg! (variant cursor state prompt-text d!) (hint-fn async) + defn call-flash-imagen-msg! (variant cursor state prompt-text d!) + hint-fn $ {} (:async true) if (nil? @*gen-ai-new) - reset! *gen-ai-new $ sdk/new-client (get-gemini-key!) + let + mod $ js-await (js/import |@google/genai) + GoogleGenAI $ .-GoogleGenAI mod + reset! *gen-ai-new $ new GoogleGenAI + js-object $ :apiKey (get-gemini-key!) if-let target $ js/document.querySelector "\".show-image" .!setAttribute target "\"src" "\"" @@ -127,14 +134,20 @@ selected $ js-await (get-selected) gen-ai @*gen-ai-new content $ .!replace prompt-text "\"{{selected}}" (or selected "\"<未找到选中内容>") + abort-signal $ let + abort $ new js/AbortController + reset! *abort-control abort + .-signal abort sdk-result $ js-await - sdk/generate-content! gen-ai $ {} (:model "\"gemini-2.5-flash-image") (:contents content) - :abort-signal $ sdk/make-abort-signal *abort-control - :http-options $ sdk/make-http-options |https://ja.chenyong.life - :response-modalities $ js-array "\"TEXT" "\"IMAGE" + .!generateContent (.-models gen-ai) + js-object (:model "\"gemini-2.5-flash-image") (:contents content) + :config $ js-object (:abortSignal abort-signal) + :httpOptions $ js-object (:baseUrl |https://ja.chenyong.life) + :responseModalities $ js-array "\"TEXT" "\"IMAGE" + parts $ -> sdk-result .-candidates .-0 .-content .-parts *text $ atom "\"" - js-await $ -> (sdk/extract-content-parts sdk-result) - .!forEach $ fn (? chunk _a _b) + js-await $ .!forEach parts + fn (? chunk _a _b) if (some? chunk) if-let text $ .-text chunk @@ -157,11 +170,16 @@ d! $ :: :states cursor -> state (assoc :answer @*text) (assoc :loading? false) (assoc :done? true) :examples $ [] - |call-genai-msg! $ %{} :CodeEntry (:doc |) + |call-genai-msg! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn call-genai-msg! (variant cursor state prompt-text search? think? d! *text *thinking-text) (hint-fn async) + defn call-genai-msg! (variant cursor state prompt-text search? think? d! *text *thinking-text) + hint-fn $ {} (:async true) if (nil? @*gen-ai-new) - reset! *gen-ai-new $ sdk/new-client (get-gemini-key!) + let + mod $ js-await (js/import |@google/genai) + GoogleGenAI $ .-GoogleGenAI mod + reset! *gen-ai-new $ new GoogleGenAI + js-object $ :apiKey (get-gemini-key!) if-let abort $ deref *abort-control do (js/console.warn "\"Aborting prev") (.!abort abort) @@ -176,18 +194,35 @@ has-url? $ or (.!includes prompt-text "\"http://") (.!includes prompt-text "\"https://") messages0 $ or (:messages state) ([]) messages1 $ upsert-assistant-message messages0 "\"" nil + abort-signal $ let + abort $ new js/AbortController + reset! *abort-control abort + .-signal abort + tools $ -> + js-array + if search? + js-object $ :googleSearch (js-object) + , js/undefined + if has-url? + js-object $ :urlContext (js-object) + , js/undefined + .!filter $ fn (x _idx _arr) x sdk-result $ js-await - sdk/generate-content-stream! gen-ai $ {} (:model model) - :contents $ sdk/messages->contents messages0 - :thinking-config $ if think? - sdk/make-thinking-config - get-env "\"think-budget" $ if pro? 3200 800 - , true - sdk/make-thinking-config 0 false - :tools $ sdk/make-search-tools search? has-url? - :abort-signal $ sdk/make-abort-signal *abort-control - :http-options $ sdk/make-http-options |https://ja.chenyong.life - :response-mime-type $ if json? "\"application/json" nil + .!generateContentStream (.-models gen-ai) + js-object (:model model) + :contents $ messages->gemini messages0 + :config $ js-object + :thinkingConfig $ if think? + js-object + :thinkingBudget $ get-env "\"think-budget" (if pro? 3200 800) + :includeThoughts true + js-object (:thinkingBudget 0) (:includeThoughts false) + :tools $ if + > (.-length tools) 0 + , tools js/undefined + :abortSignal abort-signal + :httpOptions $ js-object (:baseUrl |https://ja.chenyong.life) + :responseMimeType $ if json? "\"application/json" js/undefined do js/setTimeout $ fn () d! $ :: :states-merge cursor state @@ -196,9 +231,11 @@ fn (? chunk) if (some? chunk) let - info $ sdk/extract-stream-chunk chunk - is-thinking? $ :thinking? info - text $ or (:text info) |__BLANK__ + part $ -> chunk .-candidates .-0 .-content .-parts .-0 + is-thinking? $ if (some? part) (.-thought part) false + text $ or + if (some? part) (.-text part) (.-text chunk) + -> chunk .-promptFeedback .-blockReason if is-thinking? (swap! *thinking-text str text) (swap! *text str text) d! $ :: :states-merge cursor state {} (:answer @*text) (:thinking @*thinking-text) (:loading? false) (:done? false) @@ -210,11 +247,16 @@ {} (:answer @*text) (:thinking @*thinking-text) (:loading? false) (:done? true) :messages $ upsert-assistant-message messages1 @*text @*thinking-text :examples $ [] - |call-imagen-4-msg! $ %{} :CodeEntry (:doc |) + |call-imagen-4-msg! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn call-imagen-4-msg! (variant cursor state prompt-text d!) (hint-fn async) + defn call-imagen-4-msg! (variant cursor state prompt-text d!) + hint-fn $ {} (:async true) if (nil? @*gen-ai-new) - reset! *gen-ai-new $ sdk/new-client (get-gemini-key!) + let + mod $ js-await (js/import |@google/genai) + GoogleGenAI $ .-GoogleGenAI mod + reset! *gen-ai-new $ new GoogleGenAI + js-object $ :apiKey (get-gemini-key!) if-let target $ js/document.querySelector "\".show-image" .!removeAttribute target "\"src" @@ -227,13 +269,19 @@ let selected $ js-await (get-selected) gen-ai @*gen-ai-new + abort-signal $ let + abort $ new js/AbortController + reset! *abort-control abort + .-signal abort response $ js-await - sdk/generate-images! gen-ai $ {} (:model "\"imagen-4.0-generate-001") (:prompt prompt-text) (:number-of-images 1) (:include-rai-reason true) - :abort-signal $ sdk/make-abort-signal *abort-control - :http-options $ sdk/make-http-options |https://ja.chenyong.life + .!generateImages (.-models gen-ai) + js-object (:model "\"imagen-4.0-generate-001") (:prompt prompt-text) + :config $ js-object (:numberOfImages 1) (:includeRaiReason true) + :httpOptions $ js-object (:baseUrl |https://ja.chenyong.life) + :signal abort-signal *text $ atom "\"" if-let - image-data $ sdk/extract-image-bytes response + image-data $ -> response .-generatedImages .-0 .-image .-imageBytes let image-blob $ base64ToBlob image-data url $ js/URL.createObjectURL image-blob @@ -246,15 +294,19 @@ d! $ :: :states cursor -> state (assoc :answer @*text) (assoc :loading? false) (assoc :done? true) :examples $ [] - |call-openrouter! $ %{} :CodeEntry (:doc |) + |call-openrouter! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn call-openrouter! (cursor state prompt-text variant thinking? d! *text) (hint-fn async) + defn call-openrouter! (cursor state prompt-text variant thinking? d! *text) + hint-fn $ {} (:async true) if (nil? @*openai) - reset! *openai $ new OpenAI - js-object (:baseURL "\"https://openrouter.ai/api/v1") - :apiKey $ get-openrouter-key! - :defaultHeaders $ js-object - :dangerouslyAllowBrowser true + let + mod $ js-await (js/import |openai) + OpenAI $ .-default mod + reset! *openai $ new OpenAI + js-object (:baseURL "\"https://openrouter.ai/api/v1") + :apiKey $ get-openrouter-key! + :defaultHeaders $ js-object + :dangerouslyAllowBrowser true if-let abort $ deref *abort-control do (js/console.warn "\"Aborting prev") (.!abort abort) @@ -286,7 +338,7 @@ d! $ :: :states-merge cursor state {} (:answer nil) (:thinking nil) (:loading? true) (:done? false) (:messages messages1) js-await $ js-for-await sdk-result - fn (? chunk) (; js/console.log "\"[CHUNK]" chunk) + fn (? chunk) if (some? chunk) do swap! *text str $ -> chunk .-choices .-0 .-delta .-content (or "\"") @@ -300,12 +352,12 @@ {} (:answer @*text) (:loading? false) (:done? true) :messages $ upsert-assistant-message messages1 @*text nil :examples $ [] - |clear-image-cache! $ %{} :CodeEntry (:doc |) + |clear-image-cache! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn clear-image-cache! () $ if-let (url @*image-cache) do (js/URL.revokeObjectURL url) (reset! *image-cache nil) :examples $ [] - |comp-abort $ %{} :CodeEntry (:doc |) + |comp-abort $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn comp-abort (t) span @@ -320,7 +372,7 @@ =< 8 nil <> "\"✕" style-abort-close :examples $ [] - |comp-container $ %{} :CodeEntry (:doc |) + |comp-container $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defcomp comp-container (reel) let @@ -464,6 +516,16 @@ .show reply-plugin d! $ fn (text) submit-message! cursor state text (:search? message-box-state) (:think? message-box-state) model d! <> |Reply + if (:focus-mode? message-box-state) nil $ a + {} (:class-name style-focus-link) (:inner-text |Focus) + :on-click $ fn (e d!) + let + focused $ .-activeElement js/document + do + if (some? focused) (.!blur focused) + d! + :cursor $ >> states :message-box + assoc message-box-state :focus-mode? true , nil if (:loading? state) div ({}) (memof1-call-by :abort-loading comp-abort "\"Loading...") @@ -516,7 +578,7 @@ if dev? $ comp-reel (>> states :reel) reel ({}) if dev? $ comp-inspect "\"Store" store nil :examples $ [] - |comp-fill $ %{} :CodeEntry (:doc |) + |comp-fill $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defcomp comp-fill (text) div @@ -526,136 +588,183 @@ js-object (:action |fill-text) (:text text) comp-i :send 12 :currentColor :examples $ [] - |comp-message-box $ %{} :CodeEntry (:doc |) + |comp-message-box $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defcomp comp-message-box (states picker-el on-submit model) let cursor $ :cursor states state $ either (:data states) - {} (:content "\"") (:search? false) (:think? false) + {} (:content "\"") (:search? false) (:think? false) (:focus-mode? false) [] (effect-focus) (on-fill cursor state on-submit) div {} $ :class-name (str-spaced css/center style-message-box-panel) div {} $ :class-name (str-spaced css/column style-message-box) - textarea $ {} - :value $ :content state - :placeholder "\"Prompt to try LLM..." - :id "\"message" - :class-name $ str-spaced css/textarea css/font-code! style-textbox - :on-input $ fn (e d!) - d! cursor $ assoc state :content (:value e) - :on-keydown $ fn (e d!) - if - and - = 13 $ :keycode e - or (:meta? e) (:ctrl? e) - on-submit (:content state) (:search? state) (:think? state) d! - :on-focus $ fn (e d!) - let - target $ .-target (:event e) - box $ .-parentElement (.-parentElement target) - class-list $ .-classList target - box-class $ .-classList box - if - not $ .!contains class-list "\"focus-within" - .!add class-list "\"focus-within" - if - not $ .!contains box-class "\"focus-within" - .!add box-class "\"focus-within" - :on-blur $ fn (e d!) - let - target $ .-target (:event e) - box $ .-parentElement (.-parentElement target) - class-list $ .-classList target - box-class $ .-classList box - if (.!contains class-list "\"focus-within") (.!remove class-list "\"focus-within") - if (.!contains box-class "\"focus-within") (.!remove box-class "\"focus-within") - =< nil 4 - div - {} $ :class-name css/row-parted - if - not $ blank? (:content state) - comp-close $ {} (:class-name style-clear) - :on-click $ fn (e d!) - d! cursor $ assoc state :content "\"" - -> (js/document.querySelector "\"#message") (.!focus) - span $ {} (:class-name style-clear) + if (:focus-mode? state) div - {} $ :class-name (str-spaced css/row style-gap12) - , picker-el + {} + :class-name $ str-spaced css/font-code! style-focus-box style-textbox-compact + :on-click $ fn (e d!) + do + d! cursor $ assoc state :focus-mode? false + js/setTimeout + fn () $ -> (js/document.querySelector "\"#message") (.!focus) + , 0 + <> $ if + blank? $ :content state + , "\"Click to expand and type..." (:content state) + textarea $ {} + :value $ :content state + :placeholder "\"Prompt to try LLM..." + :id "\"message" + :class-name $ str-spaced css/textarea css/font-code! style-textbox + :on-input $ fn (e d!) + d! cursor $ assoc state :content (:value e) + :on-keydown $ fn (e d!) if - contains? (#{} :gemini-flash :gemini-3.1-flash-lite-preview) model - div - {} - :class-name $ str-spaced css/row style-checkbox - :on-click $ fn (e d!) - d! cursor $ assoc state :think? - not $ :think? state - input $ {} - :checked $ :think? state - :type "\"checkbox" - <> "\"Think" css/font-fancy - , nil - div - {} - :class-name $ str-spaced css/row style-checkbox + and + = 13 $ :keycode e + or (:meta? e) (:ctrl? e) + on-submit (:content state) (:search? state) (:think? state) d! + :on-focus $ fn (e d!) + let + target $ .-target (:event e) + box $ .-parentElement (.-parentElement target) + class-list $ .-classList target + box-class $ .-classList box + if + not $ .!contains class-list "\"focus-within" + .!add class-list "\"focus-within" + if + not $ .!contains box-class "\"focus-within" + .!add box-class "\"focus-within" + :on-blur $ fn (e d!) + let + target $ .-target (:event e) + box $ .-parentElement (.-parentElement target) + class-list $ .-classList target + box-class $ .-classList box + if (.!contains class-list "\"focus-within") (.!remove class-list "\"focus-within") + if (.!contains box-class "\"focus-within") (.!remove box-class "\"focus-within") + if + not $ :focus-mode? state + do (=< nil 4) + div + {} $ :class-name css/row-parted + if + not $ blank? (:content state) + comp-close $ {} (:class-name style-clear) :on-click $ fn (e d!) - d! cursor $ assoc state :search? - not $ :search? state - input $ {} - :checked $ :search? state - :type "\"checkbox" - <> "\"Search" css/font-fancy - button $ {} - :class-name $ str-spaced css/button style-submit - :inner-text "\"Submit" - :on-click $ fn (e d!) - ; println $ :content state - on-submit (:content state) (:search? state) (:think? state) d! + d! cursor $ assoc state :content "\"" + -> (js/document.querySelector "\"#message") (.!focus) + span $ {} (:class-name style-clear) + div + {} $ :class-name (str-spaced css/row style-gap12) + , picker-el + if + contains? (#{} :gemini-flash :gemini-3.1-flash-lite-preview) model + div + {} + :class-name $ str-spaced css/row style-checkbox + :on-click $ fn (e d!) + d! cursor $ assoc state :think? + not $ :think? state + input $ {} + :checked $ :think? state + :type "\"checkbox" + <> "\"Think" css/font-fancy + , nil + div + {} + :class-name $ str-spaced css/row style-checkbox + :on-click $ fn (e d!) + d! cursor $ assoc state :search? + not $ :search? state + input $ {} + :checked $ :search? state + :type "\"checkbox" + <> "\"Search" css/font-fancy + button $ {} + :class-name $ str-spaced css/button style-submit + :inner-text "\"Submit" + :on-click $ fn (e d!) + on-submit (:content state) (:search? state) (:think? state) d! + , nil :examples $ [] - |comp-sessions-modal $ %{} :CodeEntry (:doc |) + |comp-sessions-modal $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defcomp comp-sessions-modal (sessions on-select on-close) - div - {} $ :class-name (str-spaced css/column css/gap8 style-sessions-list) - if (empty? sessions) - div - {} $ :style - {} (:padding |12px) - :color $ hsl 0 0 60 - <> "|No history sessions" - list-> - {} $ :class-name css/column - -> sessions (.!reverse) - map $ fn (session) - let - session-id $ :id session - created-at $ :created-at session - preview $ :preview session - date-str $ .!toLocaleString (new js/Date created-at) - [] session-id $ div - {} $ :class-name style-session-item - div - {} - :style $ {} (:flex |1) (:cursor :pointer) (:min-width 0) (:overflow :hidden) - :on-click $ fn (e d!) (on-select session-id d!) (on-close d!) + let + history-items $ foldl sessions 0 + fn (acc session) + + acc $ count + or (:messages session) ([]) + div + {} $ :class-name (str-spaced css/column css/gap8 style-sessions-list) + if (empty? sessions) + div + {} $ :style + {} (:padding |12px) + :color $ hsl 0 0 60 + <> "|No history sessions" + list-> + {} $ :class-name css/column + -> sessions (.!reverse) + map $ fn (session) + let + session-id $ :id session + created-at $ :created-at session + preview $ :preview session + date-str $ .!toLocaleString (new js/Date created-at) + [] session-id $ div + {} $ :class-name style-session-item div - {} $ :style - {} (:font-size |12px) - :color $ hsl 0 0 60 - <> date-str + {} + :style $ {} (:flex |1) (:cursor :pointer) (:min-width 0) (:overflow :hidden) + :on-click $ fn (e d!) (on-select session-id d!) (on-close d!) + div + {} $ :style + {} (:font-size |12px) + :color $ hsl 0 0 60 + <> date-str + div + {} $ :style + {} (:margin-top |4px) (:white-space :nowrap) (:overflow :hidden) (:text-overflow :ellipsis) (:max-height |1.2em) (:line-height |1.2) + <> preview div - {} $ :style - {} (:margin-top |4px) (:white-space :nowrap) (:overflow :hidden) (:text-overflow :ellipsis) (:max-height |1.2em) (:line-height |1.2) - <> preview - div - {} (:class-name style-delete-button) - :on-click $ fn (e d!) (-> e :event .!stopPropagation) - d! $ :: :remove-session session-id - <> "|✕" + {} (:class-name style-delete-button) + :on-click $ fn (e d!) (-> e :event .!stopPropagation) + d! $ :: :remove-session session-id + <> "|✕" + if + > (count sessions) 0 + div + {} + :class-name $ str-spaced css/column css/gap8 + :style $ {} (:padding "|0 12px 12px 12px") + div + {} $ :class-name (str-spaced css/row-parted) + div + {} $ :class-name (str-spaced css/row css/gap8) + a $ {} (:class-name style-clear) (:inner-text |Data) + :on-click $ fn (e d!) (tab-echo! sessions :edn) + a $ {} (:class-name style-clear) (:inner-text |Download) + :on-click $ fn (e d!) (download-sessions! sessions) + if + > (count sessions) 0 + a $ {} (:class-name style-clear) (:inner-text "|Clear all") + :on-click $ fn (e d!) + let + proceed? $ if (> history-items 10) + js/confirm $ str-spaced |Clear history-items "|messages from history?" + , true + when proceed? $ d! (:: :clear-sessions) + span $ {} + div $ {} + :style $ {} (:height 200) + , nil :examples $ [] - |create-session $ %{} :CodeEntry (:doc |) + |create-session $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn create-session (messages model) let @@ -674,14 +783,29 @@ .!slice first-msg 0 end :is-history? false :examples $ [] - |effect-focus $ %{} :CodeEntry (:doc |) + |download-sessions! $ %{} :CodeEntry (:doc |) (:schema nil) + :code $ quote + defn download-sessions! (sessions) + let + content $ format-cirru-edn sessions + blob $ new js/Blob (js-array content) + js-object $ :type |application/edn;charset=utf-8 + url $ js/URL.createObjectURL blob + link $ js/document.createElement |a + filename $ str |sessions- (js/Date.now) |.cirru + do (.!setAttribute link |href url) (.!setAttribute link |download filename) (.!appendChild js/document.body link) (.!click link) (.!remove link) + js/setTimeout + fn () $ js/URL.revokeObjectURL url + , 0 + :examples $ [] + |effect-focus $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defeffect effect-focus () (action el at?) when (= action :mount) js/setTimeout $ fn () .!select $ .!querySelector el "\"textarea" :examples $ [] - |first-line $ %{} :CodeEntry (:doc "|last message from error contains a line starts with \"data: \" and an extra error message. In order that JSON is parsed correctly, only first line is used now.") + |first-line $ %{} :CodeEntry (:doc "|last message from error contains a line starts with \"data: \" and an extra error message. In order that JSON is parsed correctly, only first line is used now.") (:schema nil) :code $ quote defn first-line (tt) let @@ -693,11 +817,11 @@ js/console.warn "\"Droping some unexpected lines:" $ .!slice lines 1 .-0 lines :examples $ [] - |generate-session-id $ %{} :CodeEntry (:doc |) + |generate-session-id $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn generate-session-id () $ str (js/Date.now) :examples $ [] - |get-anthropic-key! $ %{} :CodeEntry (:doc |) + |get-anthropic-key! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn get-anthropic-key! () $ let key $ js/localStorage.getItem "\"claude-key" @@ -710,7 +834,7 @@ , v , key :examples $ [] - |get-deepinfra-key! $ %{} :CodeEntry (:doc |) + |get-deepinfra-key! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn get-deepinfra-key! () $ let key $ js/localStorage.getItem "\"deepinfra-key" @@ -723,7 +847,7 @@ , v , key :examples $ [] - |get-gemini-key! $ %{} :CodeEntry (:doc |) + |get-gemini-key! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn get-gemini-key! () $ let key $ js/localStorage.getItem "\"gemini-key" @@ -736,7 +860,7 @@ , v , key :examples $ [] - |get-openrouter-key! $ %{} :CodeEntry (:doc |) + |get-openrouter-key! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn get-openrouter-key! () $ let key $ js/localStorage.getItem "\"openrouter-key" @@ -749,12 +873,12 @@ , v , key :examples $ [] - |json-pattern? $ %{} :CodeEntry (:doc |) + |json-pattern? $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn json-pattern? (text) or (.!startsWith text "\"{") (.!startsWith text "\"[") :examples $ [] - |messages->anthropic $ %{} :CodeEntry (:doc |) + |messages->anthropic $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn messages->anthropic (messages) to-js-data $ map (or messages []) @@ -765,7 +889,7 @@ , |assistant |user :content $ :content m :examples $ [] - |messages->gemini $ %{} :CodeEntry (:doc |) + |messages->gemini $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn messages->gemini (messages) let @@ -779,7 +903,7 @@ :parts $ [] {} $ :text (:content m) :examples $ [] - |messages->openai $ %{} :CodeEntry (:doc |) + |messages->openai $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn messages->openai (messages) let @@ -792,11 +916,11 @@ , |assistant |user :content $ :content m :examples $ [] - |models-menu $ %{} :CodeEntry (:doc |) + |models-menu $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def models-menu $ [] (:: :item :gemini-flash "|Gemini Flash 3") (:: :item :gemini-pro "|Gemini Pro 3.1") (:: :item :gemini-3.1-flash-lite-preview "|Gemini Flash Lite 3.1") (:: :item :flash-imagen "\"Flash Imagen") (:: :item :imagen-4 "\"Imagen 4") (:: :item :gemma "|Gemma 3 27b") (:: :item :openrouter/anthropic/claude-sonnet-4.5 "\"Openrouter Claude Sonnet 4.5") (:: :item :openrouter/anthropic/claude-opus-4 "\"Openrouter Claude Opus 4") (:: :item :openrouter/google/gemini-2.5-pro-preview "\"Openrouter Google Gemini 2.5 pro preview") (:: :item :openrouter/google/gemini-2.5-flash-preview-05-20 "\"Openrouter Google Gemini 2.5 flash preview") (:: :item :openrouter/openai/gpt-5 "\"Openrouter GPT 5") (:: :item :openrouter/deepseek/deepseek-chat-v3.1 "\"Openrouter deepseek-chat-v3.1") (; :: :item :claude-4.5 "\"Claude 4.5") :examples $ [] - |on-fill $ %{} :CodeEntry (:doc |) + |on-fill $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn on-fill (cursor state on-submit) %{} respo.schema/RespoListener (:name :on-fill) @@ -812,16 +936,16 @@ on-submit (:text info) (:search? state) (:think? state) dispatch! , nil :examples $ [] - |pattern-spaced-code $ %{} :CodeEntry (:doc |) + |pattern-spaced-code $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def pattern-spaced-code $ noted "\"temp fix of nested code block" (&raw-code "\"/\\n\\s+```/g") :examples $ [] - |pick-model $ %{} :CodeEntry (:doc |) + |pick-model $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn pick-model (variant) case-default variant "\"gemini-3-flash-preview" (:gemini-3.1-flash-lite-preview "\"gemini-3.1-flash-lite-preview") (:gemini-pro "\"gemini-3.1-pro-preview") (:gemma "\"gemma-3-27b-it") :examples $ [] - |save-current-session $ %{} :CodeEntry (:doc |) + |save-current-session $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn save-current-session (store state) let @@ -836,18 +960,18 @@ assoc store :sessions $ append sessions updated-session , store :examples $ [] - |style-a-toggler $ %{} :CodeEntry (:doc |) + |style-a-toggler $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-a-toggler $ {} "\"&" $ {} (:cursor :pointer) (:background-color :white) (:color :black) "\".focus-within &" $ {} (:color :black) :examples $ [] - |style-abort-close $ %{} :CodeEntry (:doc |) + |style-abort-close $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-abort-close $ {} "\"&" $ {} (:vertical-align :middle) (:font-size 10) :examples $ [] - |style-app-global $ %{} :CodeEntry (:doc |) + |style-app-global $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-app-global $ {} str "\"& ." style-code-block @@ -858,22 +982,22 @@ "\"&:hover" $ {} (:color "\"#777") :background-color $ hsl 0 0 100 :examples $ [] - |style-checkbox $ %{} :CodeEntry (:doc |) + |style-checkbox $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-checkbox $ {} "\"&" $ {} (:cursor :pointer) (:user-select :none) (:font-size 12) (:line-height "\"28px") (:vertical-align :middle) :examples $ [] - |style-clear $ %{} :CodeEntry (:doc |) + |style-clear $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-clear $ {} "\"&" $ {} (:opacity 0.4) (:padding "\"4px 8px") (:display :inline-block) (:height "\"24px") :examples $ [] - |style-code-content $ %{} :CodeEntry (:doc |) + |style-code-content $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-code-content $ {} "\"&" $ {} (:line-height "\"1.5") (:font-size 13) :examples $ [] - |style-delete-button $ %{} :CodeEntry (:doc |) + |style-delete-button $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-delete-button $ {} |& $ {} (:padding "|4px 8px") (:font-size |18px) (:font-weight |50) @@ -887,7 +1011,7 @@ |&:active $ {} (:opacity 1) :color $ hsl 0 90 40 :examples $ [] - |style-fill $ %{} :CodeEntry (:doc |) + |style-fill $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-fill $ {} "\"&" $ {} (:cursor :pointer) (:user-select :none) (:display :inline-flex) (:align-items :center) (:justify-content :center) (:transition-duration "\"200ms") @@ -897,12 +1021,26 @@ :color $ hsl 0 0 40 :transform "\"scale(1.06)" :examples $ [] - |style-gap12 $ %{} :CodeEntry (:doc |) + |style-focus-box $ %{} :CodeEntry (:doc |) (:schema nil) + :code $ quote + defstyle style-focus-box $ {} + "\"&" $ {} (:width |100%) (:border-radius 12) (:min-height 40) (:max-height 40) (:padding "\"9px 12px") (:cursor :text) (:overflow :hidden) (:white-space :pre) (:text-overflow :ellipsis) (:background-color :transparent) + :examples $ [] + |style-focus-link $ %{} :CodeEntry (:doc |) (:schema nil) + :code $ quote + defstyle style-focus-link $ {} + "\"&" $ {} (:cursor :pointer) (:font-size 13) + :color $ hsl 200 80 40 + :text-decoration :none + :padding "\"4px 0" + "\"&:hover" $ {} (:text-decoration :underline) + :examples $ [] + |style-gap12 $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-gap12 $ {} "\"&" $ {} (:gap 12) :examples $ [] - |style-history-button $ %{} :CodeEntry (:doc |) + |style-history-button $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-history-button $ {} |& $ {} (:font-size |20px) @@ -915,7 +1053,7 @@ |&:hover $ {} :color $ hsl 200 80 50 :examples $ [] - |style-history-count $ %{} :CodeEntry (:doc |) + |style-history-count $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-history-count $ {} |& $ {} @@ -923,39 +1061,39 @@ :font-size |12px :display :inline-block :examples $ [] - |style-image $ %{} :CodeEntry (:doc |) + |style-image $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-image $ {} "\"&" $ {} (:max-width "\"100%") (:align-self :flex-start) (:border-radius "\"6px") :border $ str "\"1px solid " (hsl 0 0 90) :examples $ [] - |style-md-content $ %{} :CodeEntry (:doc |) + |style-md-content $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-md-content $ {} "\"& .md-p" $ {} (:margin "\"16px 0") (:line-height "\"1.6") :examples $ [] - |style-message-actions $ %{} :CodeEntry (:doc |) + |style-message-actions $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-actions $ {} "\"&" $ {} (:margin-top 6) (:justify-content :flex-end) (:width "\"100%") :examples $ [] - |style-message-area $ %{} :CodeEntry (:doc |) + |style-message-area $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-area $ {} "\"&" $ {} (:flex 2) (:overflow :scroll) :examples $ [] - |style-message-assistant $ %{} :CodeEntry (:doc |) + |style-message-assistant $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-assistant $ {} "\"&" $ {} (:align-self :flex-start) :examples $ [] - |style-message-box $ %{} :CodeEntry (:doc |) + |style-message-box $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-box $ {} "\"&" $ {} (:width "\"100%") (:max-width 1200) (:right "\"50%") (:padding "\"8px") (:margin :auto) (:transition-duration "\"300ms") (; :transform "\"translate(50%,0)") (:transition-property "\"height") "\"&:focus-within" $ {} (:opacity 1) (; :transform "\"translate(50%,0)") :examples $ [] - |style-message-box-panel $ %{} :CodeEntry (:doc |) + |style-message-box-panel $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-box-panel $ {} "\"&" $ {} (:position :absolute) (:bottom 0) (:opacity 1) (:width "\"100%") @@ -965,17 +1103,17 @@ :background-color $ hsl 0 0 100 0.9 :box-shadow $ str "\"0 0px 8px " (hsl 0 0 0 0.3) :examples $ [] - |style-message-item $ %{} :CodeEntry (:doc |) + |style-message-item $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-item $ {} "\"&" $ {} (:line-height "\"1.6") :examples $ [] - |style-message-list $ %{} :CodeEntry (:doc |) + |style-message-list $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-list $ {} "\"&" $ {} (:flex 2) (:padding "\"40px 16px 20vh 16px") (:width "\"100%") (:max-width 1200) (:margin :auto) (:position :relative) :examples $ [] - |style-message-role $ %{} :CodeEntry (:doc |) + |style-message-role $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-role $ {} "\"&" $ {} (:font-size 12) @@ -983,12 +1121,12 @@ :margin-bottom 6 :padding-right "\"16px" :examples $ [] - |style-message-text $ %{} :CodeEntry (:doc |) + |style-message-text $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-text $ {} "\"&" $ {} (:white-space :pre-wrap) (:line-height "\"1.6") (:margin 0) (:padding-right "\"16px") :examples $ [] - |style-message-user $ %{} :CodeEntry (:doc |) + |style-message-user $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-user $ {} "\"&" $ {} (:align-self :flex-end) @@ -1004,7 +1142,7 @@ :border-radius "\"2px" "\"&::-webkit-scrollbar-track" $ {} (:background-color :transparent) :examples $ [] - |style-more $ %{} :CodeEntry (:doc |) + |style-more $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-more $ {} "\"&" $ {} (:text-align :center) (:min-width 80) @@ -1017,12 +1155,12 @@ "\"&:hover" $ {} :box-shadow $ str "\"1px 1px 4px " (hsl 0 0 0 0.2) :examples $ [] - |style-reply-actions $ %{} :CodeEntry (:doc |) + |style-reply-actions $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-reply-actions $ {} "\"&" $ {} (:margin-top 6) (:justify-content :flex-start) (:width "\"100%") :examples $ [] - |style-reply-button $ %{} :CodeEntry (:doc |) + |style-reply-button $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-reply-button $ {} "\"&" $ {} (:text-align :center) (:min-width 80) @@ -1035,7 +1173,7 @@ "\"&:hover" $ {} :box-shadow $ str "\"1px 1px 4px " (hsl 0 0 0 0.2) :examples $ [] - |style-session-item $ %{} :CodeEntry (:doc |) + |style-session-item $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-session-item $ {} |& $ {} (:padding |12px) @@ -1047,23 +1185,29 @@ |:hover $ {} :background-color $ hsl 0 0 96 :examples $ [] - |style-sessions-list $ %{} :CodeEntry (:doc |) + |style-sessions-list $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-sessions-list $ {} |& $ {} (:flex |1) (:overflow-y :auto) (:min-width |300px) :examples $ [] - |style-submit $ %{} :CodeEntry (:doc |) + |style-submit $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-submit $ {} "\"&" $ {} :examples $ [] - |style-textbox $ %{} :CodeEntry (:doc |) + |style-textbox $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-textbox $ {} "\"&" $ {} (:border-radius 12) (:height "|max(100px,15vh)") (:width "\"100%") (:transition-duration "\"320ms") (:border :none) (:background-color :transparent) "\"&.focus-within" $ {} (:height "|max(240px,32vh)") (:border :none) (:box-shadow :none) :examples $ [] - |style-thinking $ %{} :CodeEntry (:doc |) + |style-textbox-compact $ %{} :CodeEntry (:doc |) (:schema nil) + :code $ quote + defstyle style-textbox-compact $ {} + "\"&" $ {} (:height 40) (:min-height 40) (:max-height 40) (:overflow :hidden) + "\"&.focus-within" $ {} (:height "|max(240px,32vh)") (:min-height "\"unset") (:max-height "\"unset") + :examples $ [] + |style-thinking $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-thinking $ {} "\"&" $ {} (:max-height 200) (:overflow :auto) (:padding "\"12px 16px") @@ -1076,9 +1220,10 @@ :border $ str "\"1px solid " (hsl 0 0 90) "\"& .md-p" $ {} (:margin "\"4px 0") :examples $ [] - |submit-message! $ %{} :CodeEntry (:doc |) + |submit-message! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn submit-message! (cursor state prompt-text search? think? model d!) (hint-fn async) + defn submit-message! (cursor state prompt-text search? think? model d!) + hint-fn $ {} (:async true) let state1 $ assoc state :messages append-user-message (:messages state) prompt-text @@ -1112,7 +1257,7 @@ d! cursor $ -> state (assoc :answer err-text) (assoc :loading? false) (assoc :done? true) assoc :messages $ upsert-assistant-message (:messages state) err-text nil :examples $ [] - |upsert-assistant-message $ %{} :CodeEntry (:doc |) + |upsert-assistant-message $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn upsert-assistant-message (messages content thinking) let @@ -1126,7 +1271,7 @@ -> last-msg (assoc :content content) (assoc :thinking thinking) conj messages0 $ {} (:role :assistant) (:content content) (:thinking thinking) :examples $ [] - :ns $ %{} :CodeEntry (:doc |) + :ns $ %{} :NsEntry (:doc |) :code $ quote ns app.comp.container $ :require (respo-ui.css :as css) respo.css :refer $ defstyle @@ -1142,35 +1287,32 @@ "\"../extension/get-selected" :refer $ get-selected memof.once :refer $ memof1-call memof1-call-by "\"../lib/image" :refer $ base64ToBlob - "\"openai" :default OpenAI feather.core :refer $ comp-i respo-alerts.core :refer $ [] use-modal-menu use-prompt use-drawer - genai.sdk :as sdk - :examples $ [] + respo-ui.util :refer $ tab-echo! |app.config $ %{} :FileEntry :defs $ {} - |chrome-extension? $ %{} :CodeEntry (:doc |) + |chrome-extension? $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def chrome-extension? $ and (some? js/window.chrome) (some? js/window.chrome.runtime) (some? js/window.chrome.runtime.id) :examples $ [] - |dev? $ %{} :CodeEntry (:doc |) + |dev? $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def dev? $ = "\"dev" (get-env "\"mode" "\"release") :examples $ [] - |site $ %{} :CodeEntry (:doc |) + |site $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def site $ {} (:storage-key "\"msg-buffer") :examples $ [] - :ns $ %{} :CodeEntry (:doc |) + :ns $ %{} :NsEntry (:doc |) :code $ quote (ns app.config) - :examples $ [] |app.main $ %{} :FileEntry :defs $ {} - |*reel $ %{} :CodeEntry (:doc |) + |*reel $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defatom *reel $ -> reel-schema/reel (assoc :base schema/store) (assoc :store schema/store) :examples $ [] - |connect-to-worker! $ %{} :CodeEntry (:doc |) + |connect-to-worker! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn connect-to-worker! () $ if and (some? js/window.chrome) (some? js/window.chrome.runtime) (some? js/window.chrome.runtime.connect) @@ -1183,7 +1325,7 @@ do (println "|Worker disconnected, retrying in 500ms...") (js/setTimeout connect-to-worker! 500) , nil :examples $ [] - |dispatch! $ %{} :CodeEntry (:doc |) + |dispatch! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn dispatch! (op) when @@ -1191,7 +1333,20 @@ js/console.log "\"Dispatch:" op reset! *reel $ reel-updater updater @*reel op :examples $ [] - |listen-extension! $ %{} :CodeEntry (:doc |) + |hydrate-storage-later! $ %{} :CodeEntry (:doc |) (:schema nil) + :code $ quote + defn hydrate-storage-later! () $ js/setTimeout + fn () $ let + raw $ js/localStorage.getItem (:storage-key config/site) + when (some? raw) + let + t_start $ .!now js/Date + dispatch! $ :: :hydrate-storage (parse-cirru-edn raw) + println "\"Hydrated in" + - (.!now js/Date) t_start + , "\"ms" + :examples $ [] + |listen-extension! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn listen-extension! () js/chrome.runtime.onMessage.addListener $ fn (message sender respond!) @@ -1226,9 +1381,11 @@ send-to-component! event-tuple connect-to-worker! :examples $ [] - |main! $ %{} :CodeEntry (:doc |) + |main! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn main! () + defn main! () $ let + t0 $ .!now js/Date + println "\"Starting main! at" t0 println "\"Running mode:" $ if config/dev? "\"dev" "\"release" if config/dev? $ load-console-formatter! render-app! @@ -1242,26 +1399,24 @@ fn (event) if (.-ctrlKey event) (.!preventDefault event) js-object $ :passive false - ; flipped js/setInterval 60000 persist-storage! - let - raw $ js/localStorage.getItem (:storage-key config/site) - when (some? raw) - dispatch! $ :: :hydrate-storage (parse-cirru-edn raw) + hydrate-storage-later! if config/chrome-extension? $ listen-extension! - println "|App started." + let + t1 $ .!now js/Date + println "|App started at" t1 |cost (- t1 t0) "\"ms" :examples $ [] - |mount-target $ %{} :CodeEntry (:doc |) + |mount-target $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def mount-target $ js/document.querySelector |.app :examples $ [] - |persist-storage! $ %{} :CodeEntry (:doc |) + |persist-storage! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn persist-storage! () println "\"Saved at" $ .!toISOString (new js/Date) js/localStorage.setItem (:storage-key config/site) format-cirru-edn $ :store @*reel :examples $ [] - |reload! $ %{} :CodeEntry (:doc |) + |reload! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn reload! () $ if (nil? build-errors) do (remove-watch *reel :changes) (clear-cache!) @@ -1270,11 +1425,19 @@ hud! "\"ok~" "\"Ok" hud! "\"error" build-errors :examples $ [] - |render-app! $ %{} :CodeEntry (:doc |) + |render-app! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn render-app! () $ render! mount-target (comp-container @*reel) dispatch! - :examples $ [] - :ns $ %{} :CodeEntry (:doc |) + defn render-app! () + let + t_start $ .!now js/Date + println "\"Rendering app..." + render! mount-target (comp-container @*reel) dispatch! + println "\"Rendered in" + - (.!now js/Date) t_start + , "\"ms" + render! mount-target (comp-container @*reel) dispatch! + :examples $ [] + :ns $ %{} :NsEntry (:doc |) :code $ quote ns app.main $ :require respo.core :refer $ render! clear-cache! @@ -1288,10 +1451,9 @@ "\"./calcit.build-errors" :default build-errors "\"bottom-tip" :default hud! respo.controller.client :refer $ send-to-component! - :examples $ [] |app.schema $ %{} :FileEntry :defs $ {} - |store $ %{} :CodeEntry (:doc |) + |store $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def store $ {} :states $ {} @@ -1300,12 +1462,11 @@ :current-session-id nil :model nil :examples $ [] - :ns $ %{} :CodeEntry (:doc |) + :ns $ %{} :NsEntry (:doc |) :code $ quote (ns app.schema) - :examples $ [] |app.updater $ %{} :FileEntry :defs $ {} - |updater $ %{} :CodeEntry (:doc |) + |updater $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn updater (store op op-id op-time) tag-match op @@ -1331,11 +1492,14 @@ or (:sessions store) ([]) fn (s) not $ = (:id s) id + (:clear-sessions) + -> store + assoc :sessions $ [] + assoc :current-session-id nil _ $ do (eprintln "\"unknown op:" op) store :examples $ [] - :ns $ %{} :CodeEntry (:doc |) + :ns $ %{} :NsEntry (:doc |) :code $ quote ns app.updater $ :require respo.cursor :refer $ update-states update-states-merge app.comp.container :refer $ save-current-session generate-session-id - :examples $ [] diff --git a/deps.cirru b/deps.cirru index e5b3ea9..f5f8751 100644 --- a/deps.cirru +++ b/deps.cirru @@ -1,11 +1,10 @@ -{} (:calcit-version |0.11.8) +{} (:calcit-version |0.12.11) :dependencies $ {} (|Memkits/genai.calcit |0.0.2) |Respo/alerts.calcit |0.10.9 |Respo/reel.calcit |main |Respo/respo-feather.calcit |main |Respo/respo-markdown.calcit |0.4.11 |Respo/respo-ui.calcit |0.6.4 - |Respo/respo.calcit |0.16.28 - |calcit-lang/lilac |main + |Respo/respo.calcit |0.16.32 |calcit-lang/memof |0.0.23 diff --git a/index.html b/index.html index eca3b2b..063b0d6 100644 --- a/index.html +++ b/index.html @@ -1,19 +1,26 @@ - - + + + Gemini Msg + + + + + + - - Gemini Msg - - - - - - - - -
- - - + +
+ + diff --git a/llms/Calcit.md b/llms/Calcit.md deleted file mode 100644 index edbf8ba..0000000 --- a/llms/Calcit.md +++ /dev/null @@ -1,870 +0,0 @@ -# Calcit 编程 Agent 指南 - -本文档为 AI Agent 提供 Calcit 项目的操作指南。 - -## 🚀 快速开始(新 LLM 必读) - -**核心原则:用命令行工具(不要直接编辑文件),用 search 定位(比逐层导航快 10 倍)** - -### 标准流程 - -```bash -# 搜索 → 修改 → 验证 -cr query search 'symbol' -f 'ns/def' # 1. 定位(输出:[3,2,1] in ...) -cr tree replace 'ns/def' -p '3,2,1' --leaf -e 'new' # 2. 修改 -cr tree show 'ns/def' -p '3,2,1' # 3. 验证(可选) -``` - -### 三种搜索方式 - -```bash -cr query search 'target' -f 'ns/def' # 搜索符号/字符串 -cr query search-expr 'fn (x)' -f 'ns/def' -l # 搜索代码结构 -cr tree replace-leaf 'ns/def' --pattern 'old' --replacement 'new' # 批量替换 -``` - -### 效率对比 - -| 操作 | 传统方法 | search 方法 | 效率 | -| ---------- | ----------------------- | ------------------- | -------- | -| 定位符号 | 逐层 `tree show` 10+ 步 | `query search` 1 步 | **10倍** | -| 查找表达式 | 手动遍历代码 | `search-expr` 1 步 | **10倍** | -| 批量重命名 | 手动找每处 | 自动列出所有位置 | **5倍** | - ---- - -## ⚠️ 重要警告:禁止直接修改的文件 - -以下文件**严格禁止使用文本替换或直接编辑**: - -- **`calcit.cirru`** - 这是 calcit-editor 结构化编辑器的专用格式,包含完整的编辑器元数据 -- **`compact.cirru`** - 这是 Calcit 程序的紧凑快照格式,必须使用 `cr edit` 相关命令进行修改 - -这两个文件的格式对空格和结构极其敏感,直接文本修改会破坏文件结构。请使用下面文档中的 CLI 命令进行代码查询和修改。 - -## Calcit 与 Cirru 的关系 - -- **Calcit** 是编程语言本身(一门类似 Clojure 的函数式编程语言) -- **Cirru** 是语法格式(缩进风格的 S-expression,类似去掉括号改用缩进的 Lisp) -- **关系**:Calcit 代码使用 Cirru 语法书写和存储 - -**具体体现:** - -- `compact.cirru` 和 `calcit.cirru` 是用 Cirru 格式存储的 Calcit 程序 -- `cr cirru` 工具用于 Cirru 语法与 JSON 的转换(帮助理解和生成代码) -- Cirru 语法特点: - - 用缩进代替括号(类似 Python/YAML) - - 字符串用前缀 `|` 或 `"` 标记(如 `|hello` 表示字符串 "hello") - - 单行用空格分隔元素(如 `defn add (a b) (+ a b)`) - -**类比理解:** - -- Python 语言 ← 使用 → Python 语法 -- Calcit 语言 ← 使用 → Cirru 语法 - -生成 Calcit 代码前,建议先运行 `cr cirru show-guide` 了解 Cirru 语法规则。 - ---- - -## Calcit CLI 命令 - -Calcit 程序使用 `cr` 命令: - -### 主要运行命令 - -- `cr` 或 `cr compact.cirru` - 代码解释执行,默认读取 config 执行 init-fn 定义的入口 -- `cr compact.cirru js` - 编译生成 JavaScript 代码 -- `cr -1 ` - 执行一次然后退出(不进入监听模式) -- `cr --check-only` - 仅检查代码正确性,不执行程序 - - 对 init_fn 和 reload_fn 进行预处理验证 - - 输出:预处理进度、warnings、检查耗时 - - 用于 CI/CD 或快速验证代码修改 -- `cr js -1` - 检查代码正确性,生成 JavaScript(不进入监听模式) -- `cr js --check-only` - 检查代码正确性,不生成 JavaScript -- `cr eval '' [--dep ...]` - 执行一段 Calcit 代码片段,用于快速验证写法 - - `--dep` 参数可以加载 `~/.config/calcit/modules/` 中的模块(直接使用模块名) - - 示例:`cr eval 'echo 1' --dep calcit.std` - - 可多次使用 `--dep` 加载多个模块 - -### 查询子命令 (`cr query`) - -这些命令用于查询项目信息: - -**项目全局分析:** - -- `cr analyze call-graph` - 分析从入口点开始的调用图结构 -- `cr analyze count-calls` - 统计每个定义的调用次数 - - _使用示例:_ - - ```bash - # 分析整个项目的调用图 - cr analyze call-graph - # 统计调用次数 - cr analyze count-calls - ``` - -**基础查询:** - -- `cr query ns [--deps]` - 列出项目中所有命名空间(--deps 包含依赖) -- `cr query ns ` - 读取命名空间详情(imports, 定义预览) -- `cr query defs ` - 列出命名空间中的定义 -- `cr query pkg` - 获取项目包名 -- `cr query config` - 读取项目配置(init_fn, reload_fn, version) -- `cr query error` - 读取 .calcit-error.cirru 错误堆栈文件 -- `cr query modules` - 列出项目依赖的模块(来自 compact.cirru 配置) - -**渐进式代码探索(Progressive Disclosure):** - -- `cr query peek ` - 查看定义签名(参数、文档、表达式数量),不返回完整实现体 - - 输出:Doc、Form 类型、参数列表、Body 表达式数量、首个表达式预览、Examples 数量 - - 用于快速了解函数接口,减少 token 消耗 -- `cr query def [-j]` - 读取定义的完整 Cirru 代码 - - 默认输出:Doc、Examples 数量、Cirru 格式代码 - - `-j` / `--json`:同时输出 JSON 格式(用于程序化处理) - - 推荐:LLM 直接读取 Cirru 格式即可,通常不需要 JSON -- `cr query examples ` - 读取定义的示例代码 - - 输出:每个 example 的 Cirru 格式和 JSON 格式 - -**符号搜索与引用分析:** - -- `cr query find [--deps] [-f] [-n ]` - 跨命名空间搜索符号 - - 默认精确匹配:返回定义位置 + 所有引用位置(带上下文预览) - - `-f` / `--fuzzy`:模糊搜索,匹配 "namespace/definition" 格式的路径 - - `-n `:限制模糊搜索结果数量(默认 20) - - `--deps`:包含核心命名空间(calcit.\* 开头) -- `cr query usages [--deps]` - 查找定义的所有使用位置 - - 返回:引用该定义的所有位置(带上下文预览) - - 用于理解代码影响范围,重构前的影响分析 - -**代码模式搜索(快速定位 ⭐⭐⭐):** - -- `cr query search [-f ] [-l]` - 搜索叶子节点(符号/字符串),比逐层导航快 10 倍 - - `-f ` - 过滤到特定命名空间或定义 - - `-l / --loose`:宽松匹配,包含模式 - - `-d `:限制搜索深度 - - `-p `:从指定路径开始搜索(如 `"3,2,1"`) - - 返回:完整路径 + 父级上下文,多个匹配时自动显示批量替换命令 - - 示例: - - `cr query search 'println' -f app.main/main!` - 精确搜索 - - `cr query search 'comp-' -f app.ui/layout -l` - 模糊搜索(所有 comp- 开头) - - `cr query search 'task-id' -f app.comp/render` - 返回所有匹配位置并自动排序 - -**高级结构搜索(搜索代码结构 ⭐⭐⭐):** - -- `cr query search-expr [-f ] [-l] [-j]` - 搜索结构表达式(List) - - `-l / --loose`:宽松匹配,查找包含连续子序列的结构 - - `-j / --json`:将模式解析为 JSON 数组 - - 示例: - - `cr query search-expr 'fn (x)' -f app.main/process -l` - 查找函数定义 - - `cr query search-expr '>> state task-id'` - 查找状态访问 - - `cr query search-expr 'memof1-call-by' -l` - 查找记忆化调用 - -**搜索结果格式:** `[索引1,索引2,...] in 父级上下文`,可配合 `cr tree show -p ''` 查看节点。**修改代码时优先用 search 命令,比逐层导航快 10 倍。** - -### LLM 辅助:动态方法提示 - -- `&inspect-class-methods` - 打印某个值对应 class 的方法清单(不改变原值) - - 用法:`(&inspect-class-methods value |optional note)` - - 用途:帮助 LLM 发现动态类型的方法与 proc 签名信息(不是测试/验证用途) - - 适合在 pipeline 中插入,快速查看方法、参数、命名空间和 proc 类型信息 - -### 文档子命令 (`cr docs`) - -查询 Calcit 语言文档(guidebook): - -- `cr docs search [-c ] [-f ]` - 按关键词搜索文档内容 - - `-c ` - 显示匹配行的上下文行数(默认 5) - - `-f ` - 按文件名过滤搜索结果 - - 输出:匹配行及其上下文,带行号和高亮 - - 示例:`cr docs search "macro" -c 10` 或 `cr docs search "defn" -f macros.md` - -- `cr docs read [-s ] [-n ]` - 阅读指定文档 - - `-s ` - 起始行号(默认 0) - - `-n ` - 读取行数(默认 80) - - 输出:文档内容、当前范围、是否有更多内容 - - 示例:`cr docs read macros.md` 或 `cr docs read intro.md -s 20 -n 30` - -- `cr docs list` - 列出所有可用文档 - -### Cirru 语法工具 (`cr cirru`) - -用于 Cirru 语法和 JSON 之间的转换: - -- `cr cirru parse ''` - 解析 Cirru 代码为 JSON -- `cr cirru format ''` - 格式化 JSON 为 Cirru 代码 -- `cr cirru parse-edn ''` - 解析 Cirru EDN 为 JSON -- `cr cirru show-guide` - 显示 Cirru 语法指南(帮助 LLM 生成正确的 Cirru 代码) - -**⚠️ 重要:生成 Cirru 代码前请先阅读语法指南** - -运行 `cr cirru show-guide` 获取完整的 Cirru 语法说明,包括: - -- `$` 操作符(单节点展开) -- `|` 前缀(字符串字面量), 这个是 Cirru 特殊的地方, 而不是直接用引号包裹 -- `,` 操作符(注释标记) -- `~` 和 `~@`(宏展开) -- 常见错误和避免方法 - -### 库管理 (`cr libs`) - -查询和了解 Calcit 官方库: - -- `cr libs` - 列出所有官方库 -- `cr libs search ` - 按关键词搜索库(搜索名称、描述、分类) -- `cr libs readme [-f ]` - 查看库的文档 - - 优先从本地 `~/.config/calcit/modules/` 读取 - - 本地不存在时从 GitHub 仓库获取 - - `-f` 参数可指定其他文档文件(如 `-f Skills.md`) - - 默认读取 `README.md` -- `cr libs scan-md ` - 扫描本地模块目录下的所有 `.md` 文件 - - 递归扫描子目录 - - 显示相对路径列表 -- `caps` - 安装/更新依赖 - -**查看已安装模块:** - -```bash -# 列出 ~/.config/calcit/modules/ 下所有已安装的模块 -ls ~/.config/calcit/modules/ - -# 查看当前项目配置的模块依赖 -cr query modules -``` - -### 精细代码树操作 (`cr tree`) - -⚠️ **关键警告:路径索引动态变化** - -删除或插入节点后,同级后续节点的索引会自动改变。**必须从后往前操作**或**每次修改后重新搜索路径**。 - -**核心概念:** - -- 路径格式:逗号分隔的索引(如 `"3,2,1"`),空字符串 `""` 表示根节点 -- 每个命令都有 `--help` 查看详细参数 -- 命令执行后会显示 "Next steps" 提示下一步操作 - -**主要操作:** - -- `cr tree show -p '' [-j]` - 查看节点 - - 默认输出:节点类型、Cirru 预览、子节点索引列表、操作提示 - - `-j` / `--json`:同时输出 JSON 格式(用于程序化处理) - - 推荐:直接查看 Cirru 格式即可,通常不需要 JSON -- `cr tree replace` - 替换节点 -- `cr tree replace-leaf` - 查找并替换所有匹配的 leaf 节点(无需指定路径) - - `--pattern ` - 要搜索的模式(精确匹配 leaf 节点) - - `--replacement ` - 替换值(默认作为 leaf 节点处理) - - 自动遍历整个定义,一次性替换所有匹配项 - - 示例:`cr tree replace-leaf 'ns/def' --pattern 'old-name' --replacement 'new-name'` -- `cr tree delete` - 删除节点 -- `cr tree insert-before/after` - 插入相邻节点 -- `cr tree insert-child/append-child` - 插入子节点 -- `cr tree swap-next/prev` - 交换相邻节点 -- `cr tree wrap` - 用新结构包装节点 - -**输入方式(通用):** - -- `-e ''` - 内联代码(自动识别 Cirru/JSON) -- `--leaf` - 强制作为 leaf 节点(符号或字符串) -- `-j ''` / `-f ` / `-s` (stdin) - -**推荐工作流(高效定位 ⭐⭐⭐):** - -```bash -# ===== 方案 A:单点修改(精确定位) ===== - -# 1. 快速定位目标节点(一步到位) -cr query search "target-symbol" -f namespace/def -# 输出:[3,2,5,1] in (fn (x) target-symbol ...) - -# 2. 直接修改(路径已知) -cr tree replace namespace/def -p '3,2,5,1' --leaf -e 'new-symbol' - -# 3. 验证结果(可选) -cr tree show namespace/def -p '3,2,5,1' - - -# ===== 方案 B:批量重命名(多处修改) ===== - -# 1. 搜索所有匹配位置 -cr query search "old-name" -f namespace/def -# 自动显示:4 处匹配,已按路径从大到小排序 -# [3,2,5,8] [3,2,5,2] [3,1,0] [2,1] - -# 2. 按提示从后往前修改(避免路径变化) -cr tree replace namespace/def -p '3,2,5,8' --leaf -e 'new-name' -cr tree replace namespace/def -p '3,2,5,2' --leaf -e 'new-name' -# ... 继续按序修改 - -# 或:一次性替换所有匹配项 -cr tree replace-leaf namespace/def --pattern 'old-name' --replacement 'new-name' - - -# ===== 方案 C:结构搜索(查找表达式) ===== - -# 1. 搜索包含特定模式的表达式 -cr query search-expr "fn (task)" -f namespace/def -l -# 输出:[3,2,2,5,2,4,1] in (map $ fn (task) ...) - -# 2. 查看完整结构(可选) -cr tree show namespace/def -p '3,2,2,5,2,4,1' - -# 3. 修改整个表达式或子节点 -cr tree replace namespace/def -p '3,2,2,5,2,4,1,2' -e 'let ((x 1)) (+ x task)' -``` - -**关键技巧:** - -- **优先使用 `search` 系列命令**:比逐层导航快 10+ 倍,一步直达目标 -- **路径格式**:`"3,2,1"` 表示第3个子节点 → 第2个子节点 → 第1个子节点 -- **批量修改自动提示**:搜索找到多处时,自动显示路径排序和批量替换命令 -- **路径动态变化**:删除/插入后,同级后续索引会变化,按提示从后往前操作 -- 所有命令都会显示 Next steps 和操作提示 - -**结构化变更示例:** - -这些高级操作允许你在修改时引用原始节点及其内部结构: - -- **包裹节点**(使用 `cr tree wrap` 或 `cr tree replace` 的 `--refer-original`): - - ```bash - # 将路径 "3,2" 的节点包裹在 println 中 - cr tree wrap ns/def -p '3,2' -e 'println $$$$' --refer-original '$$$$' - ``` - -- **重构并复用原子节点**(使用 `--refer-inner-branch`): - - 假设原节点是 `(+ 1 2)` (路径 "3,1"),其子节点索引 1 是 `1`,索引 2 是 `2` - - 将其重构为 `(* 2 10)`: - - ```bash - cr tree replace ns/def -p '3,1' -e '(* #### 10)' --refer-inner-branch '2' --refer-inner-placeholder '####' - ``` - -- **多处重用原始节点**: - ```bash - # 将节点 x 变为 (+ x x) - cr tree replace ns/def -p '2' -e '(+ $ $)' --refer-original '$' - ``` - 详细参数和示例使用 `cr tree --help` 查看。 - -### 代码编辑 (`cr edit`) - -直接编辑 compact.cirru 项目代码,支持三种输入方式: - -- `--file ` 或 `-f ` - 从文件读取(默认 Cirru 格式,使用 `-J` 指定 JSON) -- `--json ` 或 `-j ` - 内联 JSON 字符串 -- `--stdin` 或 `-s` - 从标准输入读取(默认 Cirru 格式,使用 `-J` 指定 JSON) - -额外支持“内联代码”参数: - -- `--code ` 或 `-e `:直接在命令行里传入一段代码。 - - 默认按 **Cirru 单行表达式(one-liner)** 解析。 - - 如果输入“看起来像 JSON”(例如 `-e '"abc"'`,或 `-e '["a"]'` 这类 `[...]` 且包含 `"`),则会按 JSON 解析。 - - ⚠️ 当输入看起来像 JSON 但 JSON 不合法时,会直接报错(不会回退当成 Cirru one-liner)。 - -对 `--file/--stdin` 输入,还支持以下“格式开关”(与 `-J/--json-input` 类似): - -- `--leaf`:把输入当成 **leaf 节点**,直接使用 Cirru 符号或 `|text` 字符串,无需 JSON 引号。 - - 传入符号:`-e 'my-symbol'` - - 传入字符串:加 Cirru 字符串前缀 `|` 或 `"`,例如 `-e '|my string'` 或 `-e '"my string'` - -⚠️ 注意:这些开关彼此互斥(一次只用一个)。 - -**推荐简化规则(命令行更好写):** - -- **JSON(单行)**:优先用 `-j ''` 或 `-e ''`(不需要 `-J`)。 -- **Cirru 单行表达式**:用 `-e ''`(`-e` 默认按 one-liner 解析)。 -- **Cirru 多行缩进**:用 `-f file.cirru` 或 `-s`(stdin)。 -- `-J/--json-input` 主要用于 **file/stdin** 读入 JSON(如 `-f code.json -J` 或 `-s -J`)。 - -补充:`-e/--code` 只有在 `[...]` 内部包含 `"` 时才会自动按 JSON 解析(例如 `-e '["a"]'`)。 -像 `-e '[]'` / `-e '[ ]'` 会默认按 Cirru one-liner 处理;如果你需要“空 JSON 数组”,用显式 JSON:`-j '[]'`。 - -如果你想在命令行里明确“这段就是 JSON”,请用 `-j ''`(`-J` 是给 file/stdin 用的)。 - -**定义操作:** - -- `cr edit def ` - 添加新定义(若已存在会报错,需用 `cr tree replace` 修改) -- `cr edit rm-def ` - 删除定义 -- `cr edit doc ''` - 更新定义的文档 -- `cr edit examples ` - 设置定义的示例代码(批量替换) -- `cr edit add-example ` - 添加单个示例 -- `cr edit rm-example ` - 删除指定索引的示例(0-based) - -**命名空间操作:** - -- `cr edit add-ns ` - 添加命名空间 -- `cr edit rm-ns ` - 删除命名空间 -- `cr edit imports ` - 更新导入规则(全量替换) -- `cr edit add-import ` - 添加单个 import 规则 -- `cr edit rm-import ` - 移除指定来源的 import 规则 -- `cr edit ns-doc ''` - 更新命名空间文档 - -**模块和配置:** - -- `cr edit add-module ` - 添加模块依赖 -- `cr edit rm-module ` - 删除模块依赖 -- `cr edit config ` - 设置配置(key: init-fn, reload-fn, version) - -**增量变更导出:** - -- `cr edit inc` - 记录增量代码变更并导出到 `.compact-inc.cirru`,触发 watcher 热更新 - - `--added ` - 标记新增的定义 - - `--changed ` - 标记修改的定义 - - `--removed ` - 标记删除的定义 - - `--added-ns ` - 标记新增的命名空间 - - `--removed-ns ` - 标记删除的命名空间 - - `--ns-updated ` - 标记命名空间导入变更 - - 配合 watcher 使用实现热更新(详见"开发调试"章节) - -使用 `--help` 参数了解详细的输入方式和参数选项。 - ---- - -## Calcit 语言基础 - -### Cirru 语法核心概念 - -**与其他 Lisp 的区别:** - -- **缩进语法**:用缩进代替括号(类似 Python/YAML),单行用空格分隔 -- **字符串前缀**:`|hello` 或 `"hello"` 表示字符串,`|` 前缀更简洁 -- **无方括号花括号**:只用圆括号概念(体现在 JSON 转换中),Cirru 文本层面无括号 - -**常见混淆点:** - -❌ **错误理解:** Calcit 字符串是 `"x"` → JSON 是 `"\"x\""` -✅ **正确理解:** Cirru `|x` → JSON `"x"`,Cirru `"x"` → JSON `"x"` - -**字符串 vs 符号的关键区分:** - -- `|Add` 或 `"Add` → **字符串**(用于显示文本、属性值等, 前缀形式区分字面量类型) -- `Add` → **符号/变量名**(Calcit 会在作用域中查找) -- 常见错误:受其他语言习惯影响,忘记加 `|` 前缀导致 `unknown symbol` 错误 - -**CLI 使用提示:** - -- 替换包含空格的字符串:`--leaf -e '|text with spaces'` 或 `-j '"text"'` -- 避免解析为列表:字符串字面量必须用 `--leaf` 或 `-j` 明确标记 - -**示例对照:** - -| Cirru 代码 | JSON 等价 | JavaScript 等价 | -| ---------------- | -------------------------------- | ------------------------ | -| `\|hello` | `"hello"` | `"hello"` | -| `"world"` | `"world"` | `"world"` | -| `\|a b c` | `"a b c"` | `"a b c"` | -| `fn (x) (+ x 1)` | `["fn", ["x"], ["+", "x", "1"]]` | `fn(x) { return x + 1 }` | - -### 数据结构:Tuple vs Vector - -Calcit 特有的两种序列类型: - -**Tuple (`::`)** - 不可变、用于模式匹配 - -```cirru -; 创建 tuple -:: :event/type data - -; 模式匹配 -tag-match event - (:event/click data) (handle-click data) - (:event/input text) (handle-input text) -``` - -**Vector (`[]`)** - 可变、用于列表操作 - -```cirru -; 创建 vector -[] item1 item2 item3 - -; DOM 列表 -div {} $ [] - button {} |Click - span {} |Text -``` - -**常见错误:** - -```cirru -; ❌ 错误:用 vector 传事件 -send-event! $ [] :clipboard/read text -; 报错:tag-match expected tuple - -; ✅ 正确:用 tuple -send-event! $ :: :clipboard/read text -``` - -### 类型标注与检查 - -Calcit 支持可选的类型标注,用于开发时的类型检查。 - -#### 函数参数类型 (`assert-type`) - -```cirru -defn calculate-total (items discount) - assert-type items :list - assert-type discount :number - ; let 绑定中的变量会自动推断类型 - let - sum $ foldl items 0 &+ - * sum $ - 1 discount -``` - -#### 函数返回类型 (`hint-fn`) - -```cirru -defn add-numbers (a b) - assert-type a :number - assert-type b :number - hint-fn $ return-type :number - &+ a b -``` - -**常见类型:** `:number` `:string` `:bool` `:list` `:map` `:set` `:tuple` `:keyword` `:nil` - -**验证类型:** `cr --check-only` 或 `cr ir -1` 查看 IR 中的类型信息 - -### 其他易错点 - -比较容易犯的错误: - -- Calcit 中字符串通过前缀区分,`|` 和 `"` 开头表示字符串。`|x` 对应 JavaScript 字符串 `"x"`。产生 JSON 时注意不要重复包裹引号。 -- Calcit 采用 Cirru 缩进语法,可以理解成去掉跨行括号改用缩进的 Lisp 变种。用 `cr cirru parse` 和 `cr cirru format` 互相转化试验。 -- Calcit 跟 Clojure 在语义上比较像,但语法层面只用圆括号,不用方括号花括号。 - ---- - -## 开发调试 - -简单脚本用 `cr -1 ` 直接执行。编译 JavaScript 用 `cr -1 js` 执行一次编译。 - -Calcit snapshot 文件中 config 有 `init-fn` 和 `reload-fn` 配置: - -- 初次启动调用 `init-fn` -- 每次修改代码后调用 `reload-fn` - -**典型开发流程:** - -```bash -# 1. 启动监听模式(用户自行使用) -cr # 解释执行模式 -cr js # JS 编译模式 - -# 2. 修改代码后触发增量更新(详见"增量触发更新"章节) -cr edit inc --changed ns/def - -# 3. 一次性执行/编译(用于简单脚本) -cr -1 # 执行一次 -cr -1 js # 编译一次 -``` - -### 增量触发更新(推荐)⭐⭐⭐ - -当使用监听模式(`cr` 或 `cr js`)开发时,推荐使用 `cr edit inc` 命令触发增量更新,而非全量重新编译/执行: - -**工作流程:** - -```bash -# 【终端 1】启动 watcher(监听模式) -cr # 或 cr js - -# 【终端 2】修改代码后触发增量更新 -# 修改定义 -cr edit def app.core/my-fn -e 'defn my-fn (x) (+ x 1)' - -# 触发增量更新 -cr edit inc --changed app.core/my-fn - -# 等待 ~300ms 后查看编译结果 -cr query error -``` - -**增量更新命令参数:** - -```bash -# 新增定义 -cr edit inc --added namespace/definition - -# 修改定义 -cr edit inc --changed namespace/definition - -# 删除定义 -cr edit inc --removed namespace/definition - -# 新增命名空间 -cr edit inc --added-ns namespace - -# 删除命名空间 -cr edit inc --removed-ns namespace - -# 更新命名空间导入 -cr edit inc --ns-updated namespace - -# 组合使用(批量更新) -cr edit inc \ - --changed app.core/add \ - --changed app.core/multiply \ - --removed app.core/old-fn -``` - -**查看编译结果:** - -```bash -cr query error # 命令会显示详细的错误信息或成功状态 -``` - -**何时使用全量操作:** - -```bash -# 极少数情况:增量更新不符合预期时 -cr -1 js # 重新编译 JavaScript -cr -1 # 重新执行程序 - -# 或重启监听模式(Ctrl+C 停止后重启) -cr # 或 cr js - -# CI/CD 或脚本验证(不启动 watcher) -cr --check-only # 仅语法检查 -``` - -**增量更新优势:** 快速反馈、精确控制变更范围、watcher 保持运行状态 - ---- - -## 文档支持 - -遇到疑问时使用: - -- `cr docs search ` - 搜索 Calcit 教程内容 -- `cr docs read ` - 阅读完整文档 -- `cr docs list` - 查看所有可用文档 -- `cr query ns ` - 查看命名空间说明和函数文档 -- `cr query peek ` - 快速查看定义签名 -- `cr query def ` - 读取完整语法树 -- `cr query examples ` - 查看示例代码 -- `cr query find ` - 跨命名空间搜索符号 -- `cr query usages ` - 查找定义的使用位置 -- `cr query search [-f ]` - 搜索叶子节点 -- `cr query search-expr [-f ]` - 搜索结构表达式 -- `cr query error` - 查看最近的错误堆栈 - ---- - -## 代码修改示例 - -**添加新函数:** - -````bash -# Cirru one liner -cr edit def app.core/multiply -e 'defn multiply (x y) (* x y)' -# 基本操作:** - -```bash -# 添加新函数(命令会提示 Next steps) -cr edit def 'app.core/multiply' -e 'defn multiply (x y) (* x y)' - -# 替换整个定义(-p '' 表示根路径) -cr tree replace 'app.core/multiply' -p '' -e 'defn multiply (x y z) (* x y z)' - -# 更新文档和示例 -cr edit doc 'app.core/multiply' '乘法函数,返回两个数的积' -cr edit add-example 'app.core/multiply' -e 'multiply 5 6' -```` - -**修改定义工作流(命令会显示子节点索引和 Next steps):** - -```bash -# 1. 搜索定位 -cr query search '' -f 'ns/def' -l - -# 2. 查看节点(输出会显示索引和操作提示) -cr tree show 'ns/def' -p '' - -# 3. 执行替换(会显示 diff 和验证命令) -cr tree replace 'ns/def' -p '' --leaf -e '' - -# 4. 检查结果 -cr query error -# 添加命名空间 -cr edit add-ns app.util - -# 添加导入规则 -cr edit add-import app.main -e 'app.util :refer $ helper' - -# 移除导入规则 -cr edit rm-import app.main app.util - -# 更新项目配置 -cr edit config init-fn app.main/main! -``` - -**更新命名空间导入(全量替换):** - -```bash -cr edit imports app.main -j '[["app.lib", ":as", "lib"], ["app.util", ":refer", ["helper"]]]' -``` - ---- - -## ⚠️ 常见陷阱和最佳实践 - -### 1. 路径索引动态变化问题 ⭐⭐⭐ - -**核心原则:** 删除/插入会改变同级后续节点索引。 - -**批量修改策略:** - -- **从后往前操作**(推荐):先删大索引,再删小索引 -- **单次操作后重新搜索**:每次修改立即用 `cr query search` 更新路径 -- **整体重写**:用 `cr tree replace -p ""` 替换整个定义 - -命令会在路径错误时提示最长有效路径和可用子节点。 - -### 2. 输入格式参数使用速查 ⭐⭐⭐ - -**参数混淆矩阵(已全面支持 `-e` 自动识别):** - -| 场景 | 示例用法 | 解析结果 | 说明 | -| ------------------- | -------------------------------------- | ----------------------------- | --------------------------------- | -| **表达式 (Cirru)** | `-e 'defn add (a b) (+ a b)'` | `["defn", "add", ...]` (List) | 默认按 Cirru one-liner 解析 | -| **原子符号 (Leaf)** | `--leaf -e 'my-symbol'` | `"my-symbol"` (Leaf) | **推荐**,避免被包装成 list | -| **字符串 (Leaf)** | `--leaf -e '\|hello world'` | `"hello world"` (Leaf) | 符号前缀 `\|` 表示字符串 | -| **JSON 数组** | `-e '["+", "x", "1"]'` | `["+", "x", "1"]` (List) | **自动识别** (含 `[` 且有 `"`) | -| **JSON 字符串** | `-e '"my leaf"'` | `"my leaf"` (Leaf) | **自动识别** (含引用的字符串) | -| **内联 JSON** | `-j '["defn", ...]'` | `["defn", ...]` (List) | 显式按 JSON 解析,忽略 Cirru 规则 | -| **外部文件** | `-f code.cirru` (或 `-f code.json -J`) | 根据文件内容解析 | `-J` 用于标记文件内是 JSON | - -**核心规则:** - -1. **智能识别模式**:`-e / --code` 现在会自动识别 JSON。如果你传入 `["a"]` 或 `"a"`,它会直接按 JSON 处理,无需再额外加 `-J` 或 `-j`。 -2. **强制 Leaf 模式**:如果你需要确保输入是一个叶子节点(符号或字符串),请在任何地方使用 `--leaf` 开关。它会将原始输入直接作为内容,不经过任何解析。 -3. **显式 JSON 模式**:如果你想明确告诉工具“这段就是 JSON”,优先用 `-j ''`。 -4. **统一性**:`cr tree` 和 `cr edit` 的所有子命令(replace, def, insert 等)现在共享完全相同的输入解析逻辑。 - -**实战示例:** - -```bash -# ✅ 替换表达式 -cr tree replace app.main/fn -p "2" -e 'println |hello' - -# ✅ 替换 leaf(推荐 --leaf) -cr tree replace app.main/fn -p "2,0" --leaf -e 'new-symbol' - -# ✅ 替换字符串 leaf -cr tree replace app.main/fn -p "2,1" --leaf -e '|new text' - -# ❌ 避免:用 -e 传单个 token(会变成 list) -cr tree replace app.main/fn -p "2,0" -e 'symbol' # 结果:["symbol"] -``` - -### 3. Cirru 字符串和数据类型 ⭐⭐ - -**Cirru 字符串前缀:** - -| Cirru 写法 | JSON 等价 | 使用场景 | -| -------------- | -------------- | ------------ | -| `\|hello` | `"hello"` | 推荐,简洁 | -| `"hello"` | `"hello"` | 也可以 | -| `\|a b c` | `"a b c"` | 包含空格 | -| `\|[tag] text` | `"[tag] text"` | 包含特殊字符 | - -**Tuple vs Vector:** - -```cirru -; ✅ Tuple - 用于事件、模式匹配 -:: :clipboard/read text - -; ✅ Vector - 用于 DOM 列表 -[] (button) (div) - -; ❌ 错误:用 vector 传事件 -send-to-component! $ [] :clipboard/read text -; 报错:tag-match expected tuple - -; ✅ 正确:用 tuple -send-to-component! $ :: :clipboard/read text -``` - -**记忆规则:** - -- **`::` (tuple)**: 事件、模式匹配、不可变数据结构 -- **`[]` (vector)**: DOM 元素列表、动态集合 - -### 4. 推荐工作流程 - -**基本流程(search 快速定位 ⭐⭐⭐):** - -```bash -# 1. 快速定位(比逐层导航快10倍) -cr query search 'target' -f 'ns/def' # 或 search-expr 'fn (x)' -l 搜索结构 - -# 2. 执行修改(会显示 diff 和验证命令) -cr tree replace 'ns/def' -p "" --leaf -e '' - -# 3. 增量更新(推荐) -cr edit inc --changed ns/def -# 等待 ~300ms 后检查 -cr query error -``` - -**新手提示:** - -- 不知道目标在哪?用 `search` 或 `search-expr` 快速找到所有匹配 -- 想了解代码结构?用 `tree show` 逐层探索 -- 需要批量重命名?搜索后按提示从大到小路径依次修改 -- 不确定修改是否正确?每步后用 `tree show` 验证 - -### 5. Shell 特殊字符转义 ⭐⭐ - -Calcit 函数名中的 `?`, `->`, `!` 等字符在 bash/zsh 中有特殊含义,需要用单引号包裹: - -```bash -# ❌ 错误 -cr query def app.main/valid? -cr eval '-> x (+ 1) (* 2)' - -# ✅ 正确 -cr query def 'app.main/valid?' -cr eval 'thread-first x (+ 1) (* 2)' # 用 thread-first 代替 -> -``` - -**建议:** 命令行中优先使用英文名称(`thread-first` 而非 `->`),更清晰且无需转义。 - ---- - -## 💡 Calcit vs Clojure 关键差异 - -**语法层面:** - -- **只用圆括号**:Calcit 的 Cirru 语法不使用方括号 `[]` 和花括号 `{}`,统一用缩进表达结构 -- **函数前缀**:Calcit 用 `&` 区分内置函数(`&+`、`&str`)和用户定义函数 - -**集合函数参数顺序(易错 ⭐⭐⭐):** - -- **Calcit**: 集合在**第一位** → `map data fn` 或 `-> data (map fn)` -- **Clojure**: 函数在第一位 → `(map fn data)` 或 `(->> data (map fn))` -- **症状**:`unknown data for foldl-shortcut` 报错 -- **原因**:误用 `->>` 或参数顺序错误 - -**其他差异:** - -- **宏系统**:Calcit 更简洁,缺少 Clojure 的 reader macro(如 `#()`) -- **数据类型**:Calcit 的 Tuple (`::`) 和 Vector (`[]`) 有特定用途(见"Cirru 字符串和数据类型") - ---- - -## 常见错误排查 - -| 错误信息 | 原因 | 解决方法 | -| ---------------------------- | ----------------------- | --------------------------------- | -| `Path index X out of bounds` | 路径已过期 | 重新运行 `cr query search` | -| `tag-match expected tuple` | 传入 vector 而非 tuple | 改用 `::` | -| 字符串被拆分 | 没有用 `\|` 或 `"` 包裹 | 使用 `\|complete string` | -| `unexpected format` | 语法错误 | 用 `cr cirru parse ''` 验证 | - -**调试命令:** `cr query error`(会显示详细的错误堆栈和提示) - ---- - -Also read `llms/Respo.md` for framework usage. diff --git a/llms/Respo.md b/llms/Respo.md deleted file mode 100644 index 244336a..0000000 --- a/llms/Respo.md +++ /dev/null @@ -1,918 +0,0 @@ -# Respo Development Guide for LLM Agents - -**🤖 This guide is specifically designed for LLM agents to develop, debug, and maintain Respo applications.** - -📚 **Related Documentation**: - -- [← Back to README](../README.md) -- [Beginner Guide](./beginner-guide.md) -- [CLI Tools Reference](../Agents.md) -- [API Reference](./api.md) - ---- - -## Project Structure - -The Respo project is a virtual DOM library written in Calcit-js, containing: - -- **Main codebase**: `compact.cirru` (2314 lines) - serialized source code -- **Compiled source**: `calcit.cirru` (13806 lines) - full AST representation -- **Namespaces**: 33 total namespaces organized by functionality -- **Version**: 0.16.21 -- **Dependencies**: memof (memoization), lilac (UI utilities), calcit-test (testing) - -### Core Namespace Organization - -**User-facing APIs** (what you typically use): - -- `respo.core` - Core APIs: defcomp, div, render!, clear-cache!, etc. -- `respo.comp.space` - Utility component comp-space (=<) -- `respo.comp.inspect` - Debugging component comp-inspect -- `respo.render.html` - HTML generation: make-string, make-html - -**Application layer** (in example app): - -- `respo.app.core` - Main application logic (\*store, dispatch!, render-app!) -- `respo.app.schema` - Data structures and schemas -- `respo.app.updater` - State management and updates -- `respo.app.comp.*` - Application components (container, task, todolist, wrap, zero) -- `respo.app.style.widget` - Application styles - -**Rendering and internal** (low-level): - -- `respo.render.diff` - Find differences between virtual DOM trees -- `respo.render.dom` - DOM element creation and manipulation -- `respo.render.effect` - Component lifecycle effects -- `respo.render.patch` - Apply DOM patches -- `respo.controller.client` - Client-side state management (activate-instance!, patch-instance!, send-to-component!) -- `respo.controller.resolve` - Event handling and resolution (build-deliver-event, wrap-dispatch) - -**Utilities**: - -- `respo.util.dom` - DOM utilities -- `respo.util.format` - Element formatting (purify-element, mute-element) -- `respo.util.list` - List utilities (map-val, map-with-idx) -- `respo.util.detect` - Type detection (component?, element?, effect?) -- `respo.css` - CSS utilities -- `respo.cursor` - Cursor management for nested states - ---- - -## Essential Calcit CLI Commands for Development - -### 1. Exploration and Discovery - -```bash -# List all namespaces in the project -cr query ns - -# Get details about a specific namespace (imports, definitions) -cr query ns respo.core -cr query ns respo.app.core - -# List all definitions in a namespace -cr query defs respo.core -cr query defs respo.app.updater - -# Quick peek at a definition (signature, parameters, docs) -cr query peek respo.core/defcomp -cr query peek respo.core/render! - -# Get complete definition as JSON syntax tree -cr query def respo.core/render! -cr query def respo.app.core/dispatch! - -# Search for a symbol across all namespaces -cr query find render! -cr query find *store - -# Find all usages of a specific definition -cr query usages respo.core/render! -cr query usages respo.app.core/dispatch! -``` - -### 2. Precise Code Navigation (tree pattern) - -When you need to understand or modify specific parts of a definition: - -```bash -# Step 1: Read the complete definition first -cr query def respo.app.updater/updater - -# Step 2: Use tree show to examine the structure (limit depth to reduce output) -cr tree show respo.app.updater/updater -p "" -d 1 # View root level - -# Step 3: Dive deeper into specific indices -cr tree show respo.app.updater/updater -p "2" -d 1 # Check 3rd element -cr tree show respo.app.updater/updater -p "2,1" -d 1 # Check 2nd child of 3rd element - -# Step 4: Confirm target location before editing -cr tree show respo.app.updater/updater -p "2,1,0" # Final confirmation - -# Step 5: Use tree commands for surgical modifications -# JSON inline (recommended) -cr tree replace respo.app.updater/updater -p "2,1,0" -j '"new-value"' -# Or from stdin -echo '"new-value"' | cr tree replace respo.app.updater/updater -p "2,1,0" -s -J -``` - -echo '["defn", "hello", [], ["println", "|Hello"]]' | cr edit def respo.app.core/hello -s -J - -### 3. Code Modification (Agent Optimized) - -**Best Practice: Use JSON AST** -For LLM Agents, **JSON inline (`-j`) is the most reliable method** for code generation. It avoids whitespace/indentation ambiguity inherent in Cirru. - -**Input Modes:** - -- `-j ''`: **Recommended.** Inline JSON string. Escape quotes carefully. -- `-e ''`: Inline Cirru one-liner. Good for short, simple expressions. -- `-f ` / `-s`: Read from file/stdin (defaults to Cirru). -- `-J`: Combine with `-f`/`-s` to indicate JSON input. - -**JSON AST Structure Guide:** - -- Function: `(defn f (x) x)` -> `["defn", "f", ["x"], "x"]` -- Map: `{:a 1}` -> `["{}", [":a", "1"]]` -- String: `"|hello"` -> `"|hello"` (in JSON string: `"\"|hello\""`) -- Keyword: `:key` -> `":key"` - -**Common Commands:** - -```bash -# 1. Add/Update Definition (JSON) -# (defn greet (name) (println "|Hello" name)) -cr edit def respo.demo/greet -j '["defn", "greet", ["name"], ["println", "\"|Hello\"", "name"]]' - -# 2. Add Definition (Cirru One-liner - risky for complex code) -cr edit def respo.demo/simple -e 'defn simple (x) (+ x 1)' - -# 3. Update Imports (JSON) -# (ns respo.demo (:require [respo.core :refer [div span]])) -cr edit imports respo.demo -j '[["respo.core", ":refer", ["div", "span"]]]' - -# 4. Remove Definition -cr edit rm-def respo.demo/old-fn - -# 5. Namespace Operations -cr edit add-ns respo.new-feature -cr edit rm-ns respo.deprecated -``` - -**💡 Pro Tip: Validation** -If unsure about the JSON structure, generate it from Cirru first: - -```bash -cr cirru parse -O 'defn f (x) (+ x 1)' -# Output: ["defn", "f", ["x"], ["+", "x", "1"]] -``` - -### 4. Project Configuration - -```bash -# Get project configuration (init-fn, reload-fn, version) -cr query config - -# Set project configuration -cr edit config version "0.16.22" -cr edit config init-fn "respo.main/main!" -cr edit config reload-fn "respo.main/reload!" -``` - -### 5. Workflow: Building From Scratch - -Follow this sequence to create a new feature cleanly: - -**Step 1: Create Namespace** - -```bash -cr edit add-ns respo.app.feature-x -``` - -**Step 2: Add Imports** -Define dependencies (e.g., `respo.core`). - -```bash -# Cirru: (:require [respo.core :refer [defcomp div span]]) -cr edit imports respo.app.feature-x -j '[["respo.core", ":refer", ["defcomp", "div", "span"]]]' -``` - -**Step 3: Create Component** -Define the component logic. - -```bash -# Cirru: (defcomp comp-x (data) (div {} (<> "Feature X"))) -cr edit def respo.app.feature-x/comp-x -j '["defcomp", "comp-x", ["data"], ["div", ["{}"], ["<>", "\"|Feature X\""]]]' -``` - -**Step 4: Verify** - -```bash -cr query def respo.app.feature-x/comp-x -cr --check-only -``` - -**Step 5: Integrate** -Mount or use it in `respo.app.comp.container`. - -```bash -# 1. Add import to container ns -cr edit require respo.app.comp.container respo.app.feature-x - -# 2. Add usage (using surgical edit) -# Find where to insert using `cr tree show ...` -# cr tree insert-child ... -j '["respo.app.feature-x/comp-x", "data"]' -``` - -### 6. Documentation and Language - -```bash -# Check for syntax errors and warnings -cr --check-only -cr js --check-only - -# Get language documentation -cr docs api render! -cr docs ref component -cr docs list-api # List all API docs -cr docs list-guide # List all guide docs - -# Parse Cirru code to JSON (for understanding syntax) -cr cirru parse '(div {} (<> "hello"))' - -# Format JSON to Cirru code -cr cirru format '["div", {}, ["<>", "hello"]]' - -# Parse EDN to JSON -cr cirru parse-edn '{:a 1 :b [2 3]}' - -# Show Cirru syntax guide (read before generating Cirru) -cr cirru show-guide -``` - -### 6. Library Management - -```bash -# List official libraries -cr libs - -# Search libraries by keyword -cr libs search router - -# Read library README from GitHub -cr libs readme respo - -# Install/update dependencies -caps -``` - -### 7. Code Analysis - -```bash -# Call graph analysis from init-fn (or custom root) -cr analyze call-graph -cr analyze call-graph --root app.main/main! --ns-prefix app. --include-core --max-depth 5 --format json - -# Call count statistics -cr analyze count-calls -cr analyze count-calls --root app.main/main! --ns-prefix app. --include-core --format json --sort count -``` - ---- - -## Development Workflow for LLM Agents - -### Step 1: Understand the Problem - -```bash -# Always start by exploring related code -cr query ns respo.app.updater # Understand state management -cr query find my-function-name # Find where it's defined/used -cr query usages respo.core/render! # See how render! is used -``` - -### Step 2: Implement the Solution - -Use the **precise editing pattern** for complex changes: - -```bash -# 1. Read the whole definition -cr query def namespace/function-name - -# 2. Map out the structure with tree show -cr tree show namespace/function-name -p "" -d 1 - -# 3. Navigate to target position -cr tree show namespace/function-name -p "2,1" -d 1 - -# 4. Make the change (JSON inline recommended) -cr tree replace namespace/function-name -p "2,1,0" -j '["new", "code"]' - -# Or from stdin (JSON format) -echo '["new", "code"]' | cr tree replace namespace/function-name -p "2,1,0" -s -J - -# 5. Verify -cr tree show namespace/function-name -p "2,1" -``` - -### Step 3: Test and Validate - -```bash -# Check syntax without running -cr --check-only - -# Compile to JavaScript and check for errors -cr js --check-only - -# Run the app once to test -cr -1 - -# Compile to JavaScript once -cr -1 js - -# Watch mode (will call reload! on code changes) -cr -``` - -### Step 4: Debug Issues - -```bash -# Check for error messages -cr query error - -# Read error stack traces -cat .calcit-error.cirru # (if it exists) - -# Search for the problematic code -cr query find problem-symbol -cr query usages namespace/definition - -# Review the definition in detail -cr query def namespace/definition -``` - ---- - -## Common Patterns and Best Practices - -### 1. Component Definition Pattern - -**Cirru (Read):** - -```cirru -; Standard component structure -defcomp comp-name (param1 param2 & options) - div $ {} - :class-name "|component-name" - :style $ comp-style - <> "|Content" -``` - -**JSON AST (Write - for `cr edit`):** - -```json -[ - "defcomp", - "comp-name", - ["param1", "param2", "&", "options"], - [ - "div", - ["{}", [":class-name", "|component-name"], [":style", "comp-style"]], - ["<>", "|Content"] - ] -] -``` - -### 2. State Management Pattern - -```cirru -; Define store atom at app.core level -defatom *store $ {} - :states $ {} - :data $ {} - -; Create dispatcher -defn dispatch! (op) - reset! *store (updater @*store op) - -; Updater function pattern -defn updater (store op) - tag-match op - (:action-name value) $ - assoc store :data (process-action (:data store) value) - (:nested-action id op2) $ - update-in store [:data :nested id] (process-nested op2) - _ store -``` - -### 3. Rendering Pattern - -```cirru -; Initial render -defn render-app! () - render! mount-point (comp-container @*store) dispatch! - -; Watch for store changes -add-watch *store :changes $ fn () - render-app! - -; Hot reload with cache clearing -defn reload! () - remove-watch *store :changes - add-watch *store :changes $ fn () - render-app! - clear-cache! - render-app! -``` - -### 4. DOM Element Creation - -```cirru -; Using predefined elements (defn wrappers for create-element) -div $ {} (<> "text") -button $ {} (<> "Click me") -input $ {:value "|default"} -span $ {:class-name "|style-name"} (<> "content") - -; Dynamic elements with create-element -create-element :custom-tag $ {:prop-name "|value"} - <> "|child" - -; List rendering with list-> -list-> $ {} - :style $ {} (:display "|flex") - , $ {} - :a $ comp-item item-1 - :b $ comp-item item-2 - :c $ comp-item item-3 -``` - -### 5. Styling Pattern - -```cirru -; Define styles as maps -def style-container $ {} - :display "|flex" - :padding "|10px" - :background-color "|#f0f0f0" - -; Conditional styles -defn style-for-state (state) - if (= state :active) - assoc style-container :background-color "|#3388ff" - style-container - -; Merge styles -let - base $ {} (:color "|black") - extended $ merge base $ {} (:font-size 14) - extended -``` - -**Testing Style to String Conversion:** - -```bash -# Basic example (thread-first pipeline avoids bash escaping issues) -cr eval 'thread-first ({} (:display "|flex") (:color "|red") (:padding "|10px")) .to-list respo.render.dom/style->string println' --dep respo.calcit/ -# Output: padding:10px;color:red;display:flex; -``` - -**Notes:** - -- `--dep respo.calcit/` loads the module from `~/.config/calcit/modules/` -- `thread-first` (or `->`) chains operations: create map → convert to list → style->string → print -- Direct `ns/def` format to reference functions from loaded modules - -**Inline Style Object Format:** - -```cirru -# Map format (key-value pairs) -my-styles $ {} - :display "|flex" - :color "|red" - :padding "|10px" - :font-size "|14px" -``` - -### 6. Event Handling - -**DOM Event Handlers:** - -```cirru -; Simple click handler -div - {} - :on-click $ fn (e dispatch!) - dispatch! [:button-clicked] - -; Input with value tracking -input - {} - :value "|current-value" - :on-input $ fn (e dispatch!) - let - value (e.target.value) - dispatch! [:input-changed value] - -; Keyboard events -div - {} - :on-keydown $ fn (e dispatch!) - when (= (e.key) "|Enter") - dispatch! [:submit-form] -``` - -**Component-Level Event Listeners:** - -Components can define custom listeners that respond to events sent via `send-to-component!`. This is useful for global shortcuts, external triggers, or testing. - -```cirru -; Define a listener function that returns a RespoListener record -defn on-keydown (cursor state) - %{} respo.schema/RespoListener (:name :on-keydown) - :handler $ fn (event dispatch!) - tag-match event $ - :keydown info - when - and - = |m $ :key info - :ctrl info - ; Handle Ctrl+M shortcut - dispatch! $ :: :states cursor - assoc state :message "|Shortcut triggered!" - -; Use listener in component by including it in the component body -defcomp comp-with-listener (states data) - let - cursor $ :cursor states - state $ either (:data states) ({}) - [] (on-keydown cursor state) ; Add listener to component - div $ {} - <> $ :message state -``` - -**Triggering Component Listeners:** - -Use `send-to-component!` (from `respo.controller.client`) to programmatically send events to the component tree: - -```cirru -; Send keyboard event to all listening components -send-to-component! $ :: :keydown - {} $ :key "|m" - :ctrl true - -; Trigger from timer or external source -js/window.setTimeout - fn () - send-to-component! $ :: :custom-event - {} $ :data |some-value - , 1000 - -; Useful for: -; - Global keyboard shortcuts (Ctrl+S, Escape, etc.) -; - WebSocket message handlers -; - Timer-based triggers -; - Testing component event handlers -``` - ---- - -## Debugging Common Issues - -### Issue: Component not re-rendering - -**Diagnosis**: - -```bash -# Check if render-app! is being called -cr query find render-app! -cr query usages respo.main/render-app! - -# Verify store watcher is set up -cr query def respo.app.core/dispatch! -cr query def respo.main/main! -``` - -**Solution Pattern**: - -```cirru -; Ensure watch is on *store -add-watch *store :changes $ fn () - render-app! - -; Ensure clear-cache! is called on reload -defn reload! () - remove-watch *store :changes - clear-cache! - add-watch *store :changes $ fn () - render-app! - render-app! -``` - -### Issue: State not updating - -**Diagnosis**: - -```bash -# Check updater function logic -cr query def respo.app.updater/updater - -# Verify dispatch! is calling updater correctly -cr query def respo.app.core/dispatch! - -# Check the state path in component -cr query def respo.app.comp.container/comp-container -``` - -**Solution Pattern**: - -```cirru -; Verify tag-match pattern matches dispatched action -tag-match op - (:action-name params) $ - ; Make sure return value is updated store - assoc store :data new-value - _ store ; Default case needed! - -; Ensure dispatch! is called with correct tuple -dispatch! [:action-name actual-value] -``` - -### Issue: Component effects not triggering - -**Diagnosis**: - -```bash -# Check effect definition -cr query def respo.core/defeffect # macro documentation - -# Find effect in component -cr query find my-effect -cr query usages respo.app.comp.task/my-effect -``` - -**Solution Pattern**: - -```cirru -; Effects must be first in component body -defcomp comp-with-effect (props) - [] - effect-name param1 param2 ; First! - div $ {} ; Then render - <> "|content" - -; Effect must match action lifecycle -defeffect my-effect (initial-value) - (action element at-place?) - when (= action :mount) - do (println "|mounted") - when (= action :update) - do (println "|updated") -``` - -### Issue: Hot reload breaking state - -**Diagnosis**: - -```bash -# Check reload! function -cr query def respo.main/reload! - -# Verify clear-cache! is called -cr query usages respo.core/clear-cache! -``` - -**Solution Pattern**: - -```cirru -; clear-cache! must be called during reload -defn reload! () - remove-watch *store :changes - clear-cache! ; Critical! - add-watch *store :changes $ fn () - render-app! - render-app! -``` - ---- - -## Modification Strategy: Safe Editing Guide - -### Before any edit, follow this checklist: - -1. **Understand the context** - - ```bash - cr query ns namespace-name # See imports and doc - cr query peek namespace-name/def-name # See signature - ``` - -2. **Map the exact location** - - ```bash - cr tree show namespace-name/def-name -p "" -d 2 # Overview - cr tree show namespace-name/def-name -p "2" -d 2 # Check section - cr tree show namespace-name/def-name -p "2,1" -d 2 # Precise location - ``` - -3. **Make surgical change** - -```bash -# JSON inline (recommended) -cr tree replace namespace-name/def-name -p "2,1,0" -j '"new-value"' - -# Or from stdin (JSON format) -echo '"new-value"' | cr tree replace namespace-name/def-name -p "2,1,0" -s -J -``` - -4. **Verify immediately** - ```bash - cr tree show namespace-name/def-name -p "2,1" # Confirm change - cr --check-only # Verify syntax - ``` - -### Common edit operations: - -```bash -# Replace a value (JSON inline) -cr tree replace ns/def -p "2,1,0" -j '"new-value"' - -# Insert before a position (JSON) -cr tree insert-before ns/def -p "2,1" -j '["new", "element"]' - -# Insert after a position (JSON) -cr tree insert-after ns/def -p "2,1" -j '["new", "element"]' - -# Delete a node -cr tree delete ns/def -p "2,1,0" - -# Insert as child (first child) -cr tree insert-child ns/def -p "2,1" -j '"child-value"' - -# Append as child (last child, from stdin) -echo '"child-value"' | cr tree append-child ns/def -p "2,1" -s -J -``` - ---- - -## Testing and Validation - -### Basic validation - -```bash -# Syntax check only (no execution) -cr --check-only - -# Check JavaScript compilation -cr js --check-only - -# Run application once -cr -1 - -# Compile to JS once -cr -1 js -``` - -### Test-driven development - -```bash -# Look at test files -cr query defs respo.test.main -cr query def respo.test.main/test-fn - -# Run tests -cr -1 ; (if init-fn runs tests) -``` - -### Error diagnosis - -```bash -# View error file -cr query error -cat .calcit-error.cirru - -# Search for the problematic definition -cr query find problem-name - -# Check the full definition -cr query def namespace/problem-name - -# Validate dependencies -cr query ns namespace-name # Check imports -``` - ---- - -## Important Notes for LLM Agents - -### ⚠️ Critical Rules - -1. **NEVER directly edit `calcit.cirru` or `compact.cirru`** with text editors - - - Use `cr edit` commands instead - - These are serialized AST structures, not human-readable code - -2. **ALWAYS use relative paths for documentation links** - - - Use `../` and `../../` for navigation - - This allows easy file discovery for LLM tools - -3. **ALWAYS check syntax before assuming it's correct** - - ```bash - cr --check-only - ``` - -4. **ALWAYS verify modifications work** - - ```bash - cr tree show namespace/def -p "modified-path" # Confirm change - cr --check-only # Check syntax - cr -1 # Test run - ``` - -5. **Use peek before def** to reduce token consumption - ```bash - cr query peek ns/def # Light summary - cr query def ns/def # Full AST (use only if needed) - ``` - -### 🎯 Optimization Tips for Token Usage - -```bash -# Fast exploration with limited output -cr query peek respo.core/defcomp # 5-10 lines -cr query defs respo.app.updater # Quick list - -# Slower but comprehensive -cr query def respo.app.updater/updater # Full JSON AST - -# Use -d flag to limit JSON depth -cr tree show ns/def -p "2,1" -d 1 # Shallow -cr tree show ns/def -p "2,1" -d 3 # Medium -cr tree show ns/def -p "2,1" # Full (default) - -# Search before diving deep -cr query find my-function # Find location first -cr query usages ns/def # See usage patterns -``` - -### 📖 Documentation Strategy - -When stuck, use these resources in order: - -1. This file (Respo-Agent.md) - you are here -2. [README.md](../README.md) - Project overview and index -3. [Beginner Guide](./beginner-guide.md) - Conceptual introduction -4. [API Reference](./api.md) - Specific API documentation -5. [Guide docs](./guide/) - Detailed topics -6. `cr docs api ` - Language documentation -7. Project code itself: `cr query ns ` - ---- - -## Quick Reference - -### Most Used Commands - -```bash -# Exploration (read-only, no changes) -cr query ns # List namespaces -cr query ns respo.core # Read namespace details -cr query defs respo.app.core # List definitions -cr query peek respo.core/render! # Quick peek -cr query def respo.core/render! # Full definition -cr query find render! # Search globally -cr query usages respo.core/render! # Find usages - -# Navigation (precise editing) -cr tree show ns/def -p "" -d 1 # View structure -cr tree show ns/def -p "2,1" -d 1 # Drill down -cr tree show ns/def -p "2,1,0" # Confirm target - -# Modification (careful!) -cr edit def ns/def -j '["defn", "func", [], "body"]' -cr tree replace ns/def -p "2,1,0" -j '"value"' -cr edit rm-def ns/def - -# Validation -cr --check-only # Check syntax -cr query error # View errors -cr -1 # Test run -``` - -### File Paths in Documentation - -When referring to files from within `docs/`: - -- `./` - same directory -- `../` - parent (docs/ to root) -- `../../` - grandparent (docs/apis/ to root) - -Example from `docs/apis/defcomp.md`: - -```markdown -- [Back to README](../../README.md) -- [API Overview](../api.md) -- [Another API](./render!.md) -``` diff --git a/package.json b/package.json index fe92b3d..31ebc53 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,21 @@ { "dependencies": { - "@calcit/procs": "^0.11.8", - "@google/genai": "^1.43.0", + "@calcit/procs": "^0.12.11", + "@google/genai": "^1.44.0", + "@tiye/main-fonts": "0.0.1", "axios": "^1.12.2", "cirru-color": "^0.2.4", "copy-text-to-clipboard": "^3.2.2", + "dayjs": "^1.11.18", "feather-icons": "^4.29.2", "openai": "^6.16.0" }, "devDependencies": { "bottom-tip": "^0.1.5", - "vite": "^8.0.0-beta.11" + "vite": "^8.0.0" }, "scripts": { - "build": "yarn vite build --base ./ && rm -rfv extension/dist && cp -vr dist extension/" + "build": "rm -rfv dist && cr js && yarn vite build --base ./ && rm -rfv extension/dist && cp -vr dist extension/" }, "version": "0.0.1", "packageManager": "yarn@4.12.0" diff --git a/vite.config.mjs b/vite.config.mjs index 8aef6af..ff17b8f 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -1,7 +1,29 @@ -import { defineConfig } from 'vite'; +import { defineConfig } from "vite"; export default defineConfig({ build: { - minify: false - } + minify: false, + rollupOptions: { + output: { + manualChunks(id) { + if (id.includes("@google/genai")) { + return "genai"; + } + + if (id.includes("/openai/") || id.includes("/openai@")) { + return "openai"; + } + + if ( + id.includes("/node_modules/respo") || + id.includes("/node_modules/reel") || + id.includes("/node_modules/memof") || + id.includes("/node_modules/@calcit/procs") + ) { + return "vendor-core"; + } + }, + }, + }, + }, }); diff --git a/yarn.lock b/yarn.lock index a3ba0ab..c837ac3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,14 +5,14 @@ __metadata: version: 8 cacheKey: 10c0 -"@calcit/procs@npm:^0.11.8": - version: 0.11.8 - resolution: "@calcit/procs@npm:0.11.8" +"@calcit/procs@npm:^0.12.11": + version: 0.12.11 + resolution: "@calcit/procs@npm:0.12.11" dependencies: "@calcit/ternary-tree": "npm:0.0.25" - "@cirru/parser.ts": "npm:^0.0.8" + "@cirru/parser.ts": "npm:^0.0.9" "@cirru/writer.ts": "npm:^0.1.7" - checksum: 10c0/e57d1e1da4f0dbf180045117952cf036bdeca99a9324472ddc00f1deeb8cc44e106923f22613a94b2a4506494e5c88117bd6892a56a5fc15c9de5c4f9806cf3a + checksum: 10c0/1b972d6f3bb4fd2291688e3ff528216abbc43f314ffd7ef6f177a2c8a3c07d67963fa6e1062c52a5c91f87ac3724c97da6023dfc167e707178909e012cf50e7d languageName: node linkType: hard @@ -23,10 +23,10 @@ __metadata: languageName: node linkType: hard -"@cirru/parser.ts@npm:^0.0.8": - version: 0.0.8 - resolution: "@cirru/parser.ts@npm:0.0.8" - checksum: 10c0/a722a1ae31503cd602d4bb868f9831f7cf0a3d457cde40fc363120271ebb1320c5cabdf051393b5b10ef3e29452c6e25ab8c2b0d981605299312391f528957b6 +"@cirru/parser.ts@npm:^0.0.9": + version: 0.0.9 + resolution: "@cirru/parser.ts@npm:0.0.9" + checksum: 10c0/3b13623b8f627ac81adae0cb6a4e3664f0da09115a997ca9b78b8d5d0b0f9b9b18c1ad847e068831cb1052c7da2a00329d2af04421b1024a004eef0ddca5fd7a languageName: node linkType: hard @@ -65,9 +65,9 @@ __metadata: languageName: node linkType: hard -"@google/genai@npm:^1.43.0": - version: 1.43.0 - resolution: "@google/genai@npm:1.43.0" +"@google/genai@npm:^1.44.0": + version: 1.44.0 + resolution: "@google/genai@npm:1.44.0" dependencies: google-auth-library: "npm:^10.3.0" p-retry: "npm:^4.6.2" @@ -78,7 +78,7 @@ __metadata: peerDependenciesMeta: "@modelcontextprotocol/sdk": optional: true - checksum: 10c0/b20d989340928447b138baeef8cc11b1011f16e8668688ac80831ccff601f0b2490207b0dca33ebbead97bb44a09f7e909c19df2db2f6410caed838543c4cff4 + checksum: 10c0/38afc41eeeb0b236c166072dc347e65cafcc812d0e0d9364a175f1dabc63a6cad457eb2b1e6c6f341549b56450f028d99952f898d8d623c21197973d12f25a4b languageName: node linkType: hard @@ -154,17 +154,17 @@ __metadata: languageName: node linkType: hard -"@oxc-project/runtime@npm:0.111.0": - version: 0.111.0 - resolution: "@oxc-project/runtime@npm:0.111.0" - checksum: 10c0/99f1794e5746ede1c2729f568f627350922be2e4d0c299cfd60b024eb99fbf99c7c5db127072d20647e8f532c7e3e1d30f390ba35880f9afb2dfd81441747f62 +"@oxc-project/runtime@npm:0.115.0": + version: 0.115.0 + resolution: "@oxc-project/runtime@npm:0.115.0" + checksum: 10c0/88905181724fcad06d2852969e706a25a7b6c4fadac22dd6aece24b882a947eda7487451e0824781c9dc87b40b2c6ee582790e47fec5a9ba5d27c6e8c6c35bc1 languageName: node linkType: hard -"@oxc-project/types@npm:=0.111.0": - version: 0.111.0 - resolution: "@oxc-project/types@npm:0.111.0" - checksum: 10c0/c5fea30eeff99fec22b4d6ed758cf5fa83733d5ca8753a14da7207a840f4f9a576473eb44acb5e6c375e18199e5bfc0aed04a601be05de05a8f0080b9e514d57 +"@oxc-project/types@npm:=0.115.0": + version: 0.115.0 + resolution: "@oxc-project/types@npm:0.115.0" + checksum: 10c0/47fc31eb3fb3fcf4119955339f92ba2003f9b445834c1a28ed945cd6b9cd833c7ba66fca88aa5277336c2c55df300a593bc67970e544691eceaa486ebe12cb58 languageName: node linkType: hard @@ -248,103 +248,124 @@ __metadata: languageName: node linkType: hard -"@rolldown/binding-android-arm64@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-android-arm64@npm:1.0.0-rc.2" +"@rolldown/binding-android-arm64@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-android-arm64@npm:1.0.0-rc.9" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rolldown/binding-darwin-arm64@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-rc.2" +"@rolldown/binding-darwin-arm64@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-rc.9" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rolldown/binding-darwin-x64@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-rc.2" +"@rolldown/binding-darwin-x64@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-rc.9" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rolldown/binding-freebsd-x64@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-rc.2" +"@rolldown/binding-freebsd-x64@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-rc.9" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.2" +"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.9" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.2" +"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.9" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.2" +"@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.9" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.2" +"@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.9" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.9" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.9" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.2" +"@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.9" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.2" +"@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.9" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.2" +"@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.9" dependencies: "@napi-rs/wasm-runtime": "npm:^1.1.1" conditions: cpu=wasm32 languageName: node linkType: hard -"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.2" +"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.9" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.2" +"@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.9" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rolldown/pluginutils@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "@rolldown/pluginutils@npm:1.0.0-rc.2" - checksum: 10c0/35d3dec35e00ab090d5ff8287e27af98a15da897dc8b034fe0e00d03e0931b9e993603c054be9e8925e2bde040c44c18b48cb8aeea6a261fd1c8f46837038927 +"@rolldown/pluginutils@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.9" + checksum: 10c0/fca488fb96b134ccf95b42632b6112b4abb8b3a9688f166fbd627410def2538ee201953717d234ddecbff62dfe4edc4e72c657b01a9d0750134608d767eea5fd + languageName: node + linkType: hard + +"@tiye/main-fonts@npm:0.0.1": + version: 0.0.1 + resolution: "@tiye/main-fonts@npm:0.0.1" + checksum: 10c0/6183664fb638f3b8a14254fe6abba8c79abe7c241c30a88985f511dae7dc1174202d7e10dcd3ce64fe071a4e1ee68b46b02463defced0c331fca3c22e995006f languageName: node linkType: hard @@ -603,6 +624,13 @@ __metadata: languageName: node linkType: hard +"dayjs@npm:^1.11.18": + version: 1.11.20 + resolution: "dayjs@npm:1.11.20" + checksum: 10c0/8af525e2aa100c8db9923d706c42b2b2d30579faf89456619413a5c10916efc92c2b166e193c27c02eb3174b30aa440ee1e7b72b0a2876b3da651d204db848a0 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.3.4": version: 4.4.3 resolution: "debug@npm:4.4.3" @@ -1165,99 +1193,99 @@ __metadata: languageName: node linkType: hard -"lightningcss-android-arm64@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-android-arm64@npm:1.31.1" +"lightningcss-android-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-android-arm64@npm:1.32.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"lightningcss-darwin-arm64@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-darwin-arm64@npm:1.31.1" +"lightningcss-darwin-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-arm64@npm:1.32.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"lightningcss-darwin-x64@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-darwin-x64@npm:1.31.1" +"lightningcss-darwin-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-x64@npm:1.32.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"lightningcss-freebsd-x64@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-freebsd-x64@npm:1.31.1" +"lightningcss-freebsd-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-freebsd-x64@npm:1.32.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"lightningcss-linux-arm-gnueabihf@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-linux-arm-gnueabihf@npm:1.31.1" +"lightningcss-linux-arm-gnueabihf@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.32.0" conditions: os=linux & cpu=arm languageName: node linkType: hard -"lightningcss-linux-arm64-gnu@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-linux-arm64-gnu@npm:1.31.1" +"lightningcss-linux-arm64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.32.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-arm64-musl@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-linux-arm64-musl@npm:1.31.1" +"lightningcss-linux-arm64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.32.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"lightningcss-linux-x64-gnu@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-linux-x64-gnu@npm:1.31.1" +"lightningcss-linux-x64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.32.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-x64-musl@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-linux-x64-musl@npm:1.31.1" +"lightningcss-linux-x64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-musl@npm:1.32.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"lightningcss-win32-arm64-msvc@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-win32-arm64-msvc@npm:1.31.1" +"lightningcss-win32-arm64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.32.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"lightningcss-win32-x64-msvc@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-win32-x64-msvc@npm:1.31.1" +"lightningcss-win32-x64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.32.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"lightningcss@npm:^1.31.1": - version: 1.31.1 - resolution: "lightningcss@npm:1.31.1" +"lightningcss@npm:^1.32.0": + version: 1.32.0 + resolution: "lightningcss@npm:1.32.0" dependencies: detect-libc: "npm:^2.0.3" - lightningcss-android-arm64: "npm:1.31.1" - lightningcss-darwin-arm64: "npm:1.31.1" - lightningcss-darwin-x64: "npm:1.31.1" - lightningcss-freebsd-x64: "npm:1.31.1" - lightningcss-linux-arm-gnueabihf: "npm:1.31.1" - lightningcss-linux-arm64-gnu: "npm:1.31.1" - lightningcss-linux-arm64-musl: "npm:1.31.1" - lightningcss-linux-x64-gnu: "npm:1.31.1" - lightningcss-linux-x64-musl: "npm:1.31.1" - lightningcss-win32-arm64-msvc: "npm:1.31.1" - lightningcss-win32-x64-msvc: "npm:1.31.1" + lightningcss-android-arm64: "npm:1.32.0" + lightningcss-darwin-arm64: "npm:1.32.0" + lightningcss-darwin-x64: "npm:1.32.0" + lightningcss-freebsd-x64: "npm:1.32.0" + lightningcss-linux-arm-gnueabihf: "npm:1.32.0" + lightningcss-linux-arm64-gnu: "npm:1.32.0" + lightningcss-linux-arm64-musl: "npm:1.32.0" + lightningcss-linux-x64-gnu: "npm:1.32.0" + lightningcss-linux-x64-musl: "npm:1.32.0" + lightningcss-win32-arm64-msvc: "npm:1.32.0" + lightningcss-win32-x64-msvc: "npm:1.32.0" dependenciesMeta: lightningcss-android-arm64: optional: true @@ -1281,7 +1309,7 @@ __metadata: optional: true lightningcss-win32-x64-msvc: optional: true - checksum: 10c0/c6754b305d4a73652e472fc0d7d65384a6e16c336ea61068eca60de2a45bd5c30abbf012358b82eac56ee98b5d88028932cda5268ff61967cffa400b9e7ee2ba + checksum: 10c0/70945bd55097af46fc9fab7f5ed09cd5869d85940a2acab7ee06d0117004a1d68155708a2d462531cea2fc3c67aefc9333a7068c80b0b78dd404c16838809e03 languageName: node linkType: hard @@ -1621,14 +1649,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.5.6": - version: 8.5.6 - resolution: "postcss@npm:8.5.6" +"postcss@npm:^8.5.8": + version: 8.5.8 + resolution: "postcss@npm:8.5.8" dependencies: nanoid: "npm:^3.3.11" picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 + checksum: 10c0/dd918f7127ee7c60a0295bae2e72b3787892296e1d1c3c564d7a2a00c68d8df83cadc3178491259daa19ccc54804fb71ed8c937c6787e08d8bd4bedf8d17044c languageName: node linkType: hard @@ -1708,25 +1736,27 @@ __metadata: languageName: node linkType: hard -"rolldown@npm:1.0.0-rc.2": - version: 1.0.0-rc.2 - resolution: "rolldown@npm:1.0.0-rc.2" - dependencies: - "@oxc-project/types": "npm:=0.111.0" - "@rolldown/binding-android-arm64": "npm:1.0.0-rc.2" - "@rolldown/binding-darwin-arm64": "npm:1.0.0-rc.2" - "@rolldown/binding-darwin-x64": "npm:1.0.0-rc.2" - "@rolldown/binding-freebsd-x64": "npm:1.0.0-rc.2" - "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-rc.2" - "@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-rc.2" - "@rolldown/binding-linux-arm64-musl": "npm:1.0.0-rc.2" - "@rolldown/binding-linux-x64-gnu": "npm:1.0.0-rc.2" - "@rolldown/binding-linux-x64-musl": "npm:1.0.0-rc.2" - "@rolldown/binding-openharmony-arm64": "npm:1.0.0-rc.2" - "@rolldown/binding-wasm32-wasi": "npm:1.0.0-rc.2" - "@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-rc.2" - "@rolldown/binding-win32-x64-msvc": "npm:1.0.0-rc.2" - "@rolldown/pluginutils": "npm:1.0.0-rc.2" +"rolldown@npm:1.0.0-rc.9": + version: 1.0.0-rc.9 + resolution: "rolldown@npm:1.0.0-rc.9" + dependencies: + "@oxc-project/types": "npm:=0.115.0" + "@rolldown/binding-android-arm64": "npm:1.0.0-rc.9" + "@rolldown/binding-darwin-arm64": "npm:1.0.0-rc.9" + "@rolldown/binding-darwin-x64": "npm:1.0.0-rc.9" + "@rolldown/binding-freebsd-x64": "npm:1.0.0-rc.9" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-rc.9" + "@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-rc.9" + "@rolldown/binding-linux-arm64-musl": "npm:1.0.0-rc.9" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.0.0-rc.9" + "@rolldown/binding-linux-s390x-gnu": "npm:1.0.0-rc.9" + "@rolldown/binding-linux-x64-gnu": "npm:1.0.0-rc.9" + "@rolldown/binding-linux-x64-musl": "npm:1.0.0-rc.9" + "@rolldown/binding-openharmony-arm64": "npm:1.0.0-rc.9" + "@rolldown/binding-wasm32-wasi": "npm:1.0.0-rc.9" + "@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-rc.9" + "@rolldown/binding-win32-x64-msvc": "npm:1.0.0-rc.9" + "@rolldown/pluginutils": "npm:1.0.0-rc.9" dependenciesMeta: "@rolldown/binding-android-arm64": optional: true @@ -1742,6 +1772,10 @@ __metadata: optional: true "@rolldown/binding-linux-arm64-musl": optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true "@rolldown/binding-linux-x64-gnu": optional: true "@rolldown/binding-linux-x64-musl": @@ -1756,7 +1790,7 @@ __metadata: optional: true bin: rolldown: bin/cli.mjs - checksum: 10c0/fc97a768b41d40da8e7ef20f9c33795bbeff0fa8effb4704232e19d3cf5b7467a16cf0967a15bd316e70e706a3bd7990a30be99d255da758c7342ee82dee323c + checksum: 10c0/d19af14dccf569dc25c0c3c2f1142b7a6f7cec291d55bba80cea71099f89c6d634145bb1b6487626ddd41d578f183f7065ed68067e49d2b964ad6242693b0f79 languageName: node linkType: hard @@ -1764,15 +1798,17 @@ __metadata: version: 0.0.0-use.local resolution: "root-workspace-0b6124@workspace:." dependencies: - "@calcit/procs": "npm:^0.11.8" - "@google/genai": "npm:^1.43.0" + "@calcit/procs": "npm:^0.12.11" + "@google/genai": "npm:^1.44.0" + "@tiye/main-fonts": "npm:0.0.1" axios: "npm:^1.12.2" bottom-tip: "npm:^0.1.5" cirru-color: "npm:^0.2.4" copy-text-to-clipboard: "npm:^3.2.2" + dayjs: "npm:^1.11.18" feather-icons: "npm:^4.29.2" openai: "npm:^6.16.0" - vite: "npm:^8.0.0-beta.11" + vite: "npm:^8.0.0" languageName: unknown linkType: soft @@ -1984,20 +2020,20 @@ __metadata: languageName: node linkType: hard -"vite@npm:^8.0.0-beta.11": - version: 8.0.0-beta.11 - resolution: "vite@npm:8.0.0-beta.11" +"vite@npm:^8.0.0": + version: 8.0.0 + resolution: "vite@npm:8.0.0" dependencies: - "@oxc-project/runtime": "npm:0.111.0" - fdir: "npm:^6.5.0" + "@oxc-project/runtime": "npm:0.115.0" fsevents: "npm:~2.3.3" - lightningcss: "npm:^1.31.1" + lightningcss: "npm:^1.32.0" picomatch: "npm:^4.0.3" - postcss: "npm:^8.5.6" - rolldown: "npm:1.0.0-rc.2" + postcss: "npm:^8.5.8" + rolldown: "npm:1.0.0-rc.9" tinyglobby: "npm:^0.2.15" peerDependencies: "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.0.0-alpha.31 esbuild: ^0.27.0 jiti: ">=1.21.0" less: ^4.0.0 @@ -2014,6 +2050,8 @@ __metadata: peerDependenciesMeta: "@types/node": optional: true + "@vitejs/devtools": + optional: true esbuild: optional: true jiti: @@ -2036,7 +2074,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/26290592aba7f9caf0476e1194683504787aaab5ffcc6c1d02276ae0e7d03fc2ed59dfb9279528b0780542761ed3a7ce0007688fc6b942f44b11abf55c39400b + checksum: 10c0/2246d3d54788dcd53c39da82da3f934a760756642ba9a575c84c5ef9f310bc47697f7f9fde6721fa566675e93e408736b4ac068008d2ddbd75b0ed99c7fd4c67 languageName: node linkType: hard