Переглянути джерело

Add spinner animation for Task tool (#11725)

Dax 3 тижнів тому
батько
коміт
0dc80df6fd

+ 2 - 8
packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx

@@ -10,7 +10,7 @@ import { useSDK } from "../context/sdk"
 import { DialogSessionRename } from "./dialog-session-rename"
 import { useKV } from "../context/kv"
 import { createDebouncedSignal } from "../util/signal"
-import "opentui-spinner/solid"
+import { Spinner } from "./spinner"
 
 export function DialogSessionList() {
   const dialog = useDialog()
@@ -32,8 +32,6 @@ export function DialogSessionList() {
 
   const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined))
 
-  const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
-
   const sessions = createMemo(() => searchResults() ?? sync.data.session)
 
   const options = createMemo(() => {
@@ -56,11 +54,7 @@ export function DialogSessionList() {
           value: x.id,
           category,
           footer: Locale.time(x.time.updated),
-          gutter: isWorking ? (
-            <Show when={kv.get("animations_enabled", true)} fallback={<text fg={theme.textMuted}>[⋯]</text>}>
-              <spinner frames={spinnerFrames} interval={80} color={theme.primary} />
-            </Show>
-          ) : undefined,
+          gutter: isWorking ? <Spinner /> : undefined,
         }
       })
   })

+ 24 - 0
packages/opencode/src/cli/cmd/tui/component/spinner.tsx

@@ -0,0 +1,24 @@
+import { Show } from "solid-js"
+import { useTheme } from "../context/theme"
+import { useKV } from "../context/kv"
+import type { JSX } from "@opentui/solid"
+import type { RGBA } from "@opentui/core"
+import "opentui-spinner/solid"
+
+const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
+
+export function Spinner(props: { children?: JSX.Element; color?: RGBA }) {
+  const { theme } = useTheme()
+  const kv = useKV()
+  const color = () => props.color ?? theme.textMuted
+  return (
+    <Show when={kv.get("animations_enabled", true)} fallback={<text fg={color()}>⋯ {props.children}</text>}>
+      <box flexDirection="row" gap={1}>
+        <spinner frames={frames} interval={80} color={color()} />
+        <Show when={props.children}>
+          <text fg={color()}>{props.children}</text>
+        </Show>
+      </box>
+    </Show>
+  )
+}

+ 21 - 4
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

@@ -16,6 +16,7 @@ import path from "path"
 import { useRoute, useRouteData } from "@tui/context/route"
 import { useSync } from "@tui/context/sync"
 import { SplitBorder } from "@tui/component/border"
+import { Spinner } from "@tui/component/spinner"
 import { useTheme } from "@tui/context/theme"
 import {
   BoxRenderable,
@@ -1559,7 +1560,13 @@ function InlineTool(props: {
   )
 }
 
-function BlockTool(props: { title: string; children: JSX.Element; onClick?: () => void; part?: ToolPart }) {
+function BlockTool(props: {
+  title: string
+  children: JSX.Element
+  onClick?: () => void
+  part?: ToolPart
+  spinner?: boolean
+}) {
   const { theme } = useTheme()
   const renderer = useRenderer()
   const [hover, setHover] = createSignal(false)
@@ -1582,9 +1589,16 @@ function BlockTool(props: { title: string; children: JSX.Element; onClick?: () =
         props.onClick?.()
       }}
     >
-      <text paddingLeft={3} fg={theme.textMuted}>
-        {props.title}
-      </text>
+      <Show
+        when={props.spinner}
+        fallback={
+          <text paddingLeft={3} fg={theme.textMuted}>
+            {props.title}
+          </text>
+        }
+      >
+        <Spinner color={theme.textMuted}>{props.title.replace(/^# /, "")}</Spinner>
+      </Show>
       {props.children}
       <Show when={error()}>
         <text fg={theme.error}>{error()}</text>
@@ -1813,6 +1827,8 @@ function Task(props: ToolProps<typeof TaskTool>) {
 
   const current = createMemo(() => tools().findLast((x) => x.state.status !== "pending"))
 
+  const isRunning = createMemo(() => props.part.state.status === "running")
+
   return (
     <Switch>
       <Match when={props.input.description || props.input.subagent_type}>
@@ -1824,6 +1840,7 @@ function Task(props: ToolProps<typeof TaskTool>) {
               : undefined
           }
           part={props.part}
+          spinner={isRunning()}
         >
           <box>
             <text style={{ fg: theme.textMuted }}>