mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 08:13:11 +03:00
117 lines
3.4 KiB
TypeScript
117 lines
3.4 KiB
TypeScript
import {
|
|
BoxRenderable,
|
|
TextAttributes,
|
|
TextRenderable,
|
|
type CliRenderer,
|
|
} from "@opentui/core"
|
|
|
|
import type { SessionContextSnapshot } from "./protocol"
|
|
|
|
export interface ContextPanelTheme {
|
|
text: string
|
|
muted: string
|
|
border: string
|
|
accent: string
|
|
}
|
|
|
|
export function formatTokenCount(value: number): string {
|
|
if (value < 1_000) return String(value)
|
|
const compact = value >= 10_000 ? Math.round(value / 1_000) : Math.round(value / 100) / 10
|
|
return `${compact}k`
|
|
}
|
|
|
|
/** Read-only explanation of the session-owned context replayed to the agent. */
|
|
export class ContextPanel {
|
|
readonly root: BoxRenderable
|
|
private readonly title: TextRenderable
|
|
private readonly stats: TextRenderable
|
|
private readonly summary: TextRenderable
|
|
private readonly note: TextRenderable
|
|
|
|
constructor(renderer: CliRenderer, theme: ContextPanelTheme) {
|
|
this.root = new BoxRenderable(renderer, {
|
|
id: "nanobot-tui-context-panel",
|
|
width: "100%",
|
|
maxHeight: 12,
|
|
flexShrink: 0,
|
|
flexDirection: "column",
|
|
border: true,
|
|
borderStyle: "rounded",
|
|
borderColor: theme.border,
|
|
paddingLeft: 1,
|
|
paddingRight: 1,
|
|
visible: false,
|
|
})
|
|
this.title = new TextRenderable(renderer, {
|
|
id: "nanobot-tui-context-title",
|
|
content: "Agent context",
|
|
width: "100%",
|
|
height: 1,
|
|
fg: theme.text,
|
|
attributes: TextAttributes.BOLD,
|
|
})
|
|
this.stats = new TextRenderable(renderer, {
|
|
id: "nanobot-tui-context-stats",
|
|
content: "",
|
|
width: "100%",
|
|
fg: theme.accent,
|
|
wrapMode: "word",
|
|
})
|
|
this.summary = new TextRenderable(renderer, {
|
|
id: "nanobot-tui-context-summary",
|
|
content: "",
|
|
width: "100%",
|
|
maxHeight: 6,
|
|
fg: theme.text,
|
|
wrapMode: "word",
|
|
})
|
|
this.note = new TextRenderable(renderer, {
|
|
id: "nanobot-tui-context-note",
|
|
content: "Session view only · memory, instructions, and skills are added separately · Esc close",
|
|
width: "100%",
|
|
fg: theme.muted,
|
|
wrapMode: "word",
|
|
})
|
|
this.root.add(this.title)
|
|
this.root.add(this.stats)
|
|
this.root.add(this.summary)
|
|
this.root.add(this.note)
|
|
}
|
|
|
|
get visible(): boolean {
|
|
return this.root.visible
|
|
}
|
|
|
|
show(context: SessionContextSnapshot): void {
|
|
const archived = context.archivedMessages > 0
|
|
? `${context.archivedMessages} archived · summary ${context.archivedSummary ? "active" : "unavailable"}`
|
|
: "No archived messages"
|
|
this.stats.content = `~${formatTokenCount(context.estimatedSessionTokens)} session tokens · ${context.replayMessages} replay messages · ${archived}`
|
|
this.summary.content = context.archivedSummary
|
|
? `Summary\n${context.archivedSummary}`
|
|
: "The agent is currently replaying raw session messages; no compacted summary exists yet."
|
|
this.root.visible = true
|
|
}
|
|
|
|
hide(): void {
|
|
this.root.visible = false
|
|
}
|
|
|
|
resize(terminalHeight: number): void {
|
|
const compact = terminalHeight < 14
|
|
const medium = terminalHeight < 20
|
|
this.summary.visible = !compact
|
|
this.summary.maxHeight = medium ? 2 : 6
|
|
this.note.visible = !medium
|
|
this.root.maxHeight = compact ? 4 : medium ? 6 : 12
|
|
}
|
|
|
|
setTheme(theme: ContextPanelTheme): void {
|
|
this.root.borderColor = theme.border
|
|
this.title.fg = theme.text
|
|
this.stats.fg = theme.accent
|
|
this.summary.fg = theme.text
|
|
this.note.fg = theme.muted
|
|
}
|
|
}
|