Files
ai-video-fullstack/frontend/src/lib/api.ts

72 lines
2.1 KiB
TypeScript

/**
* 后端 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<T>(path: string, init?: RequestInit): Promise<T> {
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<Credential[]>("/api/credentials"),
create: (body: CredentialUpsert) =>
request<Credential>("/api/credentials", {
method: "POST",
body: JSON.stringify(body),
}),
update: (id: string, body: CredentialUpsert) =>
request<Credential>(`/api/credentials/${id}`, {
method: "PUT",
body: JSON.stringify(body),
}),
remove: (id: string) =>
request<{ ok: boolean }>(`/api/credentials/${id}`, { method: "DELETE" }),
};