'use client' import { useEffect, useMemo, useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' import { Activity, BarChart3, Bot, RefreshCcw } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { AdminUnauthorizedError, API_BASE, adminFetchJson } from '@/lib/admin-api' interface Provider { id: number name: string model_id: string base_url: string | null is_active: boolean is_default: boolean } interface HourlyStat { hour: number request_count: number input_tokens: number output_tokens: number cached_count: number error_count: number } interface DailyStats { date: string request_count: number input_tokens: number output_tokens: number cached_count: number error_count: number hourly: HourlyStat[] } interface RealtimeStats { rpm: number tpm: number } function formatNumber(value: number) { return new Intl.NumberFormat('zh-CN').format(value) } function formatPercent(value: number) { return `${(value * 100).toFixed(1)}%` } export default function AdminPage() { const router = useRouter() const [providers, setProviders] = useState([]) const [providerId, setProviderId] = useState('') const [daily, setDaily] = useState(null) const [realtime, setRealtime] = useState({ rpm: 0, tpm: 0 }) const [healthOk, setHealthOk] = useState(null) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState('') const selectedProvider = useMemo( () => providers.find((p) => String(p.id) === providerId) || null, [providers, providerId] ) const cacheHitRate = useMemo(() => { if (!daily || daily.request_count <= 0) return 0 return daily.cached_count / daily.request_count }, [daily]) const errorRate = useMemo(() => { if (!daily || daily.request_count <= 0) return 0 return daily.error_count / daily.request_count }, [daily]) const totalTokens = useMemo(() => { if (!daily) return 0 return daily.input_tokens + daily.output_tokens }, [daily]) const fetchProviders = async () => { setIsLoading(true) setError('') try { const data = await adminFetchJson('/api/v1/admin/providers') setProviders(data) const defaultOne = data.find((p) => p.is_default) || data[0] || null if (defaultOne) setProviderId(String(defaultOne.id)) } catch (err: any) { if (err instanceof AdminUnauthorizedError) { router.replace('/login') return } setError(err?.message || '加载失败') } finally { setIsLoading(false) } } const fetchDaily = async (id: string) => { try { const data = await adminFetchJson(`/api/v1/admin/stats/daily/${id}`) setDaily(data) } catch (err: any) { if (err instanceof AdminUnauthorizedError) { router.replace('/login') return } setError(err?.message || '加载统计失败') } } const fetchRealtime = async (id: string) => { try { const data = await adminFetchJson(`/api/v1/admin/stats/realtime/${id}`) setRealtime(data) } catch (err: any) { if (err instanceof AdminUnauthorizedError) { router.replace('/login') return } } } const fetchHealth = async () => { try { const res = await fetch(`${API_BASE}/health`) setHealthOk(res.ok) } catch { setHealthOk(false) } } useEffect(() => { fetchProviders() fetchHealth() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { if (!providerId) return fetchDaily(providerId) fetchRealtime(providerId) const timer = setInterval(() => fetchRealtime(providerId), 5000) return () => clearInterval(timer) // eslint-disable-next-line react-hooks/exhaustive-deps }, [providerId]) return (

概览

关键指标、Provider 状态与系统健康检查

{error && (
{error}
)}
当前 Provider 选择一个 Provider 查看实时与今日指标
API 健康: {healthOk === null ? ( 未知 ) : healthOk ? ( OK ) : ( 异常 )} {selectedProvider && ( <> 状态: {selectedProvider.is_active ? ( 启用 ) : ( 禁用 )} {selectedProvider.is_default && 默认} )}
RPM(每分钟请求) {formatNumber(realtime.rpm)} TPM(每分钟 Token) {formatNumber(realtime.tpm)} 今日请求 {daily ? formatNumber(daily.request_count) : '--'} 今日 Token {daily ? formatNumber(totalTokens) : '--'}
缓存命中率 {daily ? formatPercent(cacheHitRate) : '--'} 命中 {daily ? formatNumber(daily.cached_count) : '--'} 次 错误率 {daily ? formatPercent(errorRate) : '--'} 错误 {daily ? formatNumber(daily.error_count) : '--'} 次 输入/输出 Token {daily ? formatNumber(daily.input_tokens) : '--'} / {daily ? formatNumber(daily.output_tokens) : '--'}
Provider 列表 快速查看默认与启用状态
名称 模型 状态 {providers.length === 0 ? ( 暂无 Provider,请先去“AI 配置”添加。 ) : ( providers.map((p) => (
{p.name} {p.is_default && 默认}
{p.model_id} {p.is_active ? ( 启用 ) : ( 禁用 )}
)) )}
) }