Add spinner animation for Task tool (#11725)

This commit is contained in:
Dax
2026-02-01 22:51:55 -05:00
committed by GitHub
parent 8e985e0a75
commit 0dc80df6fd
3 changed files with 47 additions and 12 deletions

View File

@@ -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,
}
})
})

View File

@@ -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>
)
}

View File

@@ -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 }}>