Browse Source

wip: desktop work

Adam 3 months ago
parent
commit
7a32fec008
2 changed files with 119 additions and 131 deletions
  1. 116 56
      packages/desktop/src/components/message-progress.tsx
  2. 3 75
      packages/desktop/src/pages/index.tsx

+ 116 - 56
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>
   )

+ 3 - 75
packages/desktop/src/pages/index.tsx

@@ -548,77 +548,6 @@ export default function Page() {
                                 {(message) => {
                                   const diffs = createMemo(() => message.summary?.diffs ?? [])
                                   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 (
                                     <li class="group/li flex items-center self-stretch">
@@ -641,10 +570,9 @@ export default function Page() {
                                             "text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
                                           }}
                                         >
-                                          <Switch>
-                                            <Match when={working()}>{status()}</Match>
-                                            <Match when={true}>{message.summary?.title}</Match>
-                                          </Switch>
+                                          <Show when={message.summary?.title} fallback="New message">
+                                            {message.summary?.title}
+                                          </Show>
                                         </div>
                                       </button>
                                     </li>