From 7a32fec008fdfb5bc034ba3ce1bca365c1d342f5 Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Mon, 3 Nov 2025 06:50:38 -0600
Subject: wip: desktop work

---
 .../desktop/src/components/message-progress.tsx    | 172 ++++++++++++++-------
 1 file changed, 116 insertions(+), 56 deletions(-)

(limited to 'packages/desktop/src/components/message-progress.tsx')

diff --git a/packages/desktop/src/components/message-progress.tsx b/packages/desktop/src/components/message-progress.tsx
index 5533ae413..4633a953a 100644
--- a/packages/desktop/src/components/message-progress.tsx
+++ b/packages/desktop/src/components/message-progress.tsx
@@ -1,7 +1,7 @@
 import { For, JSXElement, Match, Show, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
-import { Markdown, Part } from "@opencode-ai/ui"
+import { Part } from "@opencode-ai/ui"
 import { useSync } from "@/context/sync"
-import type { AssistantMessage as AssistantMessageType, Part as PartType, ToolPart } from "@opencode-ai/sdk"
+import type { AssistantMessage as AssistantMessageType, ToolPart } from "@opencode-ai/sdk"
 import { Spinner } from "./spinner"
 
 export function MessageProgress(props: { assistantMessages: () => AssistantMessageType[]; done?: boolean }) {
@@ -22,7 +22,6 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
           p.state.status === "running",
       ) as ToolPart,
   )
-
   const resolvedParts = createMemo(() => {
     let resolved = parts()
     const task = currentTask()
@@ -32,20 +31,18 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
     }
     return resolved
   })
-  const currentText = createMemo(
-    () =>
-      resolvedParts().findLast((p) => p?.type === "text")?.text ||
-      resolvedParts().findLast((p) => p?.type === "reasoning")?.text,
-  )
+  // 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")
+    return resolvedParts().filter((p) => p?.type === "tool" && p.state.status === "completed") as ToolPart[]
   })
-  const finishedItems = createMemo<(JSXElement | PartType)[]>(() => [
+  const finishedItems = createMemo<(JSXElement | ToolPart)[]>(() => [
+    <div class="h-8 w-full" />,
     <div class="h-8 w-full" />,
     <div class="h-8 w-full" />,
-    <div class="flex items-center gap-x-5 pl-3 text-text-base">
-      <Spinner /> <span class="text-12-medium">Thinking...</span>
-    </div>,
     ...eligibleItems(),
     ...(done() ? [<div class="h-8 w-full" />, <div class="h-8 w-full" />, <div class="h-8 w-full" />] : []),
   ])
@@ -71,57 +68,120 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
     return `-${(total - 2) * 40 - 8}px`
   })
 
+  const lastPart = createMemo(() => resolvedParts().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 (
     <div class="flex flex-col gap-3">
-      <div
-        class="h-30 overflow-hidden pointer-events-none pb-1 
+      {/* <Show when={currentText()}> */}
+      {/*   {(text) => ( */}
+      {/*     <div */}
+      {/*       class="h-20 flex flex-col justify-end overflow-hidden py-3 */}
+      {/*              mask-alpha mask-t-from-80% mask-t-from-background-base mask-t-to-transparent" */}
+      {/*     > */}
+      {/*       <Markdown text={text()} class="w-full shrink-0 overflow-visible" /> */}
+      {/*     </div> */}
+      {/*   )} */}
+      {/* </Show> */}
+      <div class="flex items-center gap-x-5 pl-3 border border-transparent text-text-base">
+        <Spinner /> <span class="text-12-medium">{status()}</span>
+      </div>
+      <Show when={eligibleItems().length > 0}>
+        <div
+          class="h-30 overflow-hidden pointer-events-none pb-1 
                mask-alpha mask-t-from-33% mask-t-from-background-base mask-t-to-transparent
                mask-b-from-95% mask-b-from-background-base mask-b-to-transparent"
-      >
-        <div
-          class="w-full flex flex-col items-start self-stretch gap-2 py-8
-                 transform transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]"
-          style={{ transform: `translateY(${translateY()})` }}
         >
-          <For each={finishedItems()}>
-            {(part) => {
-              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>
-      </div>
-      <Show when={currentText()}>
-        {(text) => (
           <div
-            class="max-h-36 flex flex-col justify-end overflow-hidden py-3
-                   mask-alpha mask-t-from-80% mask-t-from-background-base mask-t-to-transparent"
+            class="w-full flex flex-col items-start self-stretch gap-2 py-8
+                 transform transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]"
+            style={{ transform: `translateY(${translateY()})` }}
           >
-            <Markdown text={text()} class="w-full shrink-0 overflow-visible" />
+            <For each={finishedItems()}>
+              {(part) => (
+                <Switch>
+                  <Match when={part && typeof part === "object" && "type" in part && part}>
+                    {(p) => {
+                      const part = p() as ToolPart
+                      const message = createMemo(() =>
+                        sync.data.message[part.sessionID].find((m) => m.id === part.messageID),
+                      )
+                      return (
+                        <div class="h-8 flex items-center w-full">
+                          <Part message={message()!} part={part} />
+                        </div>
+                      )
+                    }}
+                  </Match>
+                  <Match when={true}>
+                    <div class="h-8 flex items-center w-full">{part as JSXElement}</div>
+                  </Match>
+                </Switch>
+              )}
+            </For>
           </div>
-        )}
+        </div>
       </Show>
     </div>
   )
-- 
cgit v1.2.3

