feat(usage): filter-driven Hero with cache-normalized totals

- Normalize OpenAI/Gemini input_tokens semantics in SQL via the new
  fresh_input_sql helper (cache_read subtracted at query time, no data
  migration). Recovers correct cache hit rates for Codex/Gemini.
- Add get_usage_summary_by_app endpoint for per-app split (single
  UNION ALL + GROUP BY, avoids N+1).
- Replace UsageSummaryCards + AppBreakdownRail with a single
  filter-driven UsageHero card; clicking a filter button now truly
  changes the displayed numbers and the title accent color.
- Tighten KNOWN_APP_TYPES to the 3 app_types whose token data is
  reliably collected (claude/codex/gemini); hide claude-desktop,
  hermes, opencode, openclaw filter buttons and i18n keys.
- Flag cache_creation as N/A for OpenAI-style protocols (Codex,
  Gemini); show a "partial" tooltip when the All view mixes both
  protocol families.
This commit is contained in:
Jason
2026-05-13 10:27:29 +08:00
parent c12364a940
commit edf28b6422
17 changed files with 931 additions and 219 deletions
+12 -4
View File
@@ -6,6 +6,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { useRequestDetail } from "@/lib/query/usage";
import { getFreshInputTokens } from "@/types/usage";
interface RequestDetailPanelProps {
requestId: string;
@@ -50,6 +51,9 @@ export function RequestDetailPanel({
);
}
const freshInput = getFreshInputTokens(request);
const isCacheInclusive = request.inputTokens !== freshInput;
return (
<Dialog open onOpenChange={onClose}>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
@@ -135,7 +139,13 @@ export function RequestDetailPanel({
{t("usage.inputTokens", "输入 Tokens")}
</dt>
<dd className="font-mono">
{request.inputTokens.toLocaleString()}
{freshInput.toLocaleString()}
{isCacheInclusive && (
<span className="ml-2 text-xs text-muted-foreground/70 font-normal">
({t("usage.rawInputLabel", "原始")}:{" "}
{request.inputTokens.toLocaleString()})
</span>
)}
</dd>
</div>
<div>
@@ -167,9 +177,7 @@ export function RequestDetailPanel({
{t("usage.totalTokens", "总计")}
</dt>
<dd className="text-lg font-semibold">
{(
request.inputTokens + request.outputTokens
).toLocaleString()}
{(freshInput + request.outputTokens).toLocaleString()}
</dd>
</div>
</dl>