refactor(tui): tighten overlay helpers

- rename overlay help text component to match its role
- share picker window math across model, session, and skills overlays
This commit is contained in:
Brooklyn Nicholson 2026-04-25 14:23:45 -05:00
parent c6fdf48b79
commit 6e83d90eb4
5 changed files with 63 additions and 69 deletions

View file

@ -7,20 +7,12 @@ import type { ModelOptionProvider, ModelOptionsResponse } from '../gatewayTypes.
import { asRpcResult, rpcErrorMessage } from '../lib/rpc.js'
import type { Theme } from '../theme.js'
import { OverlayControls, useOverlayKeys } from './overlayControls.js'
import { OverlayHint, useOverlayKeys, windowItems, windowOffset } from './overlayControls.js'
const VISIBLE = 12
const MIN_WIDTH = 40
const MAX_WIDTH = 90
const pageOffset = (count: number, sel: number) => Math.max(0, Math.min(sel - Math.floor(VISIBLE / 2), count - VISIBLE))
const visibleItems = (items: string[], sel: number) => {
const off = pageOffset(items.length, sel)
return { items: items.slice(off, off + VISIBLE), off }
}
export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPickerProps) {
const [providers, setProviders] = useState<ModelOptionProvider[]>([])
const [currentModel, setCurrentModel] = useState('')
@ -135,16 +127,16 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
const n = ch === '0' ? 10 : parseInt(ch, 10)
if (!Number.isNaN(n) && n >= 1 && n <= Math.min(10, count)) {
const off = pageOffset(count, sel)
const offset = windowOffset(count, sel, VISIBLE)
if (stage === 'provider') {
const next = off + n - 1
const next = offset + n - 1
if (providers[next]) {
setProviderIdx(next)
}
} else if (provider && models[off + n - 1]) {
onSelect(`${models[off + n - 1]} --provider ${provider.slug}${persistGlobal ? ' --global' : ''}`)
} else if (provider && models[offset + n - 1]) {
onSelect(`${models[offset + n - 1]} --provider ${provider.slug}${persistGlobal ? ' --global' : ''}`)
}
}
})
@ -157,7 +149,7 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
return (
<Box flexDirection="column">
<Text color={t.color.label}>error: {err}</Text>
<OverlayControls t={t}>Esc/q cancel</OverlayControls>
<OverlayHint t={t}>Esc/q cancel</OverlayHint>
</Box>
)
}
@ -166,7 +158,7 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
return (
<Box flexDirection="column">
<Text color={t.color.dim}>no authenticated providers</Text>
<OverlayControls t={t}>Esc/q cancel</OverlayControls>
<OverlayHint t={t}>Esc/q cancel</OverlayHint>
</Box>
)
}
@ -176,7 +168,7 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
(p, i) => `${p.is_current ? '*' : ' '} ${names[i]} · ${p.total_models ?? p.models?.length ?? 0} models`
)
const { items, off } = visibleItems(rows, providerIdx)
const { items, offset } = windowItems(rows, providerIdx, VISIBLE)
return (
<Box flexDirection="column" width={width}>
@ -191,12 +183,12 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
{provider?.warning ? `warning: ${provider.warning}` : ' '}
</Text>
<Text color={t.color.dim} wrap="truncate-end">
{off > 0 ? `${off} more` : ' '}
{offset > 0 ? `${offset} more` : ' '}
</Text>
{Array.from({ length: VISIBLE }, (_, i) => {
const row = items[i]
const idx = off + i
const idx = offset + i
return row ? (
<Text
@ -217,18 +209,18 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
})}
<Text color={t.color.dim} wrap="truncate-end">
{off + VISIBLE < rows.length ? `${rows.length - off - VISIBLE} more` : ' '}
{offset + VISIBLE < rows.length ? `${rows.length - offset - VISIBLE} more` : ' '}
</Text>
<Text color={t.color.dim} wrap="truncate-end">
persist: {persistGlobal ? 'global' : 'session'} · g toggle
</Text>
<OverlayControls t={t}>/ select · Enter choose · 1-9,0 quick · Esc/q cancel</OverlayControls>
<OverlayHint t={t}>/ select · Enter choose · 1-9,0 quick · Esc/q cancel</OverlayHint>
</Box>
)
}
const { items, off } = visibleItems(models, modelIdx)
const { items, offset } = windowItems(models, modelIdx, VISIBLE)
return (
<Box flexDirection="column" width={width}>
@ -243,12 +235,12 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
{provider?.warning ? `warning: ${provider.warning}` : ' '}
</Text>
<Text color={t.color.dim} wrap="truncate-end">
{off > 0 ? `${off} more` : ' '}
{offset > 0 ? `${offset} more` : ' '}
</Text>
{Array.from({ length: VISIBLE }, (_, i) => {
const row = items[i]
const idx = off + i
const idx = offset + i
if (!row) {
return !models.length && i === 0 ? (
@ -277,15 +269,15 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
})}
<Text color={t.color.dim} wrap="truncate-end">
{off + VISIBLE < models.length ? `${models.length - off - VISIBLE} more` : ' '}
{offset + VISIBLE < models.length ? `${models.length - offset - VISIBLE} more` : ' '}
</Text>
<Text color={t.color.dim} wrap="truncate-end">
persist: {persistGlobal ? 'global' : 'session'} · g toggle
</Text>
<OverlayControls t={t}>
<OverlayHint t={t}>
{models.length ? '↑/↓ select · Enter switch · 1-9,0 quick · Esc back · q close' : 'Enter/Esc back · q close'}
</OverlayControls>
</OverlayHint>
</Box>
)
}