use tauri::{AppHandle, Emitter}; use serde::{Deserialize, Serialize}; use futures_util::StreamExt; use reqwest::Client; #[derive(Serialize, Deserialize, Clone)] struct TranslationPayload { model: String, prompt: String, stream: bool, } #[derive(Deserialize)] struct OllamaResponse { response: Option, // done: bool, } #[tauri::command] async fn translate( app: AppHandle, api_address: String, payload: TranslationPayload, ) -> Result { let client = Client::new(); let url = format!("{}/api/generate", api_address); let res = client .post(&url) .json(&payload) .send() .await .map_err(|e| e.to_string())?; if !payload.stream { let data = res.json::().await.map_err(|e| e.to_string())?; return Ok(data.response.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); // Handle potential multiple JSON objects in one chunk for line in text.lines() { if line.trim().is_empty() { continue; } if let Ok(json) = serde_json::from_str::(line) { if let Some(token) = json.response { full_response.push_str(&token); app.emit("translation-chunk", token).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"); }