import { useCallback, useEffect, useRef, useState } from "react"; import { get, post } from "../api"; import { fmtCost, fmtPercent, useT, type Key } from "../i18n"; import { navigate } from "../router"; import { useStore } from "../store"; import { Icon } from "./Icon"; import { Bar, type Plan } from "./Budget"; type Level = "ok" | "warn" | "err" | "off"; interface ProviderState { state: "ok" | "auth" | "down" | "unknown"; latency_ms?: number; checked_at?: number; error?: string | null; models: number; plan?: Plan | null; } interface McpEntry { state: "connected" | "connecting" | "failed"; tools?: number; error?: string | null; } interface Meter { key: string; provider: string; kind: "total" | "provider" | "images" | "5h" | "week" | "month"; used: number; limit: number; ratio: number; } interface Overview { version: string; providers: Record; mcp: Record; /** Budgets and plan windows (admins only). */ budgets?: Meter[]; currency?: string; } const meterLevel = (m: Meter): Level => (m.ratio >= 1 ? "err" : m.ratio >= 0.8 ? "warn" : "ok"); const POLL_MS = 30_000; /** A dropped connection shows yellow (reconnecting) for this long, then red. */ const RECONNECT_GRACE_MS = 10_000; /** Combined level of a group: all fine → ok, some broken → warn, none working → err. */ function combine(levels: Level[]): Level { const active = levels.filter((l) => l !== "off"); if (active.length === 0) return "off"; if (active.every((l) => l === "ok")) return "ok"; if (active.some((l) => l === "ok" || l === "warn")) return "warn"; return "err"; } /** The fullest window of a subscription plan (5 hours, week, month). */ const fullest = (plan?: Plan | null) => plan?.windows.reduce((a, b) => (b.ratio > a.ratio ? b : a)); const providerLevel = (p: ProviderState): Level => { const health: Level = p.state === "ok" ? "ok" : p.state === "unknown" ? "warn" : "err"; const w = fullest(p.plan); // A used-up plan blocks requests just like a rejected key. const plan: Level = !w ? "ok" : w.ratio >= 1 ? "err" : w.ratio >= 0.8 ? "warn" : "ok"; return health === "err" || plan === "err" ? "err" : health === "warn" || plan === "warn" ? "warn" : "ok"; }; const mcpLevel = (m: McpEntry): Level => (m.state === "connected" ? "ok" : m.state === "connecting" ? "warn" : "err"); function Led({ level, title }: { level: Level; title?: string }) { return ; } /** Three status LEDs (server, models, MCP) in the sidebar; a click opens the details. */ export function StatusLeds() { const t = useT(); const connected = useStore((s) => s.connected); const admin = useStore((s) => s.me?.role === "admin"); const [data, setData] = useState(); const [open, setOpen] = useState(false); const [checking, setChecking] = useState(false); const [lostAt, setLostAt] = useState(); const [pos, setPos] = useState<{ left: number; top: number }>(); const [, tick] = useState(0); const root = useRef(null); const load = useCallback(() => get("/status").then(setData).catch(() => {}), []); useEffect(() => { void load(); const id = setInterval(load, POLL_MS); return () => clearInterval(id); }, [load]); // Track when the event connection dropped (yellow while reconnecting, red after a while). useEffect(() => { if (connected) { setLostAt(undefined); void load(); return; } setLostAt((v) => v ?? Date.now()); const id = setTimeout(() => tick((n) => n + 1), RECONNECT_GRACE_MS + 100); return () => clearTimeout(id); }, [connected, load]); // Close on outside click and Escape. useEffect(() => { if (!open) return; const onDown = (e: MouseEvent) => !root.current?.contains(e.target as Node) && setOpen(false); const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false); document.addEventListener("mousedown", onDown); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey); }; }, [open]); const server: Level = connected ? "ok" : lostAt && Date.now() - lostAt > RECONNECT_GRACE_MS ? "err" : "warn"; const providers = Object.entries(data?.providers ?? {}); const mcp = Object.entries(data?.mcp ?? {}); const models: Level = data ? (providers.length ? combine(providers.map(([, p]) => providerLevel(p))) : "err") : "off"; const tools: Level = combine(mcp.map(([, m]) => mcpLevel(m))); const label = (l: Level) => t(`status.level.${l}` as Key); const summary = `${t("status.server")}: ${label(server)} · ${t("status.models")}: ${label(models)} · ${t("status.mcp")}: ${label(tools)}`; const recheck = async () => { setChecking(true); try { await post("/providers/status"); await load(); } finally { setChecking(false); } }; const go = (tab: string) => { setOpen(false); navigate(`/settings/${tab}`); }; const providerText = (p: ProviderState) => p.state === "ok" ? t("status.providerOk", { n: p.models, ms: p.latency_ms ?? 0 }) : p.state === "auth" ? t("settings.providerAuth") : p.state === "down" ? t("settings.providerDown") : t("status.notChecked"); return (
{open && (
{t("status.server")}
{connected ? t("sidebar.connected") : t("sidebar.disconnected")} {data && v{data.version}}
{t("status.models")} {admin && ( )}
{providers.length === 0 &&
{t("status.noProviders")}
} {providers.map(([name, p]) => (
{name} {providerText(p)} {fullest(p.plan) && ` · ${t("status.plan", { pct: fmtPercent(fullest(p.plan)!.ratio), window: t(`budget.window.${fullest(p.plan)!.window}` as Key) })}`}
))}
{t("status.mcp")}
{mcp.length === 0 &&
{t("status.noMcp")}
} {mcp.map(([name, m]) => (
{name} {m.state === "connected" ? t("mcp.tools", { n: m.tools ?? 0 }) : t(`mcp.state.${m.state}` as Key)}
))}
{!!data?.budgets?.length && (
{t("status.budgets")}
{data.budgets.map((m) => (
{m.kind === "total" ? t("stats.total") : m.kind === "images" ? t("nav.images") : m.kind === "provider" ? m.provider : `${m.provider} · ${t(`budget.window.${m.kind}` as Key)}`} = 0.8} exceeded={m.ratio >= 1} /> {fmtPercent(m.ratio)}
))}
)} {admin && (
)}
)}
); }