import { useAsyncData } from "../../../lib/useAsyncData"; import { adminLlmUsage } from "../../../lib/api"; import { Notice } from "../../components"; import { Surface, Tag } from "../../../components/primitives"; import { Icon } from "../../../components/Icon"; import { useAutoReload } from "../../hooks"; /** LLM 用量小卡(切片 U1,契约 v0.64):近 24h 的**调用数 / 输入 token / 缓存 * 命中率**,数源是只读端点 `GET /api/v1/admin/llm/usage`。 * * 三条纪律: * * 1. **失败显示错误,绝不显示 0** —— 端点没接通或报错时渲染错误态,而不是把 * 「读不到」画成「用量为零」(那正是这块卡要消灭的错觉)。 * 2. **缓存命中率只在有输入侧明细时才给数** —— 窗口内 `cacheHit + cacheMiss` * 为 0 时显示 `—` 并说明原因:后端口径是「分母 0 ⇒ 0」,但把「没有明细」 * 写成「0% 命中」会让人以为缓存坏了。 * 3. **取数与其它卡同一条路** —— 复用 `useAsyncData` 的加载/错误态与 * `useAutoReload` 的慢轮询,不另造一套状态机。 */ /** 命中率显示口径:有输入侧明细才给百分比,否则一个诚实的破折号。 */ function hitRateLabel(totals: { cacheHit: number; cacheMiss: number; hitRate: number }): string { if (totals.cacheHit + totals.cacheMiss <= 0) return "—"; return `${(totals.hitRate * 100).toFixed(1)}%`; } /** token 数的人类可读量级(K/M),保留一位小数;0 如实显示 0。 */ function formatTokens(value: number): string { if (!Number.isFinite(value) || value <= 0) return "0"; if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`; if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`; return String(value); } /** 时间窗 → 「MM-DD HH:mm ~ MM-DD HH:mm(UTC)」。解析不出来的原样显示。 */ function formatWindow(since: string, until: string): string { const fmt = (iso: string): string => { const d = new Date(iso); if (Number.isNaN(d.getTime())) return iso; const pad = (n: number) => String(n).padStart(2, "0"); return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`; }; return `${fmt(since)} ~ ${fmt(until)}(UTC)`; } /** 一格指标:标签 + 数值 + 可选补充说明。 */ function UsageCell({ label, value, hint }: { label: string; value: string; hint: string }): React.ReactNode { return (
加载中…
窗口 {formatWindow(window.since, window.until)} {window.purpose ? ` · 用途过滤 ${window.purpose}` : ""} {usage.data.byModel.length > 0 ? ` · 模型 ${usage.data.byModel.map((m) => `${m.model} ${m.calls} 次`).join(",")}` : ""}