From 8094697c8d5300a494180b0257b136f942c06a5a Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Tue, 3 Feb 2026 14:14:27 +0800 Subject: [PATCH] Agent UI --- deps/db/src/logseq/db/frontend/property.cljs | 7 + src/main/frontend/components/block.cljs | 52 ++++- src/main/frontend/handler/agent.cljs | 211 +++++++++++++++++++ src/main/frontend/state.cljs | 1 + 4 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 src/main/frontend/handler/agent.cljs diff --git a/deps/db/src/logseq/db/frontend/property.cljs b/deps/db/src/logseq/db/frontend/property.cljs index bd0073001c..b2fb43b3ba 100644 --- a/deps/db/src/logseq/db/frontend/property.cljs +++ b/deps/db/src/logseq/db/frontend/property.cljs @@ -419,6 +419,13 @@ :schema {:type :page :public? true :classes #{:logseq.class/Agent}} + :closed-values + (mapv (fn [[db-ident value]] + {:db-ident db-ident + :value value + :uuid (common-uuid/gen-uuid :db-ident-block-uuid db-ident)}) + [[:logseq.property/agent.codex "Codex"] + [:logseq.property/agent.claude-code "Claude Code"]]) :queryable? true} :logseq.property/git-repo {:title "Git Repo" diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs index c2bc0a0d51..f309d6a20e 100644 --- a/src/main/frontend/components/block.cljs +++ b/src/main/frontend/components/block.cljs @@ -39,6 +39,7 @@ [frontend.format.block :as block] [frontend.format.mldoc :as mldoc] [frontend.fs :as fs] + [frontend.handler.agent :as agent-handler] [frontend.handler.assets :as assets-handler] [frontend.handler.block :as block-handler] [frontend.handler.db-based.property :as db-property-handler] @@ -71,6 +72,7 @@ [goog.dom :as gdom] [goog.functions :refer [debounce]] [goog.object :as gobj] + [lambdaisland.glogi :as log] [logseq.common.config :as common-config] [logseq.common.path :as path] [logseq.common.util :as common-util] @@ -2509,6 +2511,51 @@ {:align :end}))} (clock/seconds->days:hours:minutes:seconds time-spent))])))) +(defn- agent-status-class + [status] + (case status + "running" "text-emerald-600" + "paused" "text-amber-600" + "failed" "text-red-600" + "canceled" "text-red-600" + "completed" "text-emerald-700" + "created" "text-muted-foreground" + "text-muted-foreground")) + +(defn- agent-status-label + [status] + (when (string? status) + (-> status + (string/replace "-" " ") + (string/capitalize)))) + +(rum/defc task-agent-session-cp < rum/reactive + [block] + (when (ldb/class-instance? (db/entity :logseq.class/Task) block) + (let [sessions (state/sub :agent/sessions) + session (get sessions (str (:block/uuid block))) + status (:status session) + ready? (agent-handler/task-ready? block) + status-label (agent-status-label status) + status-class (agent-status-class status) + running? (contains? #{"running" "paused"} status) + btn-title (if ready? "Start agent session" "Set Project + Agent + Git Repo")] + [:div.flex.flex-row.items-center.gap-1 + (when status-label + [:span.text-xs.font-medium {:class status-class} status-label]) + (shui/button + {:variant :ghost + :size :sm + :class "text-xs h-6 !px-2" + :title btn-title + :disabled (or (not ready?) running?) + :on-click (fn [e] + (util/stop e) + (-> (agent-handler/nil [value] + (when (string? value) + (let [value (string/trim value)] + (when-not (string/blank? value) value)))) + +(defn- resolve-entity [value] + (cond + (map? value) value + (integer? value) (db/entity value) + (uuid? value) (db/entity [:block/uuid value]) + (string? value) (db/entity [:block/name (string/lower-case value)]) + :else nil)) + +(defn- agent-config + [agent-page] + (let [api-token (blank->nil (pu/get-block-property-value agent-page :logseq.property/agent-api-token)) + auth-json (blank->nil (pu/get-block-property-value agent-page :logseq.property/agent-auth-json)) + provider (blank->nil (:block/title agent-page))] + (cond-> {} + (string? provider) (assoc :provider provider) + (string? api-token) (assoc :api-token api-token) + (string? auth-json) (assoc :auth-json auth-json)))) + +(defn- project-config + [project-page] + (let [repo-url (blank->nil (pu/get-block-property-value project-page :logseq.property/git-repo)) + project-id (some-> (:block/uuid project-page) str) + title (blank->nil (:block/title project-page))] + (when (and project-id title repo-url) + {:id project-id + :title title + :repo-url repo-url}))) + +(defn- task-context + [block] + (let [block-uuid (:block/uuid block) + node-id (some-> block-uuid str) + node-title (or (blank->nil (:block/raw-title block)) + (blank->nil (:block/title block)) + "") + content (or (blank->nil (:block/raw-title block)) + (blank->nil (:block/title block)) + "") + project-page (resolve-entity (pu/get-block-property-value block :logseq.property/project)) + agent-page (resolve-entity (pu/get-block-property-value block :logseq.property/agent)) + project (when project-page (project-config project-page)) + agent (when agent-page (agent-config agent-page))] + {:block-uuid block-uuid + :node-id node-id + :node-title node-title + :content content + :attachments [] + :project project + :agent agent})) + +(defn task-ready? + [block] + (let [{:keys [project agent node-id]} (task-context block)] + (and (string? node-id) + (map? project) + (seq project) + (map? agent) + (seq agent)))) + +(defn- build-session-body + [block] + (let [{:keys [block-uuid node-id node-title content attachments project agent]} (task-context block) + session-id (some-> block-uuid str)] + (when (and session-id node-id (string? node-title) (string? content) (map? project) (map? agent)) + {:session-id session-id + :node-id node-id + :node-title node-title + :content content + :attachments attachments + :project project + :agent agent}))) + +(def ^:private session-status->task-status + {"created" :logseq.property/status.todo + "running" :logseq.property/status.doing + "paused" :logseq.property/status.todo + "completed" :logseq.property/status.done + "failed" :logseq.property/status.canceled + "canceled" :logseq.property/status.canceled}) + +(defn- terminal-status? [status] + (contains? #{"completed" "failed" "canceled"} status)) + +(defn- status->label [status-ident] + (some-> (db/entity status-ident) :block/title)) + +(defn- maybe-update-task-status! + [block-uuid status] + (when-let [status-ident (get session-status->task-status status)] + (when-let [block (db/entity [:block/uuid block-uuid])] + (let [current (pu/get-block-property-value block :logseq.property/status) + desired (status->label status-ident)] + (when (and desired (not= current desired)) + (property-handler/set-block-property! block-uuid :logseq.property/status status-ident)))))) + +(defn- update-session-state! + [block-uuid data] + (state/update-state! :agent/sessions + (fn [sessions] + (update sessions (str block-uuid) merge data)))) + +(defonce ^:private session-pollers (atom {})) + +(defn- stop-session-poller! + [block-uuid] + (when-let [{:keys [interval-id]} (get @session-pollers (str block-uuid))] + (js/clearInterval interval-id) + (swap! session-pollers dissoc (str block-uuid)))) + +(defn- (js body))} + {:response-schema :sessions/create}) + session-id (:session-id resp) + status (:status resp) + stream-url (:stream-url resp) + block-uuid (:block/uuid block)] + (update-session-state! block-uuid {:session-id session-id + :status status + :stream-url stream-url + :started-at (util/time-ms)}) + (maybe-update-task-status! block-uuid status) + (start-session-poller! base block-uuid session-id) + resp)))))) diff --git a/src/main/frontend/state.cljs b/src/main/frontend/state.cljs index 5ca4328f16..a841918f87 100644 --- a/src/main/frontend/state.cljs +++ b/src/main/frontend/state.cljs @@ -292,6 +292,7 @@ :rtc/online-info (atom {}) :rtc/asset-upload-download-progress (atom {}) :rtc/users-info (atom {}) + :agent/sessions (atom {}) :user/info {:UserGroups (storage/get :user-groups)} :encryption/graph-parsing? false