Files
fusion/packages/dashboard/app/components/AgentTokenStatsPanel.tsx
gsxdsm 1e49494bac feat(i18n): full-sweep string migration — 5,930 keys across 5 locales (#1352)
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>
2026-06-03 19:06:53 -07:00

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>
);
}