/** * 后端 API 客户端。基址走 NEXT_PUBLIC_API_BASE,缺省指向本地后端 :8000。 * * JSON 契约与后端 schemas.py 对齐(camelCase),所以返回体可直接喂给页面 state。 * 注意:api_key 读取时后端永远打码,写回打码占位符表示"不改 key"(写时哨兵)。 */ export const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8000"; export type ModelType = "LLM" | "ASR" | "TTS" | "Realtime" | "Embedding"; 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 type InterfaceField = { key: string; label: string; group: "values" | "secrets"; type: "text" | "url" | "password" | "number" | "boolean" | "select"; required: boolean; default?: unknown; options?: string[]; }; export type InterfaceDefinition = { interfaceType: string; name: string; capability: ModelType; fieldSchema: { fields: InterfaceField[] }; enabled: boolean; version: number; }; export type ModelResource = { id: string; name: string; capability: ModelType; interfaceType: string; values: Record; secrets: Record; enabled: boolean; isDefault: boolean; updatedAt?: string | null; }; export type ModelResourceUpsert = Omit< ModelResource, "id" | "capability" | "updatedAt" >; export type ModelResourceTestResult = { ok: boolean; latencyMs: number | null; message: string; detail: string; }; export const interfaceDefinitionsApi = { list: (capability?: ModelType) => request( `/api/interface-definitions${capability ? `?capability=${capability}` : ""}`, ), }; export const modelResourcesApi = { list: () => request("/api/model-resources"), create: (body: ModelResourceUpsert) => request("/api/model-resources", { method: "POST", body: JSON.stringify(body), }), update: (id: string, body: ModelResourceUpsert) => request(`/api/model-resources/${id}`, { method: "PUT", body: JSON.stringify(body), }), test: (body: ModelResourceUpsert, id?: string) => request( id ? `/api/model-resources/${id}/test` : "/api/model-resources/test", { method: "POST", body: JSON.stringify(body), }, ), duplicate: (id: string) => request(`/api/model-resources/${id}/duplicate`, { method: "POST", }), remove: (id: string) => request<{ ok: boolean }>(`/api/model-resources/${id}`, { method: "DELETE", }), }; // ---------- 助手 ---------- export type AssistantType = | "prompt" | "workflow" | "dify" | "fastgpt" | "opencode"; export type RuntimeMode = "pipeline" | "realtime"; /** 后端 AssistantOut(宽表 STI:瘦字段平铺,workflow 用 graph)。apiKey 读时打码 */ export type Assistant = { id: string; name: string; type: AssistantType; runtimeMode: RuntimeMode; greeting: string; enableInterrupt: boolean; modelResourceIds: Partial>; knowledgeBaseId: string | null; prompt: string; apiUrl: string; apiKey: string; appId: string; graph: Record; updatedAt?: string | null; }; export type AssistantUpsert = Omit; export const assistantsApi = { list: () => request("/api/assistants"), get: (id: string) => request(`/api/assistants/${id}`), create: (body: AssistantUpsert) => request("/api/assistants", { method: "POST", body: JSON.stringify(body), }), update: (id: string, body: AssistantUpsert) => request(`/api/assistants/${id}`, { method: "PUT", body: JSON.stringify(body), }), // 服务端整行复制(含真 key,密钥不经浏览器) duplicate: (id: string) => request(`/api/assistants/${id}/duplicate`, { method: "POST" }), remove: (id: string) => request<{ ok: boolean }>(`/api/assistants/${id}`, { method: "DELETE" }), }; // ---------- 知识库 ---------- export type KnowledgeBase = { id: string; name: string; description: string; embeddingModelResourceId: string | null; status: string; updatedAt?: string | null; }; export const knowledgeBasesApi = { list: () => request("/api/knowledge-bases"), };