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
+8
View File
@@ -1,6 +1,7 @@
import { invoke } from "@tauri-apps/api/core";
import type {
UsageSummary,
UsageSummaryByApp,
DailyStats,
ProviderStats,
ModelStats,
@@ -55,6 +56,13 @@ export const usageApi = {
return invoke("get_usage_summary", { startDate, endDate, appType });
},
getUsageSummaryByApp: async (
startDate?: number,
endDate?: number,
): Promise<UsageSummaryByApp[]> => {
return invoke("get_usage_summary_by_app", { startDate, endDate });
},
getUsageTrends: async (
startDate?: number,
endDate?: number,
+31
View File
@@ -45,6 +45,18 @@ export const usageKeys = {
customEndDate ?? 0,
appType ?? "all",
] as const,
summaryByApp: (
preset: UsageRangeSelection["preset"],
customStartDate: number | undefined,
customEndDate: number | undefined,
) =>
[
...usageKeys.all,
"summary-by-app",
preset,
customStartDate ?? 0,
customEndDate ?? 0,
] as const,
trends: (
preset: UsageRangeSelection["preset"],
customStartDate: number | undefined,
@@ -133,6 +145,25 @@ export function useUsageSummary(
});
}
export function useUsageSummaryByApp(
range: UsageRangeSelection,
options?: UsageQueryOptions,
) {
return useQuery({
queryKey: usageKeys.summaryByApp(
range.preset,
range.customStartDate,
range.customEndDate,
),
queryFn: () => {
const { startDate, endDate } = resolveUsageRange(range);
return usageApi.getUsageSummaryByApp(startDate, endDate);
},
refetchInterval: options?.refetchInterval ?? DEFAULT_REFETCH_INTERVAL_MS,
refetchIntervalInBackground: options?.refetchIntervalInBackground ?? false,
});
}
export function useUsageTrends(
range: UsageRangeSelection,
appType?: string,