Merge pull request #13105 from NousResearch/bb/tui-elapsed-lastmsg-8541

feat(tui): turn elapsed in FaceTicker + done-in sys line on turn end (#8541)
This commit is contained in:
brooklyn! 2026-04-20 11:40:52 -05:00 committed by GitHub
commit ab37132e59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 37 additions and 6 deletions

View file

@ -303,6 +303,7 @@ export interface AppLayoutStatusProps {
showStickyPrompt: boolean
statusColor: string
stickyPrompt: string
turnStartedAt: null | number
voiceLabel: string
}

View file

@ -102,6 +102,7 @@ export function useMainApp(gw: GatewayClient) {
const [voiceRecording, setVoiceRecording] = useState(false)
const [voiceProcessing, setVoiceProcessing] = useState(false)
const [sessionStartedAt, setSessionStartedAt] = useState(() => Date.now())
const [turnStartedAt, setTurnStartedAt] = useState<null | number>(null)
const [goodVibesTick, setGoodVibesTick] = useState(0)
const [bellOnComplete, setBellOnComplete] = useState(false)
@ -283,6 +284,14 @@ export function useMainApp(gw: GatewayClient) {
sys
})
useEffect(() => {
if (ui.busy) {
setTurnStartedAt(prev => prev ?? Date.now())
} else {
setTurnStartedAt(null)
}
}, [ui.busy])
useConfigSync({ gw, setBellOnComplete, setVoiceEnabled, sid: ui.sid })
// ── Terminal tab title ─────────────────────────────────────────────
@ -635,9 +644,21 @@ export function useMainApp(gw: GatewayClient) {
showStickyPrompt: !!stickyPrompt,
statusColor: statusColorOf(ui.status, ui.theme.color),
stickyPrompt,
turnStartedAt: ui.sid ? turnStartedAt : null,
voiceLabel: voiceRecording ? 'REC' : voiceProcessing ? 'STT' : `voice ${voiceEnabled ? 'on' : 'off'}`
}),
[cwd, gitBranch, goodVibesTick, sessionStartedAt, stickyPrompt, ui, voiceEnabled, voiceProcessing, voiceRecording]
[
cwd,
gitBranch,
goodVibesTick,
sessionStartedAt,
stickyPrompt,
turnStartedAt,
ui,
voiceEnabled,
voiceProcessing,
voiceRecording
]
)
const appTranscript = useMemo(

View file

@ -12,18 +12,24 @@ import type { Msg, Usage } from '../types.js'
const FACE_TICK_MS = 2500
const HEART_COLORS = ['#ff5fa2', '#ff4d6d']
function FaceTicker({ color }: { color: string }) {
function FaceTicker({ color, startedAt }: { color: string; startedAt?: null | number }) {
const [tick, setTick] = useState(() => Math.floor(Math.random() * 1000))
const [now, setNow] = useState(() => Date.now())
useEffect(() => {
const id = setInterval(() => setTick(n => n + 1), FACE_TICK_MS)
const face = setInterval(() => setTick(n => n + 1), FACE_TICK_MS)
const clock = setInterval(() => setNow(Date.now()), 1000)
return () => clearInterval(id)
return () => {
clearInterval(face)
clearInterval(clock)
}
}, [])
return (
<Text color={color}>
{FACES[tick % FACES.length]} {VERBS[tick % VERBS.length]}
{startedAt ? ` · ${fmtDuration(now - startedAt)}` : ''}
</Text>
)
}
@ -100,6 +106,7 @@ export function StatusRule({
bgCount,
sessionStartedAt,
showCost,
turnStartedAt,
voiceLabel,
t
}: StatusRuleProps) {
@ -120,7 +127,7 @@ export function StatusRule({
<Box flexShrink={1} width={leftWidth}>
<Text color={t.color.bronze} wrap="truncate-end">
{'─ '}
{busy ? <FaceTicker color={statusColor} /> : <Text color={statusColor}>{status}</Text>}
{busy ? <FaceTicker color={statusColor} startedAt={turnStartedAt} /> : <Text color={statusColor}>{status}</Text>}
<Text color={t.color.dim}> {model}</Text>
{ctxLabel ? <Text color={t.color.dim}> {ctxLabel}</Text> : null}
{bar ? (
@ -288,11 +295,12 @@ interface StatusRuleProps {
cols: number
cwdLabel: string
model: string
sessionStartedAt?: number | null
sessionStartedAt?: null | number
showCost: boolean
status: string
statusColor: string
t: Theme
turnStartedAt?: null | number
usage: Usage
voiceLabel?: string
}

View file

@ -194,6 +194,7 @@ const ComposerPane = memo(function ComposerPane({
status={ui.status}
statusColor={status.statusColor}
t={ui.theme}
turnStartedAt={status.turnStartedAt}
usage={ui.usage}
voiceLabel={status.voiceLabel}
/>