/** * 后端 API 客户端。基址走 NEXT_PUBLIC_API_BASE,缺省指向本地后端 :8000。 * * JSON 契约与后端 schemas.py 对齐(camelCase),所以返回体可直接喂给页面 state。 * 注意:api_key 读取时后端永远打码,写回打码占位符表示"不改 key"(写时哨兵)。 */ const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8000"; export type ModelType = "LLM" | "ASR" | "TTS" | "Realtime" | "Embedding"; export type InterfaceType = "openai" | "xfyun" | "dashscope" | "gemini"; /** 列表/详情返回(api_key 已打码) */ export type Credential = { id: string; name: string; modelId: string; type: ModelType; interfaceType: InterfaceType; apiUrl: string; apiKey: string; isDefault: boolean; }; /** 创建/更新入参。apiKey 留空或打码值 → 后端保留旧 key */ export type CredentialUpsert = { name: string; modelId: string; type: ModelType; interfaceType: InterfaceType; apiUrl: string; apiKey: string; isDefault: boolean; }; async function request(path: string, init?: RequestInit): Promise { const res = await fetch(`${API_BASE}${path}`, { headers: { "Content-Type": "application/json" }, ...init, }); if (!res.ok) { let detail = `请求失败 (${res.status})`; try { const body = (await res.json()) as { detail?: string }; if (body?.detail) detail = body.detail; } catch { // 响应体不是 JSON,沿用默认错误信息 } throw new Error(detail); } // 204 / 空响应保护 const text = await res.text(); return (text ? JSON.parse(text) : undefined) as T; } export const credentialsApi = { list: () => request("/api/credentials"), create: (body: CredentialUpsert) => request("/api/credentials", { method: "POST", body: JSON.stringify(body), }), update: (id: string, body: CredentialUpsert) => request(`/api/credentials/${id}`, { method: "PUT", body: JSON.stringify(body), }), remove: (id: string) => request<{ ok: boolean }>(`/api/credentials/${id}`, { method: "DELETE" }), };