新增: 桌面能力——AI 代理 + 文件系统(B5)
- ai_proxy: Rust reqwest 代理 AI 请求(彻底解决 CORS 问题)
- ai_proxy_stream: SSE 流式代理,通过 app.emit("ai-delta") 实时推送
- write_file/read_file: 文件读写命令(替代对话框)
- 前端 bridge.ts 新增 listen() 函数(接收 Rust 事件推送)
- 前后端零错误编译,40 测试通过
This commit is contained in:
@@ -23,5 +23,4 @@ tokio = { version = "1", features = ["full"] }
|
||||
futures = "0.3"
|
||||
|
||||
[features]
|
||||
# ts-rs 类型导出
|
||||
export-types = []
|
||||
|
||||
@@ -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<String, String> {
|
||||
// 后续通过 tauri-plugin-dialog 实现文件对话框
|
||||
Ok("ok".to_string())
|
||||
}
|
||||
|
||||
/* ---------- AI 请求代理(解决 CORS) ---------- */
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AiProxyResult {
|
||||
pub status: u16,
|
||||
pub body: String,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
/// 代理 AI 请求(Rust 端发起,不受浏览器 CORS 限制)
|
||||
#[tauri::command]
|
||||
pub async fn ai_proxy(url: String, api_key: String, body: String) -> Result<AiProxyResult, String> {
|
||||
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<String, String> {
|
||||
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<String, String> {
|
||||
std::fs::read_to_string(&path).map_err(|e| format!("读取失败: {}", e))
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user