Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions packages/tui/src/component/spinner.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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 (
<Show when={kv.get("animations_enabled", true)} fallback={<text fg={color()}>⋯ {props.children}</text>}>
<Show when={kv.get("animations_enabled", true)} fallback={<text fg={color()}>⋯ {text()}</text>}>
<box flexDirection="row" gap={1}>
<spinner frames={SPINNER_FRAMES} interval={80} color={color()} />
<Show when={props.children}>
<text fg={color()}>{props.children}</text>
<Show when={text()}>
<text fg={color()}>{text()}</text>
</Show>
</box>
</Show>
Expand Down
18 changes: 17 additions & 1 deletion packages/tui/src/config/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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<typeof Info>

export type Resolved = Omit<Info, "attention" | "keybinds" | "leader_timeout" | "mouse"> & {
export type Resolved = Omit<Info, "attention" | "keybinds" | "leader_timeout" | "mouse" | "spinner_verbs"> & {
attention: {
enabled: boolean
notifications: boolean
Expand All @@ -78,6 +85,7 @@ export type Resolved = Omit<Info, "attention" | "keybinds" | "leader_timeout" |
keybinds: TuiKeybind.BindingLookupView
leader_timeout: number
mouse: boolean
spinner_verbs: string[]
}

export const ResolveOptions = Schema.Struct({
Expand All @@ -97,6 +105,13 @@ export function resolve(input: Info, options: ResolveOptions): Resolved {
}
}

const defaultSpinnerVerbs = ["Thinking", "Working", "Processing", "Analyzing", "Computing"]
const spinnerVerbs = input.spinner_verbs
? input.spinner_verbs.mode === "replace"
? [...input.spinner_verbs.verbs]
: [...defaultSpinnerVerbs, ...input.spinner_verbs.verbs]
: []

return {
...input,
attention: {
Expand All @@ -113,6 +128,7 @@ export function resolve(input: Info, options: ResolveOptions): Resolved {
}),
leader_timeout: input.leader_timeout ?? LeaderTimeoutDefault,
mouse: input.mouse ?? true,
spinner_verbs: spinnerVerbs,
}
}

Expand Down
5 changes: 4 additions & 1 deletion packages/tui/src/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1647,6 +1647,7 @@ function ReasoningHeader(props: {
duration?: string
}) {
const { theme } = useTheme()
const tuiConfig = useTuiConfig()
const fg = () =>
props.open
? RGBA.fromValues(theme.warning.r, theme.warning.g, theme.warning.b, theme.thinkingOpacity)
Expand All @@ -1656,7 +1657,9 @@ function ReasoningHeader(props: {
<Switch>
<Match when={!props.done}>
<box flexDirection="row">
<Spinner color={fg()}>{props.title ? "Thinking: " + props.title : "Thinking"}</Spinner>
<Spinner color={fg()} verbs={tuiConfig.spinner_verbs}>
{props.title ? "Thinking: " + props.title : "Thinking"}
</Spinner>
</box>
</Match>
<Match when={true}>
Expand Down
Loading