Files
nanobot/tui/src/queue-preview.ts
T

100 lines
2.7 KiB
TypeScript

import {
BoxRenderable,
RGBA,
StyledText,
TextAttributes,
TextRenderable,
type CliRenderer,
type TextChunk,
} from "@opentui/core"
import { optionArrowUp } from "./platform-keys"
export interface QueuePreviewTheme {
accent: string
muted: string
faint: string
}
const MAX_VISIBLE = 3
/** A compact, retained projection of follow-ups waiting behind the active turn. */
export class QueuePreview {
readonly root: BoxRenderable
private readonly header: TextRenderable
private readonly rows: TextRenderable[]
private readonly editKey: string
private theme: QueuePreviewTheme
private messages: readonly string[] = []
constructor(renderer: CliRenderer, theme: QueuePreviewTheme, platform: string = process.platform) {
this.theme = theme
this.editKey = optionArrowUp(platform)
this.root = new BoxRenderable(renderer, {
id: "nanobot-tui-queue-preview",
width: "100%",
height: 1,
flexShrink: 0,
flexDirection: "column",
paddingLeft: 1,
paddingRight: 1,
visible: false,
backgroundColor: RGBA.defaultBackground(),
})
this.header = new TextRenderable(renderer, {
id: "nanobot-tui-queue-header",
width: "100%",
height: 1,
flexShrink: 0,
truncate: true,
})
this.rows = Array.from({ length: MAX_VISIBLE }, (_, index) => new TextRenderable(renderer, {
id: `nanobot-tui-queue-row-${index}`,
width: "100%",
height: 1,
flexShrink: 0,
truncate: true,
visible: false,
}))
this.root.add(this.header)
for (const row of this.rows) this.root.add(row)
}
update(messages: readonly string[]): void {
this.messages = [...messages]
const visible = messages.slice(-MAX_VISIBLE)
this.root.visible = messages.length > 0
this.root.height = messages.length ? visible.length + 1 : 1
this.header.content = new StyledText([
chunk("Queued next", this.theme.accent, true),
chunk(` ${messages.length}`, this.theme.faint),
chunk(` · ${this.editKey} edit last`, this.theme.faint),
])
this.rows.forEach((row, index) => {
const message = visible[index]
row.visible = Boolean(message)
row.content = message ? ` ↳ ${oneLine(message)}` : ""
row.fg = this.theme.muted
})
}
setTheme(theme: QueuePreviewTheme): void {
this.theme = theme
this.update(this.messages)
}
}
function oneLine(value: string): string {
const preview = value.slice(0, 240).replace(/\s+/gu, " ").trim()
return value.length > 240 ? `${preview}…` : preview
}
function chunk(text: string, color: string, bold = false): TextChunk {
return {
__isChunk: true,
text,
fg: RGBA.fromHex(color),
attributes: bold ? TextAttributes.BOLD : 0,
}
}