wip: desktop work

This commit is contained in:
Adam
2025-10-31 13:40:20 -05:00
parent c8ea2c5ce0
commit e1aed0cd01
2 changed files with 69 additions and 37 deletions

View File

@@ -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 { Part } from "@opencode-ai/ui"
import { useSync } from "@/context/sync" 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 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(() => [ const eligibleItems = createMemo(() => {
"", let allParts = parts()
"", const task = currentTask()
"Loading...", if (task && task.state && "metadata" in task.state && task.state.metadata?.sessionId) {
...items().filter( 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) =>
p?.type === "text" || p?.type === "text" ||
(p?.type === "reasoning" && p.time?.end) || (p?.type === "reasoning" && p.time?.end) ||
(p?.type === "tool" && p.state.status === "completed"), (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 const MINIMUM_DELAY = 400
@@ -54,26 +78,28 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
> >
<For each={finishedItems()}> <For each={finishedItems()}>
{(part) => { {(part) => {
if (typeof part === "string") return <div class="h-8 flex items-center w-full">{part}</div> if (part && typeof part === "object" && "type" in part) {
const message = createMemo(() => sync.data.message[part.sessionID].find((m) => m.id === part.messageID)) const message = createMemo(() => sync.data.message[part.sessionID].find((m) => m.id === part.messageID))
return ( return (
<div class="h-8 flex items-center w-full"> <div class="h-8 flex items-center w-full">
<Switch> <Switch>
<Match when={part.type === "text" && part}> <Match when={part.type === "text" && part}>
{(p) => ( {(p) => (
<div <div
textContent={p().text} textContent={p().text}
class="text-12-regular text-text-base whitespace-nowrap truncate w-full" class="text-12-regular text-text-base whitespace-nowrap truncate w-full"
/> />
)} )}
</Match> </Match>
<Match when={part.type === "reasoning" && part}> <Match when={part.type === "reasoning" && part}>
{(p) => <Part message={message()!} part={p()} />} {(p) => <Part message={message()!} part={p()} />}
</Match> </Match>
<Match when={part.type === "tool" && part}>{(p) => <Part message={message()!} part={p()} />}</Match> <Match when={part.type === "tool" && part}>{(p) => <Part message={message()!} part={p()} />}</Match>
</Switch> </Switch>
</div> </div>
) )
}
return <div class="h-8 flex items-center w-full">{part}</div>
}} }}
</For> </For>
</div> </div>

View File

@@ -583,7 +583,8 @@ export default function Page() {
<For each={local.session.userMessages()}> <For each={local.session.userMessages()}>
{(message) => { {(message) => {
const isActive = createMemo(() => local.session.activeMessage()?.id === message.id) 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 [expanded, setExpanded] = createSignal(false)
const parts = createMemo(() => sync.data.part[message.id]) const parts = createMemo(() => sync.data.part[message.id])
const title = createMemo(() => message.summary?.title) const title = createMemo(() => message.summary?.title)
@@ -597,11 +598,16 @@ export default function Page() {
const hasToolPart = createMemo(() => const hasToolPart = createMemo(() =>
assistantMessages() assistantMessages()
?.flatMap((m) => sync.data.part[m.id]) ?.flatMap((m) => sync.data.part[m.id])
.some((p) => p.type === "tool"), .some((p) => p?.type === "tool"),
) )
const working = createMemo(() => !summary()) const working = createMemo(() => !summary())
// allowing time for the animations to finish
createEffect(() => { createEffect(() => {
setTimeout(() => setInitialized(!!title()), 10_000) setTimeout(() => setTitled(!!title()), 10_000)
})
createEffect(() => {
setTimeout(() => setCompleted(!!summary()), 3_000)
}) })
return ( return (
@@ -613,7 +619,7 @@ export default function Page() {
{/* Title */} {/* Title */}
<div class="py-2 flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-10"> <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"> <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> <h1>{title()}</h1>
</Show> </Show>
</div> </div>
@@ -684,10 +690,10 @@ export default function Page() {
{/* Response */} {/* Response */}
<div class="w-full"> <div class="w-full">
<Switch> <Switch>
<Match when={working()}> <Match when={!completed()}>
<MessageProgress assistantMessages={assistantMessages} /> <MessageProgress assistantMessages={assistantMessages} done={!working()} />
</Match> </Match>
<Match when={!working() && hasToolPart()}> <Match when={completed() && hasToolPart()}>
<Collapsible variant="ghost" open={expanded()} onOpenChange={setExpanded}> <Collapsible variant="ghost" open={expanded()} onOpenChange={setExpanded}>
<Collapsible.Trigger class="text-text-weak hover:text-text-strong"> <Collapsible.Trigger class="text-text-weak hover:text-text-strong">
<div class="flex items-center gap-1 self-stretch"> <div class="flex items-center gap-1 self-stretch">