feat(tui): add transcript compaction progress marker
This commit is contained in:
@@ -32,10 +32,14 @@ import { contentText, type ParsedArguments } from './content.ts'
|
||||
import {
|
||||
formatCompletionTime,
|
||||
formatTimingTotals,
|
||||
STATUS_ANIMATION_INTERVAL_MS,
|
||||
stepTimingAt,
|
||||
type StepPosition,
|
||||
} from '../chat/timing.ts'
|
||||
|
||||
const COMPACTION_PROGRESS_FRAMES = ['◐', '◓', '◑', '◒'] as const
|
||||
const COMPACTION_PROGRESS_LABEL = 'Compaction in progress…'
|
||||
|
||||
/** Concatenate the text of every block of one type, separated by blank lines. */
|
||||
function textBlocks(content: readonly ContentBlock[], type: 'text' | 'reasoning'): string {
|
||||
return content
|
||||
@@ -120,6 +124,29 @@ export class HeaderComponent implements Component {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process-local transcript tail announcing a live standalone compaction.
|
||||
* The leading blank belongs to the component so removing it leaves no gap.
|
||||
*/
|
||||
export class CompactionProgressComponent implements Component {
|
||||
constructor(
|
||||
private readonly startedAt: number,
|
||||
private readonly now: () => number,
|
||||
private readonly palette: Palette,
|
||||
) {}
|
||||
|
||||
invalidate(): void {}
|
||||
|
||||
render(width: number): string[] {
|
||||
const elapsed = Math.max(0, this.now() - this.startedAt)
|
||||
const frameIndex = Math.floor(elapsed / STATUS_ANIMATION_INTERVAL_MS)
|
||||
% COMPACTION_PROGRESS_FRAMES.length
|
||||
const frame = COMPACTION_PROGRESS_FRAMES[frameIndex] as string
|
||||
const marker = `${this.palette.accent(frame)} ${this.palette.dim(COMPACTION_PROGRESS_LABEL)}`
|
||||
return ['', truncateToWidth(marker, Math.max(1, width), '')]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A user or steering prompt in the transcript. An underlined accent role header
|
||||
* plus blank-line spacing separate it from surrounding blocks; body lines carry
|
||||
|
||||
@@ -94,6 +94,7 @@ import {
|
||||
type Config,
|
||||
} from './config.ts'
|
||||
import {
|
||||
CompactionProgressComponent,
|
||||
ContextCardComponent,
|
||||
type ToolCardVisibility,
|
||||
HeaderComponent,
|
||||
@@ -347,7 +348,11 @@ export function createTuiChat(
|
||||
* Live standalone compaction observed by this process. Never derive this
|
||||
* state from history: a resumed log may contain a stale orphaned start.
|
||||
*/
|
||||
let compacting: { startedAt: number; timer: ReturnType<typeof setInterval> } | undefined
|
||||
let compacting: {
|
||||
startedAt: number
|
||||
timer: ReturnType<typeof setInterval>
|
||||
progress: CompactionProgressComponent
|
||||
} | undefined
|
||||
// TUI steering submissions that the inbox has not yet claimed or discarded.
|
||||
// Correlation ids avoid guessing whether a running-state submission actually
|
||||
// joined steering or fell back to the queued-turn FIFO during turn close.
|
||||
@@ -478,6 +483,11 @@ export function createTuiChat(
|
||||
|
||||
const requestRender = (): void => {
|
||||
if (disposed) return
|
||||
if (compacting !== undefined) {
|
||||
compacting.progress.invalidate()
|
||||
chat.removeChild(compacting.progress)
|
||||
chat.addChild(compacting.progress)
|
||||
}
|
||||
updatePromptValues()
|
||||
const inputPrompt = renderInputPrompt()
|
||||
editor.setPrompt({ first: inputPrompt, continuation: ' '.repeat(visibleWidth(inputPrompt)) })
|
||||
@@ -567,6 +577,7 @@ export function createTuiChat(
|
||||
const clearStatus = (): void => {
|
||||
if (compacting !== undefined) {
|
||||
clearInterval(compacting.timer)
|
||||
chat.removeChild(compacting.progress)
|
||||
compacting = undefined
|
||||
}
|
||||
clearTurnStatus()
|
||||
@@ -1525,9 +1536,11 @@ export function createTuiChat(
|
||||
// a stale resumed orphan for current work.
|
||||
if (event.type === 'compact/start' && event.data.turn === null) {
|
||||
if (compacting === undefined) {
|
||||
const startedAt = now()
|
||||
compacting = {
|
||||
startedAt: now(),
|
||||
startedAt,
|
||||
timer: setInterval(renderStatus, STATUS_ANIMATION_INTERVAL_MS),
|
||||
progress: new CompactionProgressComponent(startedAt, now, palette),
|
||||
}
|
||||
runtime.terminal.setProgress(true)
|
||||
}
|
||||
@@ -1536,6 +1549,7 @@ export function createTuiChat(
|
||||
}
|
||||
if (event.type === 'compact/end' && event.data.turn === null && compacting !== undefined) {
|
||||
clearInterval(compacting.timer)
|
||||
chat.removeChild(compacting.progress)
|
||||
compacting = undefined
|
||||
if (event.data.error !== undefined) {
|
||||
appendNotice(`Compaction failed: ${event.data.error}`, 'warning')
|
||||
|
||||
Reference in New Issue
Block a user