Migration (multi-agent sweep over 216 files, 60 batches):
- Every user-visible dashboard + TUI string moved to t() with the exact
English inline default (en rendering byte-identical)
- Catalogs merged from per-batch fragments: en/zh-CN/zh-TW/fr/es now
carry ~5,930 keys each across common/app/errors/cli namespaces;
CLI bundles regenerated (6 locales incl. ko)
Integration fixes:
- 18 type errors: reserved {{count}} interpolations renamed, malformed
plural call, hand-rolled t-param types replaced with TFunction<"app">
- 23 lint errors: superseded label constants/helpers removed
- ExecutorStatusBar hook-order violation (keyboard-open early return
moved below hooks)
- TUI tests wrapped in I18nextProvider (uninitialized fallback renders
literal {{placeholders}}); dashboard vitest.setup boots a minimal en
i18next instance for the same reason
Known WIP (next commits): ~457 residual strings across 50 batches,
Korean drafts for swept keys, and a dashboard test-suite pass that is
still being stabilized (~283 failures under investigation — fake-timer
waitFor interaction, likely stale node_modules vs merged lockfile).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
108 lines
4.4 KiB
TypeScript
108 lines
4.4 KiB
TypeScript
import { useMemo } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import type { Agent } from "../api";
|
|
import "./AgentTokenStatsPanel.css";
|
|
|
|
interface AgentTokenStatsPanelProps {
|
|
agents: Agent[];
|
|
}
|
|
|
|
interface AgentTokenRow {
|
|
id: string;
|
|
name: string;
|
|
inputTokens: number;
|
|
outputTokens: number;
|
|
totalTokens: number;
|
|
}
|
|
|
|
function normalizeTokenCount(value: number | undefined): number {
|
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
}
|
|
|
|
function formatTokenCount(value: number): string {
|
|
return value.toLocaleString();
|
|
}
|
|
|
|
export function AgentTokenStatsPanel({ agents }: AgentTokenStatsPanelProps) {
|
|
const { t } = useTranslation("app");
|
|
const { rows, totalInputTokens, totalOutputTokens, totalTokens } = useMemo(() => {
|
|
const computedRows = agents
|
|
.map((agent): AgentTokenRow => {
|
|
const inputTokens = normalizeTokenCount(agent.totalInputTokens);
|
|
const outputTokens = normalizeTokenCount(agent.totalOutputTokens);
|
|
return {
|
|
id: agent.id,
|
|
name: agent.name,
|
|
inputTokens,
|
|
outputTokens,
|
|
totalTokens: inputTokens + outputTokens,
|
|
};
|
|
})
|
|
.sort((a, b) => b.totalTokens - a.totalTokens || a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
|
|
|
|
return {
|
|
rows: computedRows,
|
|
totalInputTokens: computedRows.reduce((sum, row) => sum + row.inputTokens, 0),
|
|
totalOutputTokens: computedRows.reduce((sum, row) => sum + row.outputTokens, 0),
|
|
totalTokens: computedRows.reduce((sum, row) => sum + row.totalTokens, 0),
|
|
};
|
|
}, [agents]);
|
|
|
|
const hasUsageData = totalTokens > 0;
|
|
|
|
return (
|
|
<section className="agent-token-stats-panel" aria-label={t("agents.tokenStatistics", "Agent token usage statistics")}>
|
|
<header className="agent-token-stats-panel__header">
|
|
<h3 className="agent-token-stats-panel__title">{t("agents.tokenUsageByAgent", "Token Usage by Agent")}</h3>
|
|
</header>
|
|
|
|
<div className="agent-token-stats-panel__totals" role="list" aria-label={t("agents.tokenUsageTotals", "Token usage totals")}>
|
|
<div className="agent-token-stats-panel__total-card" role="listitem">
|
|
<span className="agent-token-stats-panel__total-label">{t("agents.inputTokens", "Input Tokens")}</span>
|
|
<span className="agent-token-stats-panel__total-value">{formatTokenCount(totalInputTokens)}</span>
|
|
</div>
|
|
<div className="agent-token-stats-panel__total-card" role="listitem">
|
|
<span className="agent-token-stats-panel__total-label">{t("agents.outputTokens", "Output Tokens")}</span>
|
|
<span className="agent-token-stats-panel__total-value">{formatTokenCount(totalOutputTokens)}</span>
|
|
</div>
|
|
<div className="agent-token-stats-panel__total-card" role="listitem">
|
|
<span className="agent-token-stats-panel__total-label">{t("agents.combinedTokens", "Combined Tokens")}</span>
|
|
<span className="agent-token-stats-panel__total-value">{formatTokenCount(totalTokens)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{hasUsageData ? (
|
|
<div className="agent-token-stats-panel__table-wrapper">
|
|
<table className="agent-token-stats-panel__table">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">{t("agents.agent", "Agent")}</th>
|
|
<th scope="col">{t("agents.input", "Input")}</th>
|
|
<th scope="col">{t("agents.output", "Output")}</th>
|
|
<th scope="col">{t("agents.total", "Total")}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{rows.map((row) => (
|
|
<tr key={row.id}>
|
|
<th scope="row" className="agent-token-stats-panel__agent-cell">
|
|
<span className="agent-token-stats-panel__agent-name">{row.name}</span>
|
|
<span className="agent-token-stats-panel__agent-id">{row.id}</span>
|
|
</th>
|
|
<td>{formatTokenCount(row.inputTokens)}</td>
|
|
<td>{formatTokenCount(row.outputTokens)}</td>
|
|
<td className="agent-token-stats-panel__total-cell">{formatTokenCount(row.totalTokens)}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
) : (
|
|
<div className="agent-token-stats-panel__empty" role="status">
|
|
{t("agents.noTokenUsageYet", "No token usage recorded yet. Token totals appear here once agents run.")}
|
|
</div>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|