import { useEffect, useMemo, useState, type ReactNode } from "react"; import { adminOverview, getStatus, type AdminOverview, type SystemStatus } from "../api"; // 管理端「概览」= 系统控制塔:统筹全系统的吞吐 / 配置态 / 健康,而非某个账号的个人工作台。 // 数据来自 /api/v1/admin/overview(系统级聚合)+ /api/v1/admin/status(实时探活)。 const STATUS_STYLE: Record = { done: { label: "完成", color: "#10b981" }, running: { label: "运行中", color: "#06b6d4" }, failed: { label: "失败", color: "#f43f5e" }, timeout: { label: "超时", color: "#f59e0b" }, rejected: { label: "已拒绝", color: "#a78bfa" }, waiting: { label: "待审批", color: "#eab308" }, }; const statusStyle = (s: string) => STATUS_STYLE[s] ?? { label: s, color: "#94a3b8" }; // 模型熔断态 → 展示样式。 const BREAKER_STYLE: Record = { closed: { label: "在线", dot: "bg-emerald-500", text: "text-emerald-600" }, open: { label: "熔断中", dot: "bg-rose-500", text: "text-rose-500" }, "half-open": { label: "半开探测", dot: "bg-amber-500", text: "text-amber-600" }, single: { label: "单点", dot: "bg-gray-300", text: "text-gray-400" }, }; const breakerStyle = (s: string) => BREAKER_STYLE[s] ?? { label: s, dot: "bg-gray-300", text: "text-gray-400" }; function last7DaysTrend(trend: { key: string; count: number }[]): { key: string; count: number }[] { const byKey = new Map(trend.map((d) => [d.key, d.count])); const out: { key: string; count: number }[] = []; const now = new Date(); for (let i = 6; i >= 0; i--) { const d = new Date(now); d.setDate(now.getDate() - i); const key = `${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; out.push({ key, count: byKey.get(key) ?? 0 }); } return out; } export function DashboardPage() { const [ov, setOv] = useState(null); const [status, setStatus] = useState(null); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [updatedAt, setUpdatedAt] = useState(null); const [err, setErr] = useState(""); const load = async () => { setRefreshing(true); try { const [o, s] = await Promise.all([adminOverview(), getStatus()]); setOv(o); setStatus(s); setUpdatedAt(new Date()); setErr(""); } catch (er) { setErr((er as Error).message); } finally { setLoading(false); setRefreshing(false); } }; useEffect(() => { void load(); const t = setInterval(() => void load(), 30000); return () => clearInterval(t); }, []); const trend = useMemo(() => (ov ? last7DaysTrend(ov.task_trend) : []), [ov]); if (loading) return
加载系统概览中…
; if (err) return
概览加载失败:{err}
; if (!ov || !status) return null; const maxTrend = Math.max(1, ...trend.map((d) => d.count)); const W = 560; const H = 120; const pad = 20; const pts = trend.map((d, i) => ({ x: pad + (i * (W - pad * 2)) / Math.max(1, trend.length - 1), y: H - pad - (d.count * (H - pad * 2)) / maxTrend, })); const dPath = pts.reduce((p, pt, i) => p + `${i === 0 ? "M" : "L"} ${pt.x.toFixed(1)} ${pt.y.toFixed(1)} `, ""); const areaPath = pts.length ? dPath + `L ${pts[pts.length - 1].x} ${H - pad} L ${pts[0].x} ${H - pad} Z` : ""; const totalStatus = ov.status_count.reduce((s, d) => s + d.count, 0) || 1; const upSvc = status.services.filter((s) => s.up).length; const svcTotal = status.services.length; const goTools = status.tools.find((t) => t.server === "mcp-go")?.tools?.length ?? 0; const pyTools = status.tools.find((t) => t.server === "mcp-py")?.tools?.length ?? 0; const infraDown = status.infra.filter((i) => !i.up).length; const m = ov.models; return (
{/* 顶栏:更新时间 + 刷新 */}
{updatedAt && 数据更新于 {updatedAt.toLocaleTimeString("zh-CN", { hour12: false })} · 每 30s 自动刷新 · 全平台口径}
{/* A. 平台总览(全局口径) */}
今日 +{ov.tasks_today}(全系统)} /> {ov.eval_avg.toFixed(2)} / 1.0 } sub={{ov.eval_count} 条评测 · 全平台均值} /> 用户 · {ov.kb_count} 库 / {ov.kb_docs} 文档} /> 基建 + 应用全绿 · {goTools + pyTools} 工具 ) : ( {infraDown > 0 ? `${infraDown} 项基建异常` : "有服务离线"} ) } />
{/* B. 控制面配置态(管理端独有) */}

模型路由 & Fallback

控制面
{m.active_chat ? ( {m.active_chat} ) : ( 未配置 )} {m.fallbacks === 0 ? 无备用(单点) : {m.fallbacks} 个备用模型} {m.active_embedding ? {m.active_embedding} : 未配置} chat {m.chat_count} · embedding {m.embedding_count}
{/* 运行时 failover/熔断态(来自 dispatcher) */} {m.health && m.health.length > 0 && (
运行时链路态(实时)
{m.health.map((h, i) => { const st = breakerStyle(h.state); return (
{h.role === "primary" ? "主" : "备"} {h.model} {st.label} {h.fails > 0 && h.state !== "closed" && 失败 {h.fails}}
); })}
)}

提示词控制面

控制面
{ov.prompts.managed}
受管提示词
{ov.prompts.overrides}
已激活热覆盖
{Math.max(0, ov.prompts.managed - ov.prompts.overrides)}
用代码默认

激活某版经 NATS 热下发各服务,不重启即生效。明细见「提示词」页。

{/* C. 全局任务吞吐 */}

全平台任务吞吐

近 7 天调度中心处理的任务总数(所有租户/用户)

真实数据
{areaPath && } {dPath && } {pts.map((p, i) => ( {`${trend[i].key}: ${trend[i].count} 任务`} ))}
{trend.map((d) => ( {d.key} ))}

任务终态分布

近 7 天全局终态占比

{ov.status_count.length === 0 &&
暂无任务
} {ov.status_count .slice() .sort((a, b) => b.count - a.count) .map((d) => { const st = statusStyle(d.key); const pct = (d.count / totalStatus) * 100; return (
{st.label} {d.count} · {pct.toFixed(0)}%
); })}
{/* D. 系统健康拓扑 */}

系统健康拓扑

基建 + 应用服务 + MCP 工具注册(NATS 实时探活)

实时
({ name: i.name, up: i.up, detail: i.detail }))} /> ({ name: s.name, up: s.up, detail: s.detail }))} /> ({ name: t.server, up: t.up, detail: `${t.tools?.length ?? 0} 个工具` }))} />
); } function Row({ label, children }: { label: string; children: ReactNode }) { return (
{label} {children}
); } function HealthGroup({ title, items }: { title: string; items: { name: string; up: boolean; detail?: string }[] }) { return (
{title}
{items.map((it) => (
{it.name} {it.up ? it.detail || "在线" : "离线"}
))}
); } type Tone = "violet" | "emerald" | "cyan" | "amber" | "rose"; const TONE: Record = { violet: "bg-violet-50 text-violet-600", emerald: "bg-emerald-50 text-emerald-600", cyan: "bg-cyan-50 text-cyan-600", amber: "bg-amber-50 text-amber-600", rose: "bg-rose-50 text-rose-600", }; function MetricCard({ label, value, sub, icon, tone }: { label: string; value: ReactNode; sub: ReactNode; icon: IconName; tone: Tone }) { return (
{label}

{value}

{sub}

); } type IconName = "tasks" | "award" | "pulse" | "users"; const PATHS: Record = { tasks: "M9 11l3 3L22 4 M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11", award: "M12 15a7 7 0 1 0 0-14 7 7 0 0 0 0 14z M8.21 13.89 7 23l5-3 5 3-1.21-9.12", pulse: "M22 12h-4l-3 9L9 3l-3 9H2", users: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2 M9 7a4 4 0 1 0 0-8 4 4 0 0 0 0 8z M22 21v-2a4 4 0 0 0-3-3.87 M16 3.13a4 4 0 0 1 0 7.75", }; function Icon({ name, className }: { name: IconName; className?: string }) { return ( ); }