import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { UsageSummaryCards } from "./UsageSummaryCards"; import { UsageTrendChart } from "./UsageTrendChart"; import { RequestLogTable } from "./RequestLogTable"; import { ProviderStatsTable } from "./ProviderStatsTable"; import { ModelStatsTable } from "./ModelStatsTable"; import type { AppTypeFilter, UsageRangePreset, UsageRangeSelection, } from "@/types/usage"; import { useUsageSummary } from "@/lib/query/usage"; import { motion } from "framer-motion"; import { BarChart3, ListFilter, Activity, RefreshCw, Coins, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useQueryClient } from "@tanstack/react-query"; import { usageKeys } from "@/lib/query/usage"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; import { PricingConfigPanel } from "@/components/usage/PricingConfigPanel"; import { cn } from "@/lib/utils"; import { fmtUsd, getLocaleFromLanguage, parseFiniteNumber } from "./format"; import { 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 RANGE_PRESETS: UsageRangePreset[] = ["today", "1d", "7d", "14d", "30d"]; function getPresetLabel( preset: UsageRangePreset, t: (key: string, options?: { defaultValue?: string }) => string, ): string { switch (preset) { case "today": return t("usage.presetToday", { defaultValue: "当天" }); case "1d": return t("usage.preset1d", { defaultValue: "1d" }); case "7d": return t("usage.preset7d", { defaultValue: "7d" }); case "14d": return t("usage.preset14d", { defaultValue: "14d" }); case "30d": return t("usage.preset30d", { defaultValue: "30d" }); case "custom": return t("usage.customRange", { defaultValue: "日历筛选" }); } } export function UsageDashboard() { const { t, i18n } = useTranslation(); const queryClient = useQueryClient(); const [range, setRange] = useState({ preset: "today" }); const [appType, setAppType] = useState("all"); const [refreshIntervalMs, setRefreshIntervalMs] = useState(30000); const refreshIntervalOptionsMs = [0, 5000, 10000, 30000, 60000] as const; const changeRefreshInterval = () => { const currentIndex = refreshIntervalOptionsMs.indexOf( refreshIntervalMs as (typeof refreshIntervalOptionsMs)[number], ); const safeIndex = currentIndex >= 0 ? currentIndex : 3; const nextIndex = (safeIndex + 1) % refreshIntervalOptionsMs.length; const next = refreshIntervalOptionsMs[nextIndex]; setRefreshIntervalMs(next); queryClient.invalidateQueries({ queryKey: usageKeys.all }); }; const language = i18n.resolvedLanguage || i18n.language || "en"; const locale = getLocaleFromLanguage(language); const resolvedRange = useMemo(() => resolveUsageRange(range), [range]); const rangeLabel = useMemo(() => { if (range.preset !== "custom") { return getPresetLabel(range.preset, t); } return `${new Date(resolvedRange.startDate * 1000).toLocaleString(locale)} - ${new Date( resolvedRange.endDate * 1000, ).toLocaleString(locale)}`; }, [locale, range, resolvedRange.endDate, resolvedRange.startDate, t]); const { data: summaryData } = useUsageSummary(range, appType, { refetchInterval: refreshIntervalMs > 0 ? refreshIntervalMs : false, }); return (

{t("usage.title")}

{t("usage.subtitle")}

{APP_FILTER_OPTIONS.map((type) => ( ))}
{RANGE_PRESETS.map((preset) => ( ))} setRange(nextRange)} />
{rangeLabel} | {(summaryData?.totalRequests ?? 0).toLocaleString()}{" "} {t("usage.requestsLabel")} | {fmtUsd(parseFiniteNumber(summaryData?.totalCost) ?? 0, 4)}{" "} {t("usage.costLabel")}
{t("usage.requestLogs")} {t("usage.providerStats")} {t("usage.modelStats")}

{t("settings.advanced.pricing.title")}

{t("settings.advanced.pricing.description")}

); }