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");