import type { DeployStatus, GenerateRequest, Model, Provider, ProviderCreatePayload, ProviderUpdatePayload, TaskStatusResponse, TranscriberConfig, TranscriberModelsStatus, TranscriberType, WhisperModelSize, } from './types' import { settings } from './storage' interface ApiEnvelope { code: number msg: string data: T } function backendUrl(): string { return (settings.value?.backendUrl || 'http://localhost:8483').replace(/\/$/, '') } async function request(path: string, init?: RequestInit): Promise { const res = await fetch(`${backendUrl()}${path}`, { headers: { 'Content-Type': 'application/json', ...(init?.headers || {}) }, ...init, }) if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`) const body = (await res.json()) as ApiEnvelope | T // 后端 ResponseWrapper 包了 {code, msg, data};非 0 视为业务错 if (body && typeof body === 'object' && 'code' in body) { const env = body as ApiEnvelope if (env.code !== 0) throw new Error(env.msg || '后端返回失败') return env.data } return body as T } export async function getProviders(): Promise { return request('/api/get_all_providers') } export async function getModelsByProvider(providerId: string): Promise { return request(`/api/model_enable/${providerId}`) } export async function setDownloaderCookie(platform: string, cookie: string): Promise { await request('/api/update_downloader_cookie', { method: 'POST', body: JSON.stringify({ platform, cookie }), }) } export async function getDownloaderCookie(platform: string): Promise { // 后端:未配置时返回 {code:0, msg:'未找到Cookies', data:null};配置时 data: {platform, cookie} const data = await request<{ platform: string, cookie: string } | null>( `/api/get_downloader_cookie/${platform}`, ) return data?.cookie ?? null } // ---- Provider CRUD ---- export async function addProvider(payload: ProviderCreatePayload): Promise { return request('/api/add_provider', { method: 'POST', body: JSON.stringify({ logo: 'custom', ...payload }), }) } export async function updateProvider(payload: ProviderUpdatePayload): Promise<{ id: string, enabled: number }> { return request<{ id: string, enabled: number }>('/api/update_provider', { method: 'POST', body: JSON.stringify(payload), }) } export async function getProviderById(id: string): Promise { return request(`/api/get_provider_by_id/${id}`) } export async function connectTest(id: string): Promise { await request('/api/connect_test', { method: 'POST', body: JSON.stringify({ id }), }) } // ---- Model CRUD ---- export async function listAllModels(providerId: string): Promise { return request(`/api/model_list/${providerId}`) } export async function addModel(providerId: string, modelName: string): Promise { await request('/api/models', { method: 'POST', body: JSON.stringify({ provider_id: providerId, model_name: modelName }), }) } export async function deleteModel(modelId: number | string): Promise { await request(`/api/models/delete/${modelId}`) } // ---- Transcriber ---- export async function getTranscriberConfig(): Promise { return request('/api/transcriber_config') } export async function setTranscriberConfig(transcriberType: TranscriberType, whisperModelSize?: WhisperModelSize): Promise { return request('/api/transcriber_config', { method: 'POST', body: JSON.stringify({ transcriber_type: transcriberType, whisper_model_size: whisperModelSize ?? null, }), }) } export async function getTranscriberModelsStatus(): Promise { return request('/api/transcriber_models_status') } export async function downloadTranscriberModel(modelSize: WhisperModelSize, transcriberType: TranscriberType = 'fast-whisper'): Promise { await request('/api/transcriber_download', { method: 'POST', body: JSON.stringify({ model_size: modelSize, transcriber_type: transcriberType }), }) } // ---- RAG Chat ---- export interface ChatMessage { role: 'user' | 'assistant' | 'system' content: string } export async function indexChatTask(taskId: string): Promise { await request('/api/chat/index', { method: 'POST', body: JSON.stringify({ task_id: taskId }), }) } export async function getChatStatus(taskId: string): Promise<{ status: 'idle' | 'indexing' | 'indexed' | 'failed', indexed: boolean }> { return request(`/api/chat/status?task_id=${encodeURIComponent(taskId)}`) } export async function askChat(payload: { task_id: string question: string history: ChatMessage[] provider_id: string model_name: string }): Promise { return request('/api/chat/ask', { method: 'POST', body: JSON.stringify(payload), }) } // ---- Monitor ---- export async function getDeployStatus(): Promise { return request('/api/deploy_status') } export async function getSysHealth(): Promise<{ ok: boolean, msg?: string }> { try { await request('/api/sys_health') return { ok: true } } catch (e) { return { ok: false, msg: (e as Error).message } } } export async function generateNote(payload: GenerateRequest): Promise<{ task_id: string }> { return request<{ task_id: string }>('/api/generate_note', { method: 'POST', body: JSON.stringify(payload), }) } export async function getTaskStatus(taskId: string): Promise { // /task_status 永远 HTTP 200;body 是 ResponseWrapper: // 成功:{code:0, data:{status, message, task_id, result?}} // 任务失败:{code:500, msg:'xxx', data:null} // 这里手动拆,把任务失败翻译成 status:'FAILED',避免 request() 抛错让 UI 收不到状态 const res = await fetch(`${backendUrl()}/api/task_status/${taskId}`) if (!res.ok) throw new Error(`HTTP ${res.status}`) const body = (await res.json()) as { code: number, msg: string, data: TaskStatusResponse | null } if (body.code === 0 && body.data) return body.data return { status: 'FAILED', message: body.msg || '任务失败', task_id: taskId } } export async function ping(): Promise { try { await getProviders() return true } catch { return false } } // markdown 里的 /static/screenshots/xxx 是相对路径,extension 渲染时需要拼绝对地址 export function absolutizeMarkdownImages(md: string): string { const base = backendUrl() return md.replace(/!\[([^\]]*)\]\((\/static\/[^)]+)\)/g, (_, alt, path) => `![${alt}](${base}${path})`) }