mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-09-04 10:11:46 +03:00
feat(tui): make follow-up queue explicit
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
BoxRenderable,
|
||||
RGBA,
|
||||
StyledText,
|
||||
TextAttributes,
|
||||
TextRenderable,
|
||||
type CliRenderer,
|
||||
type TextChunk,
|
||||
} from "@opentui/core"
|
||||
|
||||
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 theme: QueuePreviewTheme
|
||||
private messages: readonly string[] = []
|
||||
|
||||
constructor(renderer: CliRenderer, theme: QueuePreviewTheme) {
|
||||
this.theme = theme
|
||||
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(" · alt+↑ 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user