新增: AI 助手 Markdown 渲染+打字机流式+桌面 Rust 代理修复 CORS
This commit is contained in:
+57
-30
@@ -1,44 +1,71 @@
|
||||
/* =====================================================================
|
||||
* bridge.ts — Tauri IPC 安全调用桥接
|
||||
*
|
||||
* 安全检测 __TAURI__ 环境,动态 import @tauri-apps/api/core。
|
||||
* Web 模式下返回 mock,组件无需关心环境。
|
||||
* bridge.ts — Tauri IPC 桥接(AI 代理专用)
|
||||
* 桌面版(Tauri WebView)直连 AI 网关受 CORS 限制,走 Rust 侧 reqwest 代理;
|
||||
* Web 版返回 null,调用方回退浏览器 fetch(智谱等支持 CORS 的网关直连可用)
|
||||
* ===================================================================== */
|
||||
|
||||
/** 动态获取 invoke 函数 */
|
||||
let _invoke: ((cmd: string, args?: Record<string, unknown>) => Promise<unknown>) | null = null
|
||||
/** 是否运行在 Tauri 桌面环境 */
|
||||
export function isTauri(): boolean {
|
||||
return typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window)
|
||||
}
|
||||
|
||||
async function ensureInvoke() {
|
||||
if (_invoke) return true
|
||||
if (typeof window === 'undefined' || !('__TAURI__' in window)) return false
|
||||
let _invoke: ((cmd: string, args?: Record<string, unknown>) => Promise<unknown>) | 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
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
} catch { _invoke = null }
|
||||
return _invoke
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全调用 Tauri IPC 命令。
|
||||
* Web 模式下返回 undefined(调用方需处理)。
|
||||
*/
|
||||
export async function invoke<T = unknown>(cmd: string, args?: Record<string, unknown>): Promise<T | undefined> {
|
||||
const ok = await ensureInvoke()
|
||||
if (!ok || !_invoke) return undefined
|
||||
return _invoke(cmd, args) as Promise<T>
|
||||
export interface ProxyResp {
|
||||
status: number
|
||||
body: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
/** 注册 Tauri 事件监听(用于 Rust → 前端推送) */
|
||||
export async function listen<T>(event: string, handler: (payload: T) => void): Promise<() => void> {
|
||||
if (typeof window === 'undefined' || !('__TAURI__' in window)) {
|
||||
return () => {} // 空注销函数
|
||||
}
|
||||
/** 非流式代理:Rust 侧 fetch,返回 {status, body};非桌面环境返回 null */
|
||||
export async function aiProxy(url: string, apiKey: string, body: string): Promise<ProxyResp | null> {
|
||||
const invoke = await getInvoke()
|
||||
if (!invoke) return null
|
||||
try {
|
||||
const { listen } = await import('@tauri-apps/api/event')
|
||||
return listen(event, (e: { payload: T }) => handler(e.payload))
|
||||
} catch {
|
||||
return () => {}
|
||||
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<ProxyResp | null> {
|
||||
const invoke = await getInvoke()
|
||||
if (!invoke) return null
|
||||
try {
|
||||
const eventMod = await import('@tauri-apps/api/event')
|
||||
const unlisten = await eventMod.listen<string>('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_<status>__<body>
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user