From cbc57ef002e1b68f3c14f54b58e6d5dd9aea4d5a Mon Sep 17 00:00:00 2001 From: Tanishq Date: Sat, 20 Jun 2026 14:34:23 +0530 Subject: [PATCH] feat(tui): add spinnerVerbs config to customize TUI spinner text --- packages/tui/src/component/spinner.tsx | 24 ++++++++++++++++++----- packages/tui/src/config/index.tsx | 18 ++++++++++++++++- packages/tui/src/routes/session/index.tsx | 5 ++++- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/packages/tui/src/component/spinner.tsx b/packages/tui/src/component/spinner.tsx index 700780314131..bbfbf8373be8 100644 --- a/packages/tui/src/component/spinner.tsx +++ b/packages/tui/src/component/spinner.tsx @@ -1,4 +1,4 @@ -import { Show } from "solid-js" +import { createEffect, createSignal, onCleanup, Show } from "solid-js" import { useTheme } from "../context/theme" import { useKV } from "../context/kv" import type { JSX } from "@opentui/solid" @@ -7,16 +7,30 @@ import "opentui-spinner/solid" export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] -export function Spinner(props: { children?: JSX.Element; color?: RGBA }) { +export function Spinner(props: { children?: JSX.Element; color?: RGBA; verbs?: string[] }) { const { theme } = useTheme() const kv = useKV() const color = () => props.color ?? theme.textMuted + + const [index, setIndex] = createSignal(0) + + createEffect(() => { + if (!props.verbs?.length) return + const id = setInterval(() => setIndex((i) => (i + 1) % props.verbs!.length), 3000) + onCleanup(() => clearInterval(id)) + }) + + const text = () => { + if (props.verbs?.length) return props.verbs[index() % props.verbs.length] + "..." + return props.children + } + return ( - ⋯ {props.children}}> + ⋯ {text()}}> - - {props.children} + + {text()} diff --git a/packages/tui/src/config/index.tsx b/packages/tui/src/config/index.tsx index df9239763a68..ba9635130a23 100644 --- a/packages/tui/src/config/index.tsx +++ b/packages/tui/src/config/index.tsx @@ -50,6 +50,12 @@ export const Prompt = Schema.Struct({ }), }).annotate({ description: "Prompt size settings" }) +const SpinnerVerbsMode = Schema.Literals(["replace", "append"]) +const SpinnerVerbs = Schema.Struct({ + mode: SpinnerVerbsMode, + verbs: Schema.mutable(Schema.Array(Schema.String)), +}) + export const Info = Schema.Struct({ $schema: Schema.optional(Schema.String), theme: Schema.optional(Schema.String), @@ -63,10 +69,11 @@ export const Info = Schema.Struct({ scroll_acceleration: Schema.optional(ScrollAcceleration), diff_style: Schema.optional(DiffStyle), mouse: Schema.optional(Schema.Boolean).annotate({ description: "Enable or disable mouse capture (default: true)" }), + spinner_verbs: Schema.optional(SpinnerVerbs).annotate({ description: "Customize spinner verbs shown while the model is thinking" }), }) export type Info = Schema.Schema.Type -export type Resolved = Omit & { +export type Resolved = Omit & { attention: { enabled: boolean notifications: boolean @@ -78,6 +85,7 @@ export type Resolved = Omit props.open ? RGBA.fromValues(theme.warning.r, theme.warning.g, theme.warning.b, theme.thinkingOpacity) @@ -1663,7 +1664,9 @@ function ReasoningHeader(props: { - {props.title ? "Thinking: " + props.title : "Thinking"} + + {props.title ? "Thinking: " + props.title : "Thinking"} +