// 运维控制台 → Gateway 控制面 API(带 JWT 鉴权;/admin 受 RequireAdmin 保护)。 export const GATEWAY: string = (import.meta.env.VITE_GATEWAY as string | undefined) ?? "http://localhost:8080"; const ADMIN = `${GATEWAY}/api/v1/admin`; // ---- 鉴权(JWT,存 localStorage)---- const TOKEN_KEY = "sdx_admin_token"; let token = typeof localStorage !== "undefined" ? localStorage.getItem(TOKEN_KEY) ?? "" : ""; export function setToken(t: string): void { token = t; try { localStorage.setItem(TOKEN_KEY, t); } catch { /* ignore */ } } export function clearToken(): void { token = ""; try { localStorage.removeItem(TOKEN_KEY); } catch { /* ignore */ } } export function getToken(): string { return token; } function authHeaders(json = false): Record { const h: Record = token ? { Authorization: `Bearer ${token}` } : {}; if (json) h["Content-Type"] = "application/json"; return h; } // guard 在 401(未登录) 时清令牌并广播登出(403=已登录但非管理员,照常抛错)。 function guard(res: Response): Response { if (res.status === 401) { clearToken(); if (typeof window !== "undefined") window.dispatchEvent(new Event("sdx:logout")); } return res; } export interface AuthUser { id: string; email: string; name?: string; } export async function login(email: string, password: string): Promise { const res = await fetch(`${GATEWAY}/api/v1/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), }); const data = (await res.json()) as { token?: string; user?: AuthUser; error?: string }; if (!res.ok || !data.token || !data.user) throw new Error(data.error ?? `登录失败: ${res.status}`); setToken(data.token); return data.user; } export async function me(): Promise { if (!token) return null; const res = await fetch(`${GATEWAY}/api/v1/auth/me`, { headers: authHeaders() }); if (!res.ok) { clearToken(); return null; } return ((await res.json()) as { user?: AuthUser }).user ?? null; } // ---- 模型配置(id 为雪花字符串)---- export type Kind = "chat" | "embedding"; export interface Model { id: string; kind: Kind; provider: string; base_url: string; api_key: string; // 列表里是脱敏值 model: string; active: boolean; } export interface ModelInput { id?: string; kind: Kind; provider: string; base_url: string; api_key: string; model: string; } export async function listModels(kind: Kind): Promise { const res = guard(await fetch(`${ADMIN}/models?kind=${kind}`, { headers: authHeaders() })); if (!res.ok) throw new Error(`list failed: ${res.status}`); return ((await res.json()) as { models: Model[] }).models; } export async function saveModel(m: ModelInput): Promise { const res = guard(await fetch(`${ADMIN}/models`, { method: "POST", headers: authHeaders(true), body: JSON.stringify(m) })); const data = (await res.json()) as { id?: string; error?: string }; if (!res.ok) throw new Error(data.error ?? `save failed: ${res.status}`); return data.id ?? ""; } export async function setActive(id: string): Promise { const res = guard(await fetch(`${ADMIN}/models/${id}/active`, { method: "POST", headers: authHeaders() })); if (!res.ok) throw new Error(`activate failed: ${res.status}`); } export async function deleteModel(id: string): Promise { const res = guard(await fetch(`${ADMIN}/models/${id}`, { method: "DELETE", headers: authHeaders() })); if (!res.ok) throw new Error(`delete failed: ${res.status}`); } export async function testModel(m: ModelInput): Promise<{ ok: boolean; message: string }> { const res = guard(await fetch(`${ADMIN}/models/test`, { method: "POST", headers: authHeaders(true), body: JSON.stringify(m) })); return (await res.json()) as { ok: boolean; message: string }; } // ---- 计价(token↔真钱,按模型分输入/输出)---- export interface Pricing { model_id: string; input_per_1k: number; output_per_1k: number; currency: string; } export async function listPricing(): Promise { const res = guard(await fetch(`${ADMIN}/pricing`, { headers: authHeaders() })); if (!res.ok) throw new Error(`list pricing failed: ${res.status}`); return ((await res.json()) as { pricing: Pricing[] }).pricing ?? []; } export async function savePricing(p: Pricing): Promise { const res = guard(await fetch(`${ADMIN}/pricing`, { method: "PUT", headers: authHeaders(true), body: JSON.stringify(p) })); if (!res.ok) { const d = (await res.json().catch(() => ({}))) as { error?: string }; throw new Error(d.error ?? `save pricing failed: ${res.status}`); } } // gatewayOnline 用公开的 /healthz 探活(不受鉴权影响)。 export async function gatewayOnline(): Promise { try { const res = await fetch(`${GATEWAY}/healthz`); return res.ok; } catch { return false; } } // —— 服务状态:基建 / 应用服务探活 + MCP 工具注册 —— export interface StatusItem { name: string; up: boolean; detail?: string; latency_ms?: number; } export interface ToolInfo { name: string; cn: string; desc: string; } export interface ToolGroup { server: string; up: boolean; tools: ToolInfo[] | null; } export interface SystemStatus { checked_at: string; infra: StatusItem[]; services: StatusItem[]; tools: ToolGroup[]; } export async function getStatus(): Promise { const res = guard(await fetch(`${ADMIN}/status`, { headers: authHeaders() })); if (!res.ok) throw new Error(`status failed: ${res.status}`); return (await res.json()) as SystemStatus; }