'use client' import { useState } from 'react' import useSWR from 'swr' import { Zap, Monitor, Smartphone } from 'lucide-react' import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, Legend, } from 'recharts' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { ErrorBanner, EmptyState } from '@/components/ui/state' import { TableSkeleton, ChartSkeleton } from '@/components/ui/skeleton' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Badge } from '@/components/ui/badge' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { api } from '@/lib/api-client' import { formatNumber } from '@/lib/utils' import type { UsageRecord, UsageByModel, ModelUsageStat, DailyUsageStat } from '@/lib/types' export default function UsagePage() { const [days, setDays] = useState(7) const [activeTab, setActiveTab] = useState('relay') const [error, setError] = useState('') // 4 parallel SWR calls — each loads independently const { data: dailyData = [], isLoading: dailyLoading } = useSWR( ['usage.daily', days], () => api.usage.daily({ days }) ) const { data: modelData = [], isLoading: modelLoading } = useSWR( ['usage.byModel', days], () => api.usage.byModel({ days }) ) const { data: telemetryModels = [] } = useSWR( ['telemetry.modelStats'], () => api.telemetry.modelStats() ) const { data: telemetryDaily = [] } = useSWR( ['telemetry.dailyStats', days], () => api.telemetry.dailyStats({ days }) ) const relayLoading = dailyLoading || modelLoading const telemetryLoading = !telemetryModels.length && !telemetryDaily.length && (dailyLoading || modelLoading) // === Relay 用量图表数据 === const relayLineData = dailyData.map((r) => ({ day: r.day.slice(5), Input: r.input_tokens, Output: r.output_tokens, })) const relayBarData = modelData.map((r) => ({ model: r.model_id, 请求量: r.count, Input: r.input_tokens, Output: r.output_tokens, })) const relayTotalInput = dailyData.reduce((s, r) => s + r.input_tokens, 0) const relayTotalOutput = dailyData.reduce((s, r) => s + r.output_tokens, 0) const relayTotalRequests = dailyData.reduce((s, r) => s + r.count, 0) // === 遥测图表数据 === const telemetryLineData = telemetryDaily.map((r) => ({ day: r.day.slice(5), Input: r.input_tokens, Output: r.output_tokens, 设备数: r.unique_devices, })) const telemetryTotalInput = telemetryDaily.reduce((s, r) => s + r.input_tokens, 0) const telemetryTotalOutput = telemetryDaily.reduce((s, r) => s + r.output_tokens, 0) const telemetryTotalRequests = telemetryDaily.reduce((s, r) => s + r.request_count, 0) // === 合计 === const totalInput = relayTotalInput + telemetryTotalInput const totalOutput = relayTotalOutput + telemetryTotalOutput const totalRequests = relayTotalRequests + telemetryTotalRequests return (
{error && setError('')} />} {/* 时间范围 */}
时间范围:
{/* 汇总统计 — render immediately, use 0 while loading */}

总请求数

{formatNumber(totalRequests)}

总 Input Tokens

{formatNumber(totalInput)}

总 Output Tokens

{formatNumber(totalOutput)}

中转请求

{formatNumber(relayTotalRequests)}

桌面端调用

{formatNumber(telemetryTotalRequests)}

{/* Tab 切换 */} 中转用量 桌面端遥测 {/* Relay 用量 Tab */} {relayLoading ? ( <> ) : ( <> 中转 Token 用量趋势 {relayLineData.length > 0 ? ( ) : ( )} 中转按模型分布 {relayBarData.length > 0 ? ( ) : ( )} )} {/* 遥测 Tab */} {telemetryLoading ? ( <> ) : ( <> 桌面端 Token 用量趋势 {telemetryLineData.length > 0 ? ( ) : ( )} 桌面端按模型统计 {telemetryModels.length > 0 ? ( 模型 请求数 Input Tokens Output Tokens 平均延迟 成功率 {telemetryModels.map((stat) => ( {stat.model_id} {formatNumber(stat.request_count)} {formatNumber(stat.input_tokens)} {formatNumber(stat.output_tokens)} {stat.avg_latency_ms !== null ? `${Math.round(stat.avg_latency_ms)}ms` : '-'} = 0.95 ? 'default' : 'destructive'}> {(stat.success_rate * 100).toFixed(1)}% ))}
) : ( )}
)}
) }