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
+9 -10
View File
@@ -1,11 +1,15 @@
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { UsageSummaryCards } from "./UsageSummaryCards";
import { UsageHero } from "./UsageHero";
import { UsageTrendChart } from "./UsageTrendChart";
import { RequestLogTable } from "./RequestLogTable";
import { ProviderStatsTable } from "./ProviderStatsTable";
import { ModelStatsTable } from "./ModelStatsTable";
import type { AppTypeFilter, UsageRangeSelection } from "@/types/usage";
import {
KNOWN_APP_TYPES,
type AppTypeFilter,
type UsageRangeSelection,
} from "@/types/usage";
import { motion } from "framer-motion";
import {
BarChart3,
@@ -30,12 +34,7 @@ import { getUsageRangePresetLabel, resolveUsageRange } from "@/lib/usageRange";
import { UsageDateRangePicker } from "./UsageDateRangePicker";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
const APP_FILTER_OPTIONS: AppTypeFilter[] = [
"all",
"claude",
"codex",
"gemini",
];
const APP_FILTER_OPTIONS: AppTypeFilter[] = ["all", ...KNOWN_APP_TYPES];
export function UsageDashboard() {
const { t, i18n } = useTranslation();
@@ -127,9 +126,9 @@ export function UsageDashboard() {
</div>
</div>
<UsageSummaryCards
<UsageHero
range={range}
appType={appType}
appType={appType === "all" ? undefined : appType}
refreshIntervalMs={refreshIntervalMs}
/>