Files
ai-translate-client/src-tauri/src/lib.rs
2026-02-23 18:33:13 -04:00

102 lines
2.8 KiB
Rust

use tauri::{AppHandle, Emitter};
use serde::{Deserialize, Serialize};
use futures_util::StreamExt;
use reqwest::Client;
#[derive(Serialize, Deserialize, Clone)]
struct Message {
role: String,
content: String,
}
#[derive(Serialize, Deserialize, Clone)]
struct TranslationPayload {
model: String,
messages: Vec<Message>,
stream: bool,
}
#[derive(Deserialize)]
struct OpenAIResponse {
choices: Vec<Choice>,
}
#[derive(Deserialize)]
struct Choice {
message: Option<Message>,
delta: Option<Delta>,
}
#[derive(Deserialize)]
struct Delta {
content: Option<String>,
}
#[tauri::command]
async fn translate(
app: AppHandle,
api_address: String,
api_key: String,
payload: TranslationPayload,
) -> Result<String, String> {
let client = Client::new();
// Ensure URL doesn't have double slashes if api_address ends with /
let base_url = api_address.trim_end_matches('/');
let url = format!("{}/chat/completions", base_url);
let mut request = client.post(&url).json(&payload);
if !api_key.is_empty() {
request = request.header("Authorization", format!("Bearer {}", api_key));
}
let res = request
.send()
.await
.map_err(|e| e.to_string())?;
if !payload.stream {
let data = res.json::<OpenAIResponse>().await.map_err(|e| e.to_string())?;
return Ok(data.choices.get(0).and_then(|c| c.message.as_ref()).map(|m| m.content.clone()).unwrap_or_default());
}
let mut stream = res.bytes_stream();
let mut full_response = String::new();
while let Some(item) = stream.next().await {
let chunk = item.map_err(|e| e.to_string())?;
let text = String::from_utf8_lossy(&chunk);
for line in text.lines() {
let line = line.trim();
if line.is_empty() { continue; }
if line == "data: [DONE]" { break; }
if let Some(data_str) = line.strip_prefix("data: ") {
if let Ok(json) = serde_json::from_str::<OpenAIResponse>(data_str) {
if let Some(choice) = json.choices.get(0) {
if let Some(delta) = &choice.delta {
if let Some(content) = &delta.content {
full_response.push_str(content);
app.emit("translation-chunk", content).map_err(|e| e.to_string())?;
}
}
}
}
}
}
}
Ok(full_response)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_http::init())
.invoke_handler(tauri::generate_handler![translate])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}