/* ===================================================================== * bridge.ts — Tauri IPC 桥接(AI 代理专用) * 桌面版(Tauri WebView)直连 AI 网关受 CORS 限制,走 Rust 侧 reqwest 代理; * Web 版返回 null,调用方回退浏览器 fetch(智谱等支持 CORS 的网关直连可用) * ===================================================================== */ /** 是否运行在 Tauri 桌面环境 */ export function isTauri(): boolean { return typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) } let _invoke: ((cmd: string, args?: Record) => Promise) | null = null let _invokeTried = false /** 懒加载 Tauri invoke(动态 import,Web 版不会打进包) */ async function getInvoke() { if (_invokeTried) return _invoke _invokeTried = true if (!isTauri()) return null try { const mod = await import('@tauri-apps/api/core') _invoke = mod.invoke } catch { _invoke = null } return _invoke } export interface ProxyResp { status: number body: string error?: string } /** 非流式代理:Rust 侧 fetch,返回 {status, body};非桌面环境返回 null */ export async function aiProxy(url: string, apiKey: string, body: string): Promise { const invoke = await getInvoke() if (!invoke) return null try { const r = await invoke('ai_proxy', { url, apiKey, body }) as ProxyResp return r } catch (e: any) { return { status: 0, body: '', error: String(e?.message || e) } } } /** 流式代理:Rust 侧 SSE,原始 chunk(含 "data: {...}" 帧)通过 onChunk 推送; * SSE 解析由 ai.ts 的 createSSESink 统一处理(与浏览器路径共用)。非桌面返回 null */ export async function aiProxyStream( url: string, apiKey: string, body: string, onChunk: (raw: string) => void ): Promise { const invoke = await getInvoke() if (!invoke) return null try { const eventMod = await import('@tauri-apps/api/event') const unlisten = await eventMod.listen('ai-delta', (ev) => { onChunk(ev.payload || '') }) try { const full = await invoke('ai_proxy_stream', { url, apiKey, body }) as unknown as string return { status: 200, body: full } } finally { unlisten() } } catch (e: any) { // Rust 侧错误协议:__HTTP___ const raw = String(e?.message || e) const m = raw.match(/^__HTTP_(\d+)__/) if (m) return { status: Number(m[1]), body: raw.slice(m[0].length) } return { status: 0, body: '', error: raw } } }