tui: show model context in run footer (#30380)

This commit is contained in:
Simon Klee
2026-06-02 13:09:03 +02:00
committed by GitHub
parent d5a0ddb520
commit 8936914e9d
4 changed files with 45 additions and 26 deletions
@@ -739,7 +739,11 @@ export class RunFooter implements FooterApi {
return return
} }
const previous = this.currentModel()
this.setCurrentModel(model) this.setCurrentModel(model)
if (!previous || previous.providerID !== model.providerID || previous.modelID !== model.modelID) {
this.setCurrentVariant(undefined)
}
void Promise.resolve() void Promise.resolve()
.then(() => this.options.onModelSelect?.(model)) .then(() => this.options.onModelSelect?.(model))
.then((result) => { .then((result) => {
@@ -53,6 +53,7 @@ import type {
RunTuiConfig, RunTuiConfig,
} from "./types" } from "./types"
import { RUN_THEME_FALLBACK, type RunTheme } from "./theme" import { RUN_THEME_FALLBACK, type RunTheme } from "./theme"
import { modelInfo } from "./variant.shared"
const EMPTY_BORDER = { const EMPTY_BORDER = {
topLeft: "", topLeft: "",
@@ -160,7 +161,11 @@ export function RunFooterView(props: RunFooterViewProps) {
const queuedIndicator = createMemo(() => { const queuedIndicator = createMemo(() => {
const count = queuedPrompts().length const count = queuedPrompts().length
if (count === 0) return if (count === 0) return
return { count, label: count === 1 ? "prompt" : "prompts" } return { count }
})
const model = createMemo(() => {
const current = props.currentModel()
return current ? modelInfo(props.providers(), current) : { model: props.state().model, provider: undefined }
}) })
const detail = createMemo(() => { const detail = createMemo(() => {
const current = route() const current = route()
@@ -617,16 +622,33 @@ export function RunFooterView(props: RunFooterViewProps) {
{shell() ? "Shell" : props.agent} {shell() ? "Shell" : props.agent}
</text> </text>
<Show when={!shell()}> <Show when={!shell()}>
<text <box id="run-direct-footer-model" flexDirection="row" gap={1} flexGrow={1} flexShrink={1}>
id="run-direct-footer-model" <text fg={theme().muted} wrapMode="none" flexShrink={0}>
fg={theme().text} ·
wrapMode="none" </text>
truncate <text fg={theme().text} wrapMode="none" truncate flexShrink={1}>
flexGrow={1} {model().model}
flexShrink={1} </text>
> <Show when={model().provider}>
{props.state().model} {(provider) => (
</text> <text fg={theme().muted} wrapMode="none" truncate flexShrink={1}>
{provider()}
</text>
)}
</Show>
<Show when={props.currentVariant()}>
{(variant) => (
<>
<text fg={theme().muted} wrapMode="none" flexShrink={0}>
·
</text>
<text wrapMode="none" truncate flexShrink={1}>
<span style={{ fg: theme().warning, bold: true }}>{variant()}</span>
</text>
</>
)}
</Show>
</box>
</Show> </Show>
</box> </box>
</Show> </Show>
@@ -746,26 +768,18 @@ export function RunFooterView(props: RunFooterViewProps) {
<Show when={subagentIndicator()}> <Show when={subagentIndicator()}>
{(info) => ( {(info) => (
<text id="run-direct-footer-subagents-label" fg={theme().text} wrapMode="none" truncate> <text id="run-direct-footer-subagents-label" fg={theme().text} wrapMode="none" truncate>
<Show when={busy() || exiting() || duration().length > 0}> <span style={{ fg: theme().highlight }}> </span>
<span style={{ fg: theme().muted }}>· </span> {info().count} <span style={{ fg: theme().muted }}>{info().label} </span>
</Show>
{info().count} <span style={{ fg: theme().muted }}>{info().label}</span>
<span style={{ fg: theme().muted }}> · </span>
<span style={{ fg: theme().highlight }}>{subagentShortcut() || "leader+down"}</span> <span style={{ fg: theme().highlight }}>{subagentShortcut() || "leader+down"}</span>
<span style={{ fg: theme().muted }}> to view</span>
</text> </text>
)} )}
</Show> </Show>
<Show when={queuedIndicator()}> <Show when={queuedIndicator()}>
{(info) => ( {(info) => (
<text id="run-direct-footer-queued-label" fg={theme().text} wrapMode="none" truncate> <text id="run-direct-footer-queued-label" fg={theme().text} wrapMode="none" truncate>
<Show when={busy() || exiting() || duration().length > 0 || subagentIndicator()}> <span style={{ fg: theme().warning }}> </span>
<span style={{ fg: theme().muted }}>· </span> {info().count} <span style={{ fg: theme().muted }}>queued </span>
</Show>
{info().count} <span style={{ fg: theme().muted }}>queued {info().label}</span>
<span style={{ fg: theme().muted }}> · </span>
<span style={{ fg: theme().highlight }}>{queuedShortcut() || "leader+q"}</span> <span style={{ fg: theme().highlight }}>{queuedShortcut() || "leader+q"}</span>
<span style={{ fg: theme().muted }}> to edit/remove</span>
</text> </text>
)} )}
</Show> </Show>
@@ -39,7 +39,7 @@ function variantKey(model: NonNullable<RunInput["model"]>): string {
return modelKey(model.providerID, model.modelID) return modelKey(model.providerID, model.modelID)
} }
function modelInfo(providers: RunProvider[] | undefined, model: NonNullable<RunInput["model"]>) { export function modelInfo(providers: RunProvider[] | undefined, model: NonNullable<RunInput["model"]>) {
const provider = providers?.find((item) => item.id === model.providerID) const provider = providers?.find((item) => item.id === model.providerID)
return { return {
provider: provider?.name ?? model.providerID, provider: provider?.name ?? model.providerID,
@@ -647,9 +647,10 @@ test("direct footer shows editable prompts and additional queued work while runn
try { try {
await app.renderOnce() await app.renderOnce()
expect(app.captureCharFrame()).toContain("interrupt · 1 agent · ctrl+x down to view · 1 queued prompt · ctrl+x q") expect(app.captureCharFrame()).toContain("interrupt 1 agent ctrl+x down • 1 queued ctrl+x q")
expect(app.captureCharFrame()).toContain("2 queued") expect(app.captureCharFrame()).toContain("2 queued")
expect(app.captureCharFrame()).not.toContain("agent · ·") expect(app.captureCharFrame()).not.toContain("to view")
expect(app.captureCharFrame()).not.toContain("edit/remove")
} finally { } finally {
app.renderer.currentFocusedRenderable?.blur() app.renderer.currentFocusedRenderable?.blur()
app.renderer.currentFocusedEditor?.blur() app.renderer.currentFocusedEditor?.blur()