tui: fix agent cycling and prompt metadata polish (#23115)

This commit is contained in:
Kit Langton
2026-04-17 12:45:29 -04:00
committed by GitHub
parent c491161c0c
commit 13dfe569ef
4 changed files with 24 additions and 12 deletions

View File

@@ -63,6 +63,7 @@ function init() {
useKeyboard((evt) => { useKeyboard((evt) => {
if (suspended()) return if (suspended()) return
if (dialog.stack.length > 0) return if (dialog.stack.length > 0) return
if (evt.defaultPrevented) return
for (const option of entries()) { for (const option of entries()) {
if (!isEnabled(option)) continue if (!isEnabled(option)) continue
if (option.keybind && keybind.match(option.keybind, evt)) { if (option.keybind && keybind.match(option.keybind, evt)) {

View File

@@ -5,7 +5,7 @@ import path from "path"
import { fileURLToPath } from "url" import { fileURLToPath } from "url"
import { Filesystem } from "@/util" import { Filesystem } from "@/util"
import { useLocal } from "@tui/context/local" import { useLocal } from "@tui/context/local"
import { useTheme } from "@tui/context/theme" import { tint, useTheme } from "@tui/context/theme"
import { EmptyBorder, SplitBorder } from "@tui/component/border" import { EmptyBorder, SplitBorder } from "@tui/component/border"
import { useSDK } from "@tui/context/sdk" import { useSDK } from "@tui/context/sdk"
import { useRoute } from "@tui/context/route" import { useRoute } from "@tui/context/route"
@@ -463,19 +463,25 @@ export function Prompt(props: PromptProps) {
createEffect(() => { createEffect(() => {
if (!input || input.isDestroyed) return if (!input || input.isDestroyed) return
if (props.visible === false || dialog.stack.length > 0) { if (props.visible === false || dialog.stack.length > 0) {
input.blur() if (input.focused) input.blur()
return return
} }
// Slot/plugin updates can remount the background prompt while a dialog is open. // Slot/plugin updates can remount the background prompt while a dialog is open.
// Keep focus with the dialog and let the prompt reclaim it after the dialog closes. // Keep focus with the dialog and let the prompt reclaim it after the dialog closes.
input.focus() if (!input.focused) input.focus()
}) })
createEffect(() => { createEffect(() => {
if (!input || input.isDestroyed) return if (!input || input.isDestroyed) return
const capture =
store.mode === "normal"
? auto()?.visible
? (["escape", "navigate", "submit", "tab"] as const)
: (["tab"] as const)
: undefined
input.traits = { input.traits = {
capture: auto()?.visible ? ["escape", "navigate", "submit", "tab"] : undefined, capture,
suspend: !!props.disabled || store.mode === "shell", suspend: !!props.disabled || store.mode === "shell",
status: store.mode === "shell" ? "SHELL" : undefined, status: store.mode === "shell" ? "SHELL" : undefined,
} }
@@ -870,6 +876,7 @@ export function Prompt(props: PromptProps) {
() => !!local.agent.current() && store.mode === "normal" && showVariant(), () => !!local.agent.current() && store.mode === "normal" && showVariant(),
animationsEnabled, animationsEnabled,
) )
const borderHighlight = createMemo(() => tint(theme.border, highlight(), agentMetaAlpha()))
const placeholderText = createMemo(() => { const placeholderText = createMemo(() => {
if (props.showPlaceholder === false) return undefined if (props.showPlaceholder === false) return undefined
@@ -931,7 +938,7 @@ export function Prompt(props: PromptProps) {
<box ref={(r) => (anchor = r)} visible={props.visible !== false}> <box ref={(r) => (anchor = r)} visible={props.visible !== false}>
<box <box
border={["left"]} border={["left"]}
borderColor={highlight()} borderColor={borderHighlight()}
customBorderChars={{ customBorderChars={{
...SplitBorder.customBorderChars, ...SplitBorder.customBorderChars,
bottomLeft: "╹", bottomLeft: "╹",
@@ -1146,11 +1153,10 @@ export function Prompt(props: PromptProps) {
<Show when={local.agent.current()} fallback={<box height={1} />}> <Show when={local.agent.current()} fallback={<box height={1} />}>
{(agent) => ( {(agent) => (
<> <>
<text fg={fadeColor(highlight(), agentMetaAlpha())}> <text fg={fadeColor(highlight(), agentMetaAlpha())}>{store.mode === "shell" ? "Shell" : Locale.titlecase(agent().name)}</text>
{store.mode === "shell" ? "Shell" : Locale.titlecase(agent().name)}{" "}
</text>
<Show when={store.mode === "normal"}> <Show when={store.mode === "normal"}>
<box flexDirection="row" gap={1}> <box flexDirection="row" gap={1}>
<text fg={fadeColor(theme.textMuted, modelMetaAlpha())}>·</text>
<text <text
flexShrink={0} flexShrink={0}
fg={fadeColor(keybind.leader ? theme.textMuted : theme.text, modelMetaAlpha())} fg={fadeColor(keybind.leader ? theme.textMuted : theme.text, modelMetaAlpha())}
@@ -1183,7 +1189,7 @@ export function Prompt(props: PromptProps) {
<box <box
height={1} height={1}
border={["left"]} border={["left"]}
borderColor={highlight()} borderColor={borderHighlight()}
customBorderChars={{ customBorderChars={{
...EmptyBorder, ...EmptyBorder,
vertical: theme.backgroundElement.a !== 0 ? "╹" : " ", vertical: theme.backgroundElement.a !== 0 ? "╹" : " ",

View File

@@ -75,7 +75,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}, },
move(direction: 1 | -1) { move(direction: 1 | -1) {
batch(() => { batch(() => {
let next = agents().findIndex((x) => x.name === agentStore.current) + direction const current = this.current()
if (!current) return
let next = agents().findIndex((x) => x.name === current.name) + direction
if (next < 0) next = agents().length - 1 if (next < 0) next = agents().length - 1
if (next >= agents().length) next = 0 if (next >= agents().length) next = 0
const value = agents()[next] const value = agents()[next]

View File

@@ -8,20 +8,23 @@ export function createDebouncedSignal<T>(value: T, ms: number): [Accessor<T>, Sc
export function createFadeIn(show: Accessor<boolean>, enabled: Accessor<boolean>) { export function createFadeIn(show: Accessor<boolean>, enabled: Accessor<boolean>) {
const [alpha, setAlpha] = createSignal(show() ? 1 : 0) const [alpha, setAlpha] = createSignal(show() ? 1 : 0)
let revealed = show()
createEffect( createEffect(
on([show, enabled], ([visible, animate], previous) => { on([show, enabled], ([visible, animate]) => {
if (!visible) { if (!visible) {
setAlpha(0) setAlpha(0)
return return
} }
if (!animate || !previous) { if (!animate || revealed) {
revealed = true
setAlpha(1) setAlpha(1)
return return
} }
const start = performance.now() const start = performance.now()
revealed = true
setAlpha(0) setAlpha(0)
const timer = setInterval(() => { const timer = setInterval(() => {