diff --git a/tui/src/app.test.ts b/tui/src/app.test.ts index 4e55242b2..c03ffa13e 100644 --- a/tui/src/app.test.ts +++ b/tui/src/app.test.ts @@ -1529,8 +1529,7 @@ describe("NanobotTui layout", () => { const footer = setup.captureCharFrame().split("\n").find((line) => line.includes("Ready · 1.7s")) || "" expect(footer).toContain("Ready · 1.7s") expect(footer).toContain("50 tok/s") - expect(footer).toContain("1.2K in · 80 out") - expect(footer).toContain("75% cached") + expect(footer).toContain("1.2K in (75% cached) · 80 out") expect(footer).not.toContain("TTFT") expect(footer).not.toContain("enter send") diff --git a/tui/src/footer-hints.test.ts b/tui/src/footer-hints.test.ts index 40e7af4b1..6338e71a0 100644 --- a/tui/src/footer-hints.test.ts +++ b/tui/src/footer-hints.test.ts @@ -29,7 +29,7 @@ describe("footerHints", () => { expect(active.chunks).toHaveLength(0) }) - test("shows measured throughput, explicit token directions, and cache ratio", () => { + test("groups the cache ratio with input telemetry", () => { const result = footerTelemetry({ prompt_tokens: 1200, completion_tokens: 80, @@ -41,7 +41,7 @@ describe("footerHints", () => { }, 120, theme) expect(result.chunks.map(({ text }) => text).join("")) - .toBe("50 tok/s · 1.2K in · 80 out · 75% cached") + .toBe("50 tok/s · 1.2K in (75% cached) · 80 out") expect(result.chunks[0]?.fg?.toInts().slice(0, 3)).toEqual([239, 142, 48]) }) @@ -55,7 +55,7 @@ describe("footerHints", () => { }, 120, theme) expect(result.chunks.map(({ text }) => text).join("")) - .toBe("140 tok/s · 4.5M in · 19K out · 80% cached") + .toBe("140 tok/s · 4.5M in (80% cached) · 19K out") }) test("degrades telemetry instead of guessing missing provider metrics", () => { diff --git a/tui/src/footer-hints.ts b/tui/src/footer-hints.ts index 906a40c4c..3629526b4 100644 --- a/tui/src/footer-hints.ts +++ b/tui/src/footer-hints.ts @@ -52,23 +52,25 @@ export function footerTelemetry( const estimated = (usage.estimated_tokens || 0) > 0 ? "~" : "" parts.push(`${estimated}${value} tok/s`) } + const cacheHitRate = ( + typeof usage.cached_tokens === "number" + && typeof usage.prompt_tokens === "number" + && usage.prompt_tokens > 0 + ) + ? Math.min(100, Math.max(0, Math.round( + usage.cached_tokens * 100 / usage.prompt_tokens, + ))) + : null if (width >= 72) { const prompt = usage.prompt_tokens const completion = usage.completion_tokens if (typeof prompt === "number" || typeof completion === "number") { - parts.push(`${formatTelemetryTokens(prompt || 0)} in`) + const cache = cacheHitRate === null ? "" : ` (${cacheHitRate}% cached)` + parts.push(`${formatTelemetryTokens(prompt || 0)} in${cache}`) parts.push(`${formatTelemetryTokens(completion || 0)} out`) } - } - 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(`${hitRate}% cached`) + } else if (cacheHitRate !== null) { + parts.push(`${cacheHitRate}% cached`) } 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)}`)