From 333b8e907bb758adca3da69f4af72b9abe08777a Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:35:17 -0600 Subject: [PATCH] fix(desktop): busy state and reactivity --- .../desktop/src/components/prompt-input.tsx | 2 +- packages/desktop/src/context/global-sync.tsx | 9 ++++++++ packages/desktop/src/context/session.tsx | 22 +++++++++---------- packages/desktop/src/context/sync.tsx | 1 + .../desktop/src/pages/directory-layout.tsx | 12 +++++----- packages/desktop/src/pages/session.tsx | 16 +++++--------- 6 files changed, 33 insertions(+), 29 deletions(-) diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx index c2948a659..b2e552f71 100644 --- a/packages/desktop/src/components/prompt-input.tsx +++ b/packages/desktop/src/components/prompt-input.tsx @@ -266,7 +266,7 @@ export const PromptInput: Component = (props) => { if (!existing) { const created = await sdk.client.session.create() existing = created.data ?? undefined - if (existing) navigate(`${local.slug()}/session/${existing.id}`) + if (existing) navigate(existing.id) } if (!existing) return diff --git a/packages/desktop/src/context/global-sync.tsx b/packages/desktop/src/context/global-sync.tsx index 99c679c93..785a39928 100644 --- a/packages/desktop/src/context/global-sync.tsx +++ b/packages/desktop/src/context/global-sync.tsx @@ -11,6 +11,7 @@ import type { Project, FileDiff, Todo, + SessionStatus, } from "@opencode-ai/sdk" import { createStore, produce, reconcile } from "solid-js/store" import { Binary } from "@/utils/binary" @@ -25,6 +26,9 @@ type State = { config: Config path: Path session: Session[] + session_status: { + [sessionID: string]: SessionStatus + } session_diff: { [sessionID: string]: FileDiff[] } @@ -68,6 +72,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple agent: [], provider: [], session: [], + session_status: {}, session_diff: {}, todo: {}, limit: 10, @@ -108,6 +113,10 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple case "todo.updated": setStore("todo", event.properties.sessionID, event.properties.todos) break + case "session.status": { + setStore("session_status", event.properties.sessionID, event.properties.status) + break + } case "message.updated": { const messages = store.message[event.properties.info.sessionID] if (!messages) { diff --git a/packages/desktop/src/context/session.tsx b/packages/desktop/src/context/session.tsx index 36319b0c5..64820b6ae 100644 --- a/packages/desktop/src/context/session.tsx +++ b/packages/desktop/src/context/session.tsx @@ -58,16 +58,13 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex if (!store.messageId) return lastUserMessage() return userMessages()?.find((m) => m.id === store.messageId) }) - const working = createMemo(() => { - if (!params.id) return false - const last = lastUserMessage() - if (!last) return false - const assistantMessages = sync.data.message[params.id]?.filter( - (m) => m.role === "assistant" && m.parentID == last?.id, - ) as AssistantMessage[] - const error = assistantMessages?.find((m) => m?.error)?.error - return !last?.summary?.body && !error - }) + const status = createMemo( + () => + sync.data.session_status[params.id] ?? { + type: "idle", + }, + ) + const working = createMemo(() => status()?.type !== "idle") const cost = createMemo(() => { const total = pipe( @@ -102,8 +99,11 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex }) return { - id: params.id, + get id() { + return params.id + }, info, + status, working, diffs, prompt: { diff --git a/packages/desktop/src/context/sync.tsx b/packages/desktop/src/context/sync.tsx index 14c0309c4..3852a6feb 100644 --- a/packages/desktop/src/context/sync.tsx +++ b/packages/desktop/src/context/sync.tsx @@ -26,6 +26,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ .slice(0, store.limit) setStore("session", sessions) }), + status: () => sdk.client.session.status().then((x) => setStore("session_status", x.data!)), config: () => sdk.client.config.get().then((x) => setStore("config", x.data!)), changes: () => sdk.client.file.status().then((x) => setStore("changes", x.data!)), node: () => sdk.client.file.list({ query: { path: "/" } }).then((x) => setStore("node", x.data!)), diff --git a/packages/desktop/src/pages/directory-layout.tsx b/packages/desktop/src/pages/directory-layout.tsx index fe35f20f9..fff75d1af 100644 --- a/packages/desktop/src/pages/directory-layout.tsx +++ b/packages/desktop/src/pages/directory-layout.tsx @@ -14,12 +14,10 @@ export default function Layout(props: ParentProps) { return sync.data.projects.find((x) => x.worktree === decoded)?.worktree ?? "/" }) return ( - - - - {props.children} - - - + + + {props.children} + + ) } diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx index 01d32a672..c273901cc 100644 --- a/packages/desktop/src/pages/session.tsx +++ b/packages/desktop/src/pages/session.tsx @@ -378,15 +378,9 @@ export default function Page() { > {(message) => { - const assistantMessages = createMemo(() => { - if (!session.id) return [] - return sync.data.message[session.id]?.filter( - (m) => m.role === "assistant" && m.parentID == message.id, - ) as AssistantMessageType[] - }) - const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error) - const working = createMemo(() => !message.summary?.body && !error()) - + const working = createMemo( + () => message.id === session.messages.last()?.id && session.working(), + ) const handleClick = () => session.messages.setActive(message.id) return ( @@ -473,7 +467,9 @@ export default function Page() { ?.flatMap((m) => sync.data.part[m.id]) .some((p) => p?.type === "tool"), ) - const working = createMemo(() => !message.summary?.body && !error()) + const working = createMemo( + () => message.id === session.messages.last()?.id && session.working(), + ) // allowing time for the animations to finish createEffect(() => {