Files
taiji-pda-v0/components/dashboard-overview.tsx
T

381 lines
14 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Activity, Zap, GitBranch, Boxes } from "lucide-react"
import { useLanguage } from "@/contexts/language-context"
import { TaijiAPIClient } from "@/lib/api-client"
import {
ResponsiveContainer,
LineChart,
Line,
PieChart,
Pie,
Cell,
XAxis,
YAxis,
Tooltip,
AreaChart,
Area,
} from "recharts"
// 默认数据(加载时显示)
const defaultEuConsumptionData = [
{ time: "00:00", value: 0 },
{ time: "04:00", value: 0 },
{ time: "08:00", value: 0 },
{ time: "12:00", value: 0 },
{ time: "16:00", value: 0 },
{ time: "20:00", value: 0 },
{ time: "24:00", value: 0 },
]
const defaultModelDistribution = [
{ name: "GPT-4o", value: 0, color: "oklch(0.60 0.20 264)" },
{ name: "Claude 3.5", value: 0, color: "oklch(0.65 0.16 220)" },
{ name: "Llama-3", value: 0, color: "oklch(0.55 0.18 300)" },
]
const defaultApiCallsData = [
{ time: "Mon", calls: 0 },
{ time: "Tue", calls: 0 },
{ time: "Wed", calls: 0 },
{ time: "Thu", calls: 0 },
{ time: "Fri", calls: 0 },
{ time: "Sat", calls: 0 },
{ time: "Sun", calls: 0 },
]
export function DashboardOverview() {
const { t } = useLanguage()
const [loading, setLoading] = useState(true)
const [stats, setStats] = useState({
activeAgents: 0,
totalRequests: 0,
euBalance: 0,
systemHealth: 0,
})
const [euConsumptionData, setEuConsumptionData] = useState(defaultEuConsumptionData)
const [modelDistribution, setModelDistribution] = useState(defaultModelDistribution)
const [apiCallsData, setApiCallsData] = useState(defaultApiCallsData)
const [systemComponents, setSystemComponents] = useState<Array<{ name: string; status: string; latency: string }>>([])
useEffect(() => {
loadDashboardData()
}, [])
const loadDashboardData = async () => {
try {
setLoading(true)
// 并行加载数据
const [dashboardStats, monitoringDashboard, billingBalance] = await Promise.allSettled([
TaijiAPIClient.getUserDashboardStats(),
TaijiAPIClient.getMonitoringDashboard(),
TaijiAPIClient.getBillingBalance(),
])
// 处理仪表板统计
if (dashboardStats.status === "fulfilled" && dashboardStats.value?.success) {
const data = dashboardStats.value.data
setStats({
activeAgents: data.activeAgents || 0,
totalRequests: data.totalRequests || 0,
euBalance: data.euBalance || 0,
systemHealth: data.systemHealth || 0,
})
}
// 处理监控数据
if (monitoringDashboard.status === "fulfilled" && monitoringDashboard.value) {
const monitoring = monitoringDashboard.value
// 可以从监控数据中提取系统组件状态
if (monitoring.health?.services) {
const components = Object.entries(monitoring.health.services).map(([name, status]) => ({
name,
status: status === "healthy" ? t("正常运行", "Operational") : t("异常", "Error"),
latency: "0ms", // 可以从metrics中获取
}))
setSystemComponents(components)
}
}
// 处理余额数据
if (billingBalance.status === "fulfilled" && billingBalance.value?.success) {
const balance = billingBalance.value.data
setStats((prev) => ({
...prev,
euBalance: balance.euBalance || balance.balance || 0,
}))
}
// 加载趋势数据
try {
const trends = await TaijiAPIClient.getMonitoringTrends({
metric: "eu_consumption",
period: "24h",
interval: "1h",
})
if (trends?.data) {
// 转换趋势数据为图表格式
const formatted = trends.data.map((item: any, index: number) => ({
time: `${index * 4}:00`,
value: item.count || 0,
}))
if (formatted.length > 0) {
setEuConsumptionData(formatted)
}
}
} catch (error) {
console.error("Failed to load trends:", error)
}
} catch (error) {
console.error("Failed to load dashboard data:", error)
} finally {
setLoading(false)
}
}
return (
<div className="space-y-6">
{/* Hero Stats */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card className="bg-card">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t("活跃代理", "Active Agents")}
</CardTitle>
<Boxes className="h-4 w-4 text-primary" />
</CardHeader>
<CardContent>
<div className="text-3xl font-bold">
{loading ? (
<span className="inline-block h-8 w-16 animate-pulse bg-muted rounded" />
) : (
stats.activeAgents.toLocaleString()
)}
</div>
<p className="text-xs text-muted-foreground mt-1">
{loading ? "" : t("活跃代理", "Active Agents")}
</p>
</CardContent>
</Card>
<Card className="bg-card">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t("全局 API 调用", "Global API Calls")}
</CardTitle>
<Activity className="h-4 w-4 text-secondary" />
</CardHeader>
<CardContent>
<div className="text-3xl font-bold">
{loading ? (
<span className="inline-block h-8 w-20 animate-pulse bg-muted rounded" />
) : (
stats.totalRequests > 1000
? `${(stats.totalRequests / 1000).toFixed(1)}K`
: stats.totalRequests.toLocaleString()
)}
</div>
<p className="text-xs text-muted-foreground mt-1">
{loading ? "" : t("总请求数", "Total Requests")}
</p>
</CardContent>
</Card>
<Card className="bg-card">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t("EU 余额", "EU Balance")}
</CardTitle>
<Zap className="h-4 w-4 text-chart-4" />
</CardHeader>
<CardContent>
<div className="text-3xl font-bold">
{loading ? (
<span className="inline-block h-8 w-20 animate-pulse bg-muted rounded" />
) : (
stats.euBalance.toLocaleString()
)}
</div>
<p className="text-xs text-muted-foreground mt-1">
{loading ? "" : t("可用余额", "Available Balance")}
</p>
</CardContent>
</Card>
<Card className="bg-card">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t("系统健康度", "System Health")}
</CardTitle>
<GitBranch className="h-4 w-4 text-chart-2" />
</CardHeader>
<CardContent>
<div className="text-3xl font-bold">
{loading ? (
<span className="inline-block h-8 w-12 animate-pulse bg-muted rounded" />
) : (
`${stats.systemHealth}%`
)}
</div>
<p className="text-xs text-muted-foreground mt-1">
{loading ? "" : t("系统状态", "System Status")}
</p>
</CardContent>
</Card>
</div>
{/* Charts Row */}
<div className="grid gap-4 lg:grid-cols-2">
{/* EU Consumption Chart */}
<Card className="bg-card">
<CardHeader>
<CardTitle className="text-base">{t("执行单元 (EU) 消耗", "Execution Unit (EU) Consumption")}</CardTitle>
<p className="text-xs text-muted-foreground">{t("过去 24 小时", "Last 24 hours")}</p>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={250}>
<AreaChart data={euConsumptionData}>
<defs>
<linearGradient id="colorEU" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="oklch(0.60 0.20 264)" stopOpacity={0.3} />
<stop offset="95%" stopColor="oklch(0.60 0.20 264)" stopOpacity={0} />
</linearGradient>
</defs>
<XAxis dataKey="time" stroke="oklch(0.45 0 0)" fontSize={12} tickLine={false} />
<YAxis stroke="oklch(0.45 0 0)" fontSize={12} tickLine={false} />
<Tooltip
contentStyle={{
backgroundColor: "oklch(0.15 0 0)",
border: "1px solid oklch(0.22 0 0)",
borderRadius: "8px",
}}
/>
<Area
type="monotone"
dataKey="value"
stroke="oklch(0.60 0.20 264)"
strokeWidth={2}
fill="url(#colorEU)"
/>
</AreaChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* Model Distribution Chart */}
<Card className="bg-card">
<CardHeader>
<CardTitle className="text-base">{t("模型分布", "Model Distribution")}</CardTitle>
<p className="text-xs text-muted-foreground">{t("当前推理负载", "Current inference load")}</p>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={250}>
<PieChart>
<Pie
data={modelDistribution}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={90}
paddingAngle={2}
dataKey="value"
>
{modelDistribution.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip
contentStyle={{
backgroundColor: "oklch(0.15 0 0)",
border: "1px solid oklch(0.22 0 0)",
borderRadius: "8px",
}}
/>
</PieChart>
</ResponsiveContainer>
<div className="mt-4 grid grid-cols-3 gap-4">
{modelDistribution.map((model) => (
<div key={model.name} className="flex flex-col items-center">
<div className="flex items-center gap-2">
<div className="h-3 w-3 rounded-full" style={{ backgroundColor: model.color }} />
<span className="text-xs font-medium">{model.value}%</span>
</div>
<span className="text-xs text-muted-foreground mt-1">{model.name}</span>
</div>
))}
</div>
</CardContent>
</Card>
</div>
{/* API Calls Chart */}
<Card className="bg-card">
<CardHeader>
<CardTitle className="text-base">{t("每周 API 调用", "Weekly API Calls")}</CardTitle>
<p className="text-xs text-muted-foreground">{t("每日总调用次数", "Total calls per day")}</p>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={200}>
<LineChart data={apiCallsData}>
<XAxis dataKey="time" stroke="oklch(0.45 0 0)" fontSize={12} tickLine={false} />
<YAxis stroke="oklch(0.45 0 0)" fontSize={12} tickLine={false} />
<Tooltip
contentStyle={{
backgroundColor: "oklch(0.15 0 0)",
border: "1px solid oklch(0.22 0 0)",
borderRadius: "8px",
}}
/>
<Line
type="monotone"
dataKey="calls"
stroke="oklch(0.65 0.16 220)"
strokeWidth={2}
dot={{ fill: "oklch(0.65 0.16 220)", r: 4 }}
/>
</LineChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* System Status */}
<Card className="bg-card">
<CardHeader>
<CardTitle className="text-base">{t("系统组件", "System Components")}</CardTitle>
<p className="text-xs text-muted-foreground">{t("实时状态监控", "Real-time status monitoring")}</p>
</CardHeader>
<CardContent>
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
{(systemComponents.length > 0
? systemComponents
: [
{ name: "MCP Server", status: t("加载中", "Loading"), latency: "-" },
{ name: "Data Ingestion", status: t("加载中", "Loading"), latency: "-" },
{ name: "API Gateway", status: t("加载中", "Loading"), latency: "-" },
]
).map((component) => (
<div
key={component.name}
className="flex items-center justify-between rounded-lg border border-border bg-muted/30 p-3"
>
<div className="flex items-center gap-3">
<div className="h-2 w-2 rounded-full bg-green-500" />
<div>
<p className="text-sm font-medium">{component.name}</p>
<p className="text-xs text-muted-foreground">{component.latency}</p>
</div>
</div>
<span className="text-xs text-green-500">{component.status}</span>
</div>
))}
</div>
</CardContent>
</Card>
</div>
)
}