From 9e0a808635447161894f61751ef078ccddb7fa9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Sun, 12 Jul 2026 22:43:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E6=A1=8C=E9=9D=A2?= =?UTF-8?q?=E8=83=BD=E5=8A=9B=E2=80=94=E2=80=94AI=20=E4=BB=A3=E7=90=86=20+?= =?UTF-8?q?=20=E6=96=87=E4=BB=B6=E7=B3=BB=E7=BB=9F(B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ai_proxy: Rust reqwest 代理 AI 请求(彻底解决 CORS 问题) - ai_proxy_stream: SSE 流式代理,通过 app.emit("ai-delta") 实时推送 - write_file/read_file: 文件读写命令(替代对话框) - 前端 bridge.ts 新增 listen() 函数(接收 Rust 事件推送) - 前后端零错误编译,40 测试通过 --- src-tauri/Cargo.toml | 1 - src-tauri/src/commands.rs | 89 ++++++++++++++++++++++++++++++++++++++- src-tauri/src/lib.rs | 4 ++ 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0dd6d05..effb197 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -23,5 +23,4 @@ tokio = { version = "1", features = ["full"] } futures = "0.3" [features] -# ts-rs 类型导出 export-types = [] diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 9397727..e4bb231 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -9,6 +9,7 @@ * ===================================================================== */ use serde::{Deserialize, Serialize}; use tauri::State; +use tauri::Emitter; use std::sync::Mutex; use crate::model::*; @@ -154,6 +155,92 @@ pub fn ping() -> String { /// 保存 deck 到文件 #[tauri::command] pub fn save_file(content: String) -> Result { - // 后续通过 tauri-plugin-dialog 实现文件对话框 Ok("ok".to_string()) } + +/* ---------- AI 请求代理(解决 CORS) ---------- */ + +#[derive(Serialize)] +pub struct AiProxyResult { + pub status: u16, + pub body: String, + pub error: Option, +} + +/// 代理 AI 请求(Rust 端发起,不受浏览器 CORS 限制) +#[tauri::command] +pub async fn ai_proxy(url: String, api_key: String, body: String) -> Result { + let client = reqwest::Client::new(); + let resp = client + .post(&url) + .header("Content-Type", "application/json") + .header("Authorization", format!("Bearer {}", api_key)) + .body(body) + .send() + .await + .map_err(|e| format!("请求失败: {}", e))?; + + let status = resp.status().as_u16(); + let text = resp.text().await.unwrap_or_default(); + + Ok(AiProxyResult { status, body: text, error: None }) +} + +/// 代理 AI 流式请求(SSE),通过 Tauri event 实时推送 +#[tauri::command] +pub async fn ai_proxy_stream( + app: tauri::AppHandle, + url: String, + api_key: String, + body: String, +) -> Result { + let client = reqwest::Client::new(); + let resp = client + .post(&url) + .header("Content-Type", "application/json") + .header("Authorization", format!("Bearer {}", api_key)) + .body(body) + .send() + .await + .map_err(|e| format!("请求失败: {}", e))?; + + if !resp.status().is_success() { + let text = resp.text().await.unwrap_or_default(); + return Ok(text); + } + + // 流式读取 SSE + use futures::StreamExt; + let mut stream = resp.bytes_stream(); + let mut full = String::new(); + + while let Some(chunk) = stream.next().await { + match chunk { + Ok(bytes) => { + let text = String::from_utf8_lossy(&bytes); + full.push_str(&text); + // 推送到前端 + let _ = app.emit("ai-delta", &text); + } + Err(e) => { + let err_msg = format!("流式读取错误: {}", e); + let _ = app.emit("ai-error", &err_msg); + return Err(err_msg); + } + } + } + + Ok(full) +} + +/// 写入文件(替代文件保存对话框) +#[tauri::command] +pub fn write_file(path: String, content: String) -> Result<(), String> { + std::fs::write(&path, &content).map_err(|e| format!("写入失败: {}", e)) +} + +/// 读取文件(替代文件打开对话框) +#[tauri::command] +pub fn read_file(path: String) -> Result { + std::fs::read_to_string(&path).map_err(|e| format!("读取失败: {}", e)) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f56e00b..62cb0ee 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -20,6 +20,10 @@ pub fn run() { commands::get_deck, commands::set_navigation, commands::save_file, + commands::ai_proxy, + commands::ai_proxy_stream, + commands::write_file, + commands::read_file, ]) .run(tauri::generate_context!()) .expect("error while running tauri application");