mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-09-04 10:11:46 +03:00
feat(tui): replace footer hints with model telemetry
This commit is contained in:
+60
-23
@@ -1,6 +1,7 @@
|
||||
import { RGBA, StyledText, TextAttributes, type TextChunk } from "@opentui/core"
|
||||
|
||||
import { optionArrowUp } from "./platform-keys"
|
||||
import { formatTokenCount } from "./context-panel"
|
||||
import type { TokenUsage } from "./protocol"
|
||||
|
||||
export interface FooterHint {
|
||||
key: string
|
||||
@@ -30,10 +31,63 @@ export function contextualFooterHints(
|
||||
mode: FooterMode,
|
||||
width: number,
|
||||
theme: FooterHintTheme,
|
||||
platform: string = process.platform,
|
||||
shiftedEnter = false,
|
||||
_platform: string = process.platform,
|
||||
_shiftedEnter = false,
|
||||
): StyledText {
|
||||
return footerHints(hintsFor(mode, width, platform, shiftedEnter), theme)
|
||||
return footerHints(hintsFor(mode, width), theme)
|
||||
}
|
||||
|
||||
/** Last-turn model telemetry. Passive chrome reports the system, not its manual. */
|
||||
export function footerTelemetry(
|
||||
usage: TokenUsage | null,
|
||||
width: number,
|
||||
theme: FooterHintTheme,
|
||||
): StyledText {
|
||||
if (!usage) return new StyledText([])
|
||||
const parts: string[] = []
|
||||
const duration = usage.generation_ms
|
||||
const measured = usage.measured_completion_tokens
|
||||
if (typeof duration === "number" && duration > 0 && typeof measured === "number") {
|
||||
const rate = measured * 1000 / duration
|
||||
const value = rate < 10 ? rate.toFixed(1) : String(Math.round(rate))
|
||||
const estimated = (usage.estimated_tokens || 0) > 0 ? "~" : ""
|
||||
parts.push(`${estimated}${value} tok/s`)
|
||||
}
|
||||
if (
|
||||
typeof usage.cached_tokens === "number"
|
||||
&& typeof usage.prompt_tokens === "number"
|
||||
&& usage.prompt_tokens > 0
|
||||
) {
|
||||
const hitRate = Math.min(100, Math.max(0, Math.round(
|
||||
usage.cached_tokens * 100 / usage.prompt_tokens,
|
||||
)))
|
||||
parts.push(`cache ${hitRate}%`)
|
||||
}
|
||||
if (width >= 72) {
|
||||
const prompt = usage.prompt_tokens
|
||||
const completion = usage.completion_tokens
|
||||
if (typeof prompt === "number" || typeof completion === "number") {
|
||||
parts.push(`↑${formatTokenCount(prompt || 0)} ↓${formatTokenCount(completion || 0)}`)
|
||||
}
|
||||
}
|
||||
if (width >= 112 && typeof usage.ttft_ms === "number") {
|
||||
const requests = Math.max(1, usage.timed_requests || 1)
|
||||
const average = usage.ttft_ms / requests
|
||||
parts.push(`TTFT ${average < 1000 ? `${Math.round(average)}ms` : `${(average / 1000).toFixed(1)}s`}`)
|
||||
}
|
||||
if (width >= 128 && typeof usage.cost_usd === "number" && usage.cost_usd > 0) {
|
||||
parts.push(`$${usage.cost_usd < 0.01 ? usage.cost_usd.toFixed(4) : usage.cost_usd.toFixed(2)}`)
|
||||
}
|
||||
return footerMetrics(parts, theme)
|
||||
}
|
||||
|
||||
function footerMetrics(parts: readonly string[], theme: FooterHintTheme): StyledText {
|
||||
const chunks: TextChunk[] = []
|
||||
parts.forEach((text, index) => {
|
||||
if (index) chunks.push(chunk(" · ", theme.separator))
|
||||
chunks.push(chunk(text, index === 0 ? theme.accent : theme.muted, index === 0))
|
||||
})
|
||||
return new StyledText(chunks)
|
||||
}
|
||||
|
||||
/** Give shortcuts visual hierarchy without turning the footer into a toolbar. */
|
||||
@@ -51,8 +105,6 @@ export function footerHints(hints: readonly FooterHint[], theme: FooterHintTheme
|
||||
function hintsFor(
|
||||
mode: FooterMode,
|
||||
width: number,
|
||||
platform: string,
|
||||
shiftedEnter: boolean,
|
||||
): FooterHint[] {
|
||||
if (mode === "runtime") return width >= 64
|
||||
? [hint("↑↓/click", "choose"), hint("enter", "apply"), hint("esc", "close")]
|
||||
@@ -60,9 +112,7 @@ function hintsFor(
|
||||
if (mode === "mention") return width >= 64
|
||||
? [hint("↑↓", "choose"), hint("tab/enter", "insert"), hint("esc", "close")]
|
||||
: [hint("enter", "insert"), hint("esc", "close")]
|
||||
if (mode === "active") return width >= 96
|
||||
? [hint("enter", "steer"), hint("tab", "queue"), hint(optionArrowUp(platform), "edit"), stopHint()]
|
||||
: width >= 64 ? [hint("enter", "steer"), hint("tab", "queue"), stopHint()] : []
|
||||
if (mode === "active") return []
|
||||
if (mode === "branch") return width >= 64
|
||||
? [hint("type", "filter"), hint("↑↓", "choose"), hint("enter", "branch"), hint("esc", "close")]
|
||||
: [hint("enter", "branch"), hint("esc", "close")]
|
||||
@@ -76,26 +126,13 @@ function hintsFor(
|
||||
if (mode === "history") return width >= 72
|
||||
? [hint("ctrl+end", "latest"), hint("pgup/pgdn", "scroll")]
|
||||
: width >= 48 ? [hint("ctrl+end", "latest")] : []
|
||||
const newline = shiftedEnter ? "shift+enter" : "ctrl+j"
|
||||
if (width >= 112) return [
|
||||
hint("enter", "send"),
|
||||
hint(newline, "newline"),
|
||||
hint("pgup/pgdn", "scroll"),
|
||||
hint("ctrl+o", "tools"),
|
||||
stopHint(),
|
||||
]
|
||||
if (width >= 72) return [hint("enter", "send"), hint(newline, "newline"), stopHint()]
|
||||
return width >= 48 ? [hint("enter", "send"), hint(newline, "newline")] : []
|
||||
return []
|
||||
}
|
||||
|
||||
function hint(key: string, label: string): FooterHint {
|
||||
return { key, label }
|
||||
}
|
||||
|
||||
function stopHint(): FooterHint {
|
||||
return { key: "ctrl+c", label: "stop", tone: "danger" }
|
||||
}
|
||||
|
||||
function chunk(text: string, color: string, bold = false): TextChunk {
|
||||
return {
|
||||
__isChunk: true,
|
||||
|
||||
Reference in New Issue
Block a user