From fb98ab547215a3d36ffce0519760442935e9d383 Mon Sep 17 00:00:00 2001 From: Julian Freeman Date: Sun, 22 Feb 2026 19:58:34 -0400 Subject: [PATCH] fix origin problem --- package.json | 2 +- src-tauri/Cargo.lock | 169 +++++++++++++++++++++++++++++++++++++- src-tauri/Cargo.toml | 4 +- src-tauri/src/lib.rs | 63 +++++++++++++- src-tauri/tauri.conf.json | 2 +- src/App.vue | 67 ++++++--------- 6 files changed, 254 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 8061d5d..e27e752 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gemmatrans-client", "private": true, - "version": "0.1.2", + "version": "0.1.3", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4df719b..b19a5a1 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -554,7 +554,7 @@ dependencies = [ "bitflags 2.11.0", "core-foundation 0.10.1", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -996,6 +996,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1003,7 +1012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -1017,6 +1026,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1236,8 +1251,10 @@ dependencies = [ [[package]] name = "gemmatrans-client" -version = "0.1.2" +version = "0.1.3" dependencies = [ + "futures-util", + "reqwest 0.12.28", "serde", "serde_json", "tauri", @@ -1609,6 +1626,22 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -2156,6 +2189,23 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.9.0" @@ -2465,6 +2515,50 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -3150,16 +3244,19 @@ dependencies = [ "cookie_store 0.22.1", "encoding_rs", "futures-core", + "futures-util", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", "mime", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -3170,13 +3267,16 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams 0.4.2", "web-sys", "webpki-roots", ] @@ -3211,7 +3311,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.5.0", "web-sys", ] @@ -3313,6 +3413,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.8.22" @@ -3370,6 +3479,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.24.0" @@ -4316,6 +4448,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -4676,6 +4818,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.1" @@ -4838,6 +4986,19 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasm-streams" version = "0.5.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 37e47e5..c6bd946 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gemmatrans-client" -version = "0.1.2" +version = "0.1.3" description = "A translategemma client" authors = ["Julian"] edition = "2021" @@ -23,4 +23,6 @@ tauri-plugin-opener = "2" tauri-plugin-http = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" +reqwest = { version = "0.12", features = ["json", "stream"] } +futures-util = "0.3" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2a55dee..37f8bcc 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,7 +1,62 @@ -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ +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] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) +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)] @@ -9,7 +64,7 @@ pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_http::init()) - .invoke_handler(tauri::generate_handler![greet]) + .invoke_handler(tauri::generate_handler![translate]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9dab728..9461c6f 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "gemmatrans-client", - "version": "0.1.2", + "version": "0.1.3", "identifier": "top.volan.gemmatrans-client", "build": { "beforeDevCommand": "pnpm dev", diff --git a/src/App.vue b/src/App.vue index 2f2588f..8307439 100644 --- a/src/App.vue +++ b/src/App.vue @@ -14,7 +14,8 @@ import { Sun, Moon } from 'lucide-vue-next'; -import { fetch } from '@tauri-apps/plugin-http'; +import { invoke } from '@tauri-apps/api/core'; +import { listen } from '@tauri-apps/api/event'; import { useSettingsStore, LANGUAGES, DEFAULT_TEMPLATE } from './stores/settings'; import pkg from '../package.json'; import { clsx, type ClassValue } from 'clsx'; @@ -81,6 +82,20 @@ const targetText = ref(''); const isTranslating = ref(false); const showCopyFeedback = ref(false); +let unlisten: (() => void) | null = null; + +onMounted(async () => { + unlisten = await listen('translation-chunk', (event) => { + if (isTranslating.value) { + targetText.value += event.payload; + } + }); +}); + +onUnmounted(() => { + if (unlisten) unlisten(); +}); + // Language Selection const sourceLangCode = computed({ get: () => settings.sourceLang.code, @@ -146,50 +161,18 @@ const translate = async () => { settings.addLog('request', requestBody); try { - const response = await fetch(`${settings.ollamaApiAddress}/api/generate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(requestBody) + const response = await invoke('translate', { + apiAddress: settings.ollamaApiAddress, + payload: requestBody }); - - if (!response.ok) { - const errorText = await response.text(); - settings.addLog('error', { status: response.status, text: errorText }); - throw new Error(`API error (${response.status}): ${errorText || response.statusText}`); - } - - if (settings.enableStreaming) { - const reader = response.body?.getReader(); - const decoder = new TextDecoder(); - if (reader) { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - const chunk = decoder.decode(value, { stream: true }); - const lines = chunk.split('\n'); - for (const line of lines) { - if (!line.trim()) continue; - try { - const data = JSON.parse(line); - if (data.response) { - targetText.value += data.response; - } - if (data.done) { - settings.addLog('response', 'Stream finished'); - } - } catch (e) { - settings.addLog('error', `Chunk parse error: ${line}`); - } - } - } - } - } else { - const data = await response.json(); - settings.addLog('response', data); - targetText.value = data.response; + + // For non-streaming, response is returned as string + if (!settings.enableStreaming) { + targetText.value = response; } + settings.addLog('response', 'Translation completed'); } catch (err: any) { - const errorMsg = err instanceof Error ? err.message : String(err); + const errorMsg = String(err); settings.addLog('error', errorMsg); targetText.value = `Error: ${errorMsg}`; } finally {