diff options
| author | Adam <[email protected]> | 2025-10-31 13:40:20 -0500 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-10-31 15:37:50 -0500 |
| commit | e1aed0cd01d23e433519621f8d21c0a8ffa3977d (patch) | |
| tree | 6e0131f75df5c87202d68241dfbb5be8fd4023f5 | |
| parent | c8ea2c5ce077c0c5b61fa5ec43ff2038a47415a1 (diff) | |
| download | opencode-e1aed0cd01d23e433519621f8d21c0a8ffa3977d.tar.gz opencode-e1aed0cd01d23e433519621f8d21c0a8ffa3977d.zip | |
wip: desktop work
| -rw-r--r-- | packages/desktop/src/components/message-progress.tsx | 86 | ||||
| -rw-r--r-- | packages/desktop/src/pages/index.tsx | 20 |
2 files changed, 69 insertions, 37 deletions
diff --git a/packages/desktop/src/components/message-progress.tsx b/packages/desktop/src/components/message-progress.tsx index f77e196b5..fd66c5caf 100644 --- a/packages/desktop/src/components/message-progress.tsx +++ b/packages/desktop/src/components/message-progress.tsx @@ -1,23 +1,47 @@ -import { For, Match, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js" +import { For, JSXElement, Match, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js" import { Part } from "@opencode-ai/ui" import { useSync } from "@/context/sync" -import type { AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk" +import type { AssistantMessage as AssistantMessageType, Part as PartType, ToolPart } from "@opencode-ai/sdk" -export function MessageProgress(props: { assistantMessages: () => AssistantMessageType[] }) { +export function MessageProgress(props: { assistantMessages: () => AssistantMessageType[]; done?: boolean }) { const sync = useSync() - const items = createMemo(() => props.assistantMessages().flatMap((m) => sync.data.part[m.id])) + const parts = createMemo(() => props.assistantMessages().flatMap((m) => sync.data.part[m.id])) + const done = createMemo(() => props.done ?? false) + const currentTask = createMemo( + () => + parts().findLast( + (p) => + p && + p.type === "tool" && + p.tool === "task" && + p.state && + "metadata" in p.state && + p.state.metadata && + p.state.metadata.sessionId && + p.state.status === "running", + ) as ToolPart, + ) - const finishedItems = createMemo(() => [ - "", - "", - "Loading...", - ...items().filter( + const eligibleItems = createMemo(() => { + let allParts = parts() + const task = currentTask() + if (task && task.state && "metadata" in task.state && task.state.metadata?.sessionId) { + const messages = sync.data.message[task.state.metadata.sessionId as string]?.filter((m) => m.role === "assistant") + allParts = messages?.flatMap((m) => sync.data.part[m.id]) ?? parts() + } + return allParts.filter( (p) => p?.type === "text" || (p?.type === "reasoning" && p.time?.end) || (p?.type === "tool" && p.state.status === "completed"), - ), + ) + }) + const finishedItems = createMemo<(JSXElement | PartType)[]>(() => [ + "", "", + <div class="text-text-diff-add-base">Loading...</div>, + ...eligibleItems(), + ...(done() ? ["", "", ""] : []), ]) const MINIMUM_DELAY = 400 @@ -54,26 +78,28 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa > <For each={finishedItems()}> {(part) => { - if (typeof part === "string") return <div class="h-8 flex items-center w-full">{part}</div> - const message = createMemo(() => sync.data.message[part.sessionID].find((m) => m.id === part.messageID)) - return ( - <div class="h-8 flex items-center w-full"> - <Switch> - <Match when={part.type === "text" && part}> - {(p) => ( - <div - textContent={p().text} - class="text-12-regular text-text-base whitespace-nowrap truncate w-full" - /> - )} - </Match> - <Match when={part.type === "reasoning" && part}> - {(p) => <Part message={message()!} part={p()} />} - </Match> - <Match when={part.type === "tool" && part}>{(p) => <Part message={message()!} part={p()} />}</Match> - </Switch> - </div> - ) + if (part && typeof part === "object" && "type" in part) { + const message = createMemo(() => sync.data.message[part.sessionID].find((m) => m.id === part.messageID)) + return ( + <div class="h-8 flex items-center w-full"> + <Switch> + <Match when={part.type === "text" && part}> + {(p) => ( + <div + textContent={p().text} + class="text-12-regular text-text-base whitespace-nowrap truncate w-full" + /> + )} + </Match> + <Match when={part.type === "reasoning" && part}> + {(p) => <Part message={message()!} part={p()} />} + </Match> + <Match when={part.type === "tool" && part}>{(p) => <Part message={message()!} part={p()} />}</Match> + </Switch> + </div> + ) + } + return <div class="h-8 flex items-center w-full">{part}</div> }} </For> </div> diff --git a/packages/desktop/src/pages/index.tsx b/packages/desktop/src/pages/index.tsx index 2b723c55b..929aeda7a 100644 --- a/packages/desktop/src/pages/index.tsx +++ b/packages/desktop/src/pages/index.tsx @@ -583,7 +583,8 @@ export default function Page() { <For each={local.session.userMessages()}> {(message) => { const isActive = createMemo(() => local.session.activeMessage()?.id === message.id) - const [initialized, setInitialized] = createSignal(!!message.summary?.title) + const [titled, setTitled] = createSignal(!!message.summary?.title) + const [completed, setCompleted] = createSignal(!!message.summary?.body) const [expanded, setExpanded] = createSignal(false) const parts = createMemo(() => sync.data.part[message.id]) const title = createMemo(() => message.summary?.title) @@ -597,11 +598,16 @@ export default function Page() { const hasToolPart = createMemo(() => assistantMessages() ?.flatMap((m) => sync.data.part[m.id]) - .some((p) => p.type === "tool"), + .some((p) => p?.type === "tool"), ) const working = createMemo(() => !summary()) + + // allowing time for the animations to finish + createEffect(() => { + setTimeout(() => setTitled(!!title()), 10_000) + }) createEffect(() => { - setTimeout(() => setInitialized(!!title()), 10_000) + setTimeout(() => setCompleted(!!summary()), 3_000) }) return ( @@ -613,7 +619,7 @@ export default function Page() { {/* Title */} <div class="py-2 flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-10"> <div class="text-14-medium text-text-strong overflow-hidden text-ellipsis min-w-0"> - <Show when={initialized()} fallback={<Typewriter as="h1" text={title()} />}> + <Show when={titled()} fallback={<Typewriter as="h1" text={title()} />}> <h1>{title()}</h1> </Show> </div> @@ -684,10 +690,10 @@ export default function Page() { {/* Response */} <div class="w-full"> <Switch> - <Match when={working()}> - <MessageProgress assistantMessages={assistantMessages} /> + <Match when={!completed()}> + <MessageProgress assistantMessages={assistantMessages} done={!working()} /> </Match> - <Match when={!working() && hasToolPart()}> + <Match when={completed() && hasToolPart()}> <Collapsible variant="ghost" open={expanded()} onOpenChange={setExpanded}> <Collapsible.Trigger class="text-text-weak hover:text-text-strong"> <div class="flex items-center gap-1 self-stretch"> |
