feat(usage-row): use database icon for cache, add ttft + throughput display
This commit is contained in:
@@ -81,6 +81,8 @@ export class ChatTranscriptBuilder {
|
||||
#readArgs = new Map<string, Record<string, unknown>>();
|
||||
#readGroup: ReadToolGroupComponent | null = null;
|
||||
#pendingUsage: Usage | undefined;
|
||||
#pendingUsageDuration: number | undefined;
|
||||
#pendingUsageTtft: number | undefined;
|
||||
#lastAssistantUsage: Usage | undefined;
|
||||
#waitingPoll: ToolExecutionComponent | null = null;
|
||||
#todoSnapshot: ToolExecutionComponent | null = null;
|
||||
@@ -127,6 +129,8 @@ export class ChatTranscriptBuilder {
|
||||
this.#readArgs.clear();
|
||||
this.#readGroup = null;
|
||||
this.#pendingUsage = undefined;
|
||||
this.#pendingUsageDuration = undefined;
|
||||
this.#pendingUsageTtft = undefined;
|
||||
this.#lastAssistantUsage = undefined;
|
||||
this.#waitingPoll = null;
|
||||
this.#todoSnapshot = null;
|
||||
@@ -192,8 +196,12 @@ export class ChatTranscriptBuilder {
|
||||
if (!this.#pendingUsage) return;
|
||||
this.#readGroup?.seal();
|
||||
this.#readGroup = null;
|
||||
this.container.addChild(createUsageRowBlock(this.#pendingUsage));
|
||||
this.container.addChild(
|
||||
createUsageRowBlock(this.#pendingUsage, this.#pendingUsageDuration, this.#pendingUsageTtft),
|
||||
);
|
||||
this.#pendingUsage = undefined;
|
||||
this.#pendingUsageDuration = undefined;
|
||||
this.#pendingUsageTtft = undefined;
|
||||
}
|
||||
|
||||
#appendChatMessage(message: AgentMessage): void {
|
||||
@@ -343,6 +351,8 @@ export class ChatTranscriptBuilder {
|
||||
}
|
||||
|
||||
this.#pendingUsage = settings.get("display.showTokenUsage") ? message.usage : undefined;
|
||||
this.#pendingUsageDuration = message.duration;
|
||||
this.#pendingUsageTtft = message.ttft;
|
||||
}
|
||||
|
||||
#appendToolResult(message: Extract<AgentMessage, { role: "toolResult" }>): void {
|
||||
|
||||
@@ -3,13 +3,27 @@ import { Container, Spacer, Text } from "@oh-my-pi/pi-tui";
|
||||
import { formatNumber } from "@oh-my-pi/pi-utils";
|
||||
import { theme } from "../../modes/theme/theme";
|
||||
|
||||
export function createUsageRowBlock(usage: Usage): Container {
|
||||
/** Below this the rate is nonsense (cached/instant responses yield absurd tok/s). */
|
||||
const MIN_DURATION_MS = 100;
|
||||
|
||||
export function createUsageRowBlock(usage: Usage, durationMs?: number, ttftMs?: number): Container {
|
||||
const totalInput = usage.input + usage.cacheWrite;
|
||||
const parts: string[] = [];
|
||||
parts.push(`${theme.icon.input} ${formatNumber(totalInput)}`);
|
||||
parts.push(`${theme.icon.output} ${formatNumber(usage.output)}`);
|
||||
if (usage.cacheRead > 0) {
|
||||
parts.push(`cache: ${formatNumber(usage.cacheRead)}`);
|
||||
parts.push(`${theme.icon.cache} ${formatNumber(usage.cacheRead)}`);
|
||||
}
|
||||
if (ttftMs && ttftMs > 0) {
|
||||
parts.push(`${theme.icon.time} ${(ttftMs / 1000).toFixed(1)}s`);
|
||||
}
|
||||
if (durationMs && durationMs > MIN_DURATION_MS && usage.output > 0) {
|
||||
// Throughput excludes TTFT — generation time is duration minus time-to-first-token.
|
||||
const genMs = durationMs - (ttftMs ?? 0);
|
||||
if (genMs > MIN_DURATION_MS) {
|
||||
const tokPerSec = (usage.output / genMs) * 1000;
|
||||
parts.push(`${theme.icon.throughput} ${tokPerSec.toFixed(1)}/s`);
|
||||
}
|
||||
}
|
||||
const block = new Container();
|
||||
block.addChild(new Spacer(1));
|
||||
|
||||
@@ -786,7 +786,9 @@ export class EventController {
|
||||
this.#lastAssistantComponent = this.ctx.streamingComponent;
|
||||
this.#lastAssistantComponent.markTranscriptBlockFinalized();
|
||||
if (settings.get("display.showTokenUsage")) {
|
||||
this.ctx.chatContainer.addChild(createUsageRowBlock(event.message.usage));
|
||||
this.ctx.chatContainer.addChild(
|
||||
createUsageRowBlock(event.message.usage, event.message.duration, event.message.ttft),
|
||||
);
|
||||
}
|
||||
this.ctx.streamingComponent = undefined;
|
||||
this.ctx.streamingMessage = undefined;
|
||||
|
||||
@@ -297,12 +297,16 @@ export class UiHelpers {
|
||||
// read run so the row sits under it. Mirrors the live path, where the read
|
||||
// group is created during streaming and the row is appended below it.
|
||||
let pendingUsage: Usage | undefined;
|
||||
let pendingUsageDuration: number | undefined;
|
||||
let pendingUsageTtft: number | undefined;
|
||||
const flushPendingUsage = () => {
|
||||
if (!pendingUsage) return;
|
||||
readGroup?.seal();
|
||||
readGroup = null;
|
||||
this.ctx.chatContainer.addChild(createUsageRowBlock(pendingUsage));
|
||||
this.ctx.chatContainer.addChild(createUsageRowBlock(pendingUsage, pendingUsageDuration, pendingUsageTtft));
|
||||
pendingUsage = undefined;
|
||||
pendingUsageDuration = undefined;
|
||||
pendingUsageTtft = undefined;
|
||||
};
|
||||
// Rebuild-time mirror of the event controller's displaceable-poll
|
||||
// bookkeeping: a `job` poll that found every watched job still running is
|
||||
@@ -450,11 +454,12 @@ export class UiHelpers {
|
||||
false,
|
||||
content.id,
|
||||
);
|
||||
} else {
|
||||
this.ctx.pendingTools.set(content.id, component);
|
||||
}
|
||||
}
|
||||
pendingUsage = this.ctx.settings.get("display.showTokenUsage") ? message.usage : undefined;
|
||||
pendingUsageDuration = message.duration;
|
||||
pendingUsageTtft = message.ttft;
|
||||
} else if (message.role === "toolResult") {
|
||||
const pendingReadComponent = this.ctx.pendingTools.get(message.toolCallId);
|
||||
const isReadGroupResult =
|
||||
|
||||
Reference in New Issue
Block a user