import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { UsageHero } from "./UsageHero"; import { UsageTrendChart } from "./UsageTrendChart"; import { RequestLogTable } from "./RequestLogTable"; import { ProviderStatsTable } from "./ProviderStatsTable"; import { ModelStatsTable } from "./ModelStatsTable"; import { KNOWN_APP_TYPES, type AppTypeFilter, type UsageRangeSelection, } from "@/types/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 { useUsageEventBridge } from "@/hooks/useUsageEventBridge"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; import { PricingConfigPanel } from "@/components/usage/PricingConfigPanel"; import { cn } from "@/lib/utils"; import { getLocaleFromLanguage } from "./format"; 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", ...KNOWN_APP_TYPES]; 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); // 后端写入新日志时 emit `usage-log-recorded`,本 hook 立刻 invalidate 所有 // usage 查询,实现实时刷新(仅在 Dashboard 挂载时生效,离开页面自动取消监听) useUsageEventBridge(); 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 getUsageRangePresetLabel(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]); return (

{t("usage.title")}

{t("usage.subtitle")}

{APP_FILTER_OPTIONS.map((type) => ( ))}
setRange(nextRange)} />
{t("usage.requestLogs")} {t("usage.providerStats")} {t("usage.modelStats")}

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

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

); }