From 342aa27e03dd0db02b60a15a1779254bce395e19 Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Fri, 31 Oct 2025 15:37:45 -0500
Subject: wip: desktop work
---
.../desktop/src/components/message-progress.tsx | 124 ++++++++++++---------
packages/desktop/src/components/prompt-input.tsx | 2 +-
packages/desktop/src/pages/index.tsx | 108 ++++++++++++++++--
3 files changed, 171 insertions(+), 63 deletions(-)
(limited to 'packages/desktop/src')
diff --git a/packages/desktop/src/components/message-progress.tsx b/packages/desktop/src/components/message-progress.tsx
index fd66c5caf..5533ae413 100644
--- a/packages/desktop/src/components/message-progress.tsx
+++ b/packages/desktop/src/components/message-progress.tsx
@@ -1,7 +1,8 @@
-import { For, JSXElement, Match, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
-import { Part } from "@opencode-ai/ui"
+import { For, JSXElement, Match, Show, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
+import { Markdown, Part } from "@opencode-ai/ui"
import { useSync } from "@/context/sync"
import type { AssistantMessage as AssistantMessageType, Part as PartType, ToolPart } from "@opencode-ai/sdk"
+import { Spinner } from "./spinner"
export function MessageProgress(props: { assistantMessages: () => AssistantMessageType[]; done?: boolean }) {
const sync = useSync()
@@ -22,37 +23,42 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
) as ToolPart,
)
- const eligibleItems = createMemo(() => {
- let allParts = parts()
+ const resolvedParts = createMemo(() => {
+ let resolved = 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()
+ resolved = 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"),
- )
+ return resolved
+ })
+ const currentText = createMemo(
+ () =>
+ resolvedParts().findLast((p) => p?.type === "text")?.text ||
+ resolvedParts().findLast((p) => p?.type === "reasoning")?.text,
+ )
+ const eligibleItems = createMemo(() => {
+ return resolvedParts().filter((p) => p?.type === "tool" && p.state.status === "completed")
})
const finishedItems = createMemo<(JSXElement | PartType)[]>(() => [
- "",
- "",
-
Loading...
,
+ ,
+ ,
+
+ Thinking...
+
,
...eligibleItems(),
- ...(done() ? ["", "", ""] : []),
+ ...(done() ? [, , ] : []),
])
- const MINIMUM_DELAY = 400
- const [visibleCount, setVisibleCount] = createSignal(1)
+ const delay = createMemo(() => (done() ? 220 : 400))
+ const [visibleCount, setVisibleCount] = createSignal(eligibleItems().length)
createEffect(() => {
const total = finishedItems().length
if (total > visibleCount()) {
const timer = setTimeout(() => {
setVisibleCount((prev) => prev + 1)
- }, MINIMUM_DELAY)
+ }, delay())
onCleanup(() => clearTimeout(timer))
} else if (total < visibleCount()) {
setVisibleCount(total)
@@ -66,43 +72,57 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
})
return (
-
+
+
+ {(text) => (
+
+
+
+ )}
+
)
}
diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx
index 10487612b..9b2c10df7 100644
--- a/packages/desktop/src/components/prompt-input.tsx
+++ b/packages/desktop/src/components/prompt-input.tsx
@@ -334,7 +334,7 @@ export const PromptInput: Component
= (props) => {
onSubmit={handleSubmit}
classList={{
"bg-surface-raised-stronger-non-alpha border border-border-strong-base": true,
- "rounded-2xl overflow-clip focus-within:shadow-xs-border-selected": true,
+ "rounded-2xl overflow-clip focus-within:border-transparent focus-within:shadow-xs-border-select": true,
[props.class ?? ""]: !!props.class,
}}
>
diff --git a/packages/desktop/src/pages/index.tsx b/packages/desktop/src/pages/index.tsx
index 929aeda7a..35e415aaf 100644
--- a/packages/desktop/src/pages/index.tsx
+++ b/packages/desktop/src/pages/index.tsx
@@ -548,7 +548,79 @@ export default function Page() {
{(message) => {
const diffs = createMemo(() => message.summary?.diffs ?? [])
- const working = createMemo(() => !message.summary?.title)
+ const working = createMemo(() => !message.summary?.body)
+ const assistantMessages = createMemo(() => {
+ return sync.data.message[activeSession().id]?.filter(
+ (m) => m.role === "assistant" && m.parentID == message.id,
+ ) as AssistantMessageType[]
+ })
+ const parts = createMemo(() =>
+ assistantMessages().flatMap((m) => sync.data.part[m.id]),
+ )
+ const lastPart = createMemo(() => parts().slice(-1)?.at(0))
+ const rawStatus = createMemo(() => {
+ const defaultStatus = "Working..."
+ const last = lastPart()
+ if (!last) return defaultStatus
+
+ if (last.type === "tool") {
+ switch (last.tool) {
+ case "task":
+ return "Delegating work..."
+ case "todowrite":
+ case "todoread":
+ return "Planning next steps..."
+ case "read":
+ return "Gathering context..."
+ case "list":
+ case "grep":
+ case "glob":
+ return "Searching the codebase..."
+ case "webfetch":
+ return "Searching the web..."
+ case "edit":
+ case "write":
+ return "Making edits..."
+ case "bash":
+ return "Running commands..."
+ default:
+ break
+ }
+ } else if (last.type === "reasoning") {
+ return "Thinking..."
+ } else if (last.type === "text") {
+ return "Gathering thoughts..."
+ }
+ return defaultStatus
+ })
+
+ const [status, setStatus] = createSignal(rawStatus())
+ let lastStatusChange = Date.now()
+ let statusTimeout: number | undefined
+
+ createEffect(() => {
+ const newStatus = rawStatus()
+ if (newStatus === status()) return
+
+ const timeSinceLastChange = Date.now() - lastStatusChange
+
+ if (timeSinceLastChange >= 1000) {
+ setStatus(newStatus)
+ lastStatusChange = Date.now()
+ if (statusTimeout) {
+ clearTimeout(statusTimeout)
+ statusTimeout = undefined
+ }
+ } else {
+ if (statusTimeout) clearTimeout(statusTimeout)
+ statusTimeout = setTimeout(() => {
+ setStatus(rawStatus())
+ lastStatusChange = Date.now()
+ statusTimeout = undefined
+ }, 1000 - timeSinceLastChange) as unknown as number
+ }
+ })
+
return (
@@ -604,10 +679,12 @@ export default function Page() {
// allowing time for the animations to finish
createEffect(() => {
+ title()
setTimeout(() => setTitled(!!title()), 10_000)
})
createEffect(() => {
- setTimeout(() => setCompleted(!!summary()), 3_000)
+ summary()
+ setTimeout(() => setCompleted(!!summary()), 1200)
})
return (
@@ -618,9 +695,18 @@ export default function Page() {
>
{/* Title */}
-
-
}>
-
{title()}
+
+
+ }
+ >
+
{title()}
@@ -628,7 +714,7 @@ export default function Page() {
{/* Summary */}
-
+
@@ -637,7 +723,9 @@ export default function Page() {
Response
- {(summary) => }
+
+ {(summary) => }
+
@@ -699,8 +787,8 @@ export default function Page() {
- Hide steps
- Show steps
+ Hide details
+ Show details
--
cgit v1.2.3