From 0dcc24f1734da699cc7f73c13fecc458cc47f601 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Mon, 4 May 2026 21:47:44 +0200 Subject: [PATCH] Enhance Ollama integration via API and update settings UI --- settings.html | 2 +- src-tauri/src/main.rs | 79 ++++++++++++++++++++++++++++++++++----- src-tauri/tauri.conf.json | 5 ++- 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/settings.html b/settings.html index 5410353..fbd9f36 100644 --- a/settings.html +++ b/settings.html @@ -264,7 +264,7 @@ if (res.status === 'no-cli') { container.innerHTML = `
- You need to install Ollama to use the cool stuff !! + Ollama is not reachable. Start Ollama or install it to use the cool stuff !!
`; return; } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 527c5c3..106fb29 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -24,7 +24,9 @@ use std::{ use tauri::{ menu::{Menu, MenuItem, PredefinedMenuItem, Submenu}, tray::{MouseButton, MouseButtonState, TrayIcon, TrayIconBuilder, TrayIconEvent}, - AppHandle, Emitter, Manager, WebviewUrl, WebviewWindow, WebviewWindowBuilder, WindowEvent, + utils::config::Color, + AppHandle, Emitter, Manager, TitleBarStyle, WebviewUrl, WebviewWindow, WebviewWindowBuilder, + WindowEvent, }; use tempfile::TempDir; @@ -38,6 +40,8 @@ const MAX_SQUASH_PROMPT_MESSAGE_CHARS: usize = 400; const MAX_SQUASH_NAME_STATUS_CHARS: usize = 6_000; const MAX_SQUASH_DIFFSTAT_CHARS: usize = 4_000; const MAX_SQUASH_COMMIT_MESSAGE_CHARS: usize = 160; +const OLLAMA_BASE_URL: &str = "http://127.0.0.1:11434"; +const ROSE_TITLEBAR_COLOR: Color = Color(255, 241, 242, 255); const TAURI_BUILD_IGNORES: &[&str] = &["dist-tauri", "src-tauri/target", "src-tauri/gen"]; @@ -680,7 +684,7 @@ fn ensure_ollama_running() -> CommandResult<()> { .timeout(Duration::from_millis(700)) .build() .map_err(|e| e.to_string())?; - if client.get("http://127.0.0.1:11434/").send().is_ok() { + if client.get(OLLAMA_BASE_URL).send().is_ok() { return Ok(()); } @@ -709,7 +713,7 @@ fn ensure_ollama_running() -> CommandResult<()> { for _ in 0..10 { thread::sleep(Duration::from_millis(500)); - if client.get("http://127.0.0.1:11434/").send().is_ok() { + if client.get(OLLAMA_BASE_URL).send().is_ok() { return Ok(()); } } @@ -730,7 +734,7 @@ fn stream_ollama( .build() .map_err(|e| e.to_string())?; let mut response = client - .post("http://127.0.0.1:11434/api/generate") + .post(format!("{OLLAMA_BASE_URL}/api/generate")) .json(&json!({ "model": model, "prompt": prompt, @@ -1800,12 +1804,17 @@ fn open_settings_window(app: &AppHandle) -> CommandResult<()> { win.set_focus().map_err(|e| e.to_string())?; return Ok(()); } - WebviewWindowBuilder::new(app, "settings", WebviewUrl::App("settings.html".into())) - .title("Einstellungen") - .inner_size(600.0, 500.0) - .resizable(false) - .build() - .map_err(|e| e.to_string())?; + let builder = + WebviewWindowBuilder::new(app, "settings", WebviewUrl::App("settings.html".into())) + .title("Einstellungen") + .inner_size(600.0, 500.0) + .resizable(false) + .background_color(ROSE_TITLEBAR_COLOR); + #[cfg(target_os = "macos")] + let builder = builder + .title_bar_style(TitleBarStyle::Transparent) + .hidden_title(true); + builder.build().map_err(|e| e.to_string())?; Ok(()) } @@ -2263,6 +2272,10 @@ fn set_monitoring( #[tauri::command] fn ollama_list() -> CommandResult { + if let Ok(models) = ollama_list_from_api() { + return Ok(json!({ "status": "ok", "models": models })); + } + let output = run_process( "ollama", &["list".into(), "--json".into()], @@ -2287,6 +2300,27 @@ fn ollama_list() -> CommandResult { } } +fn ollama_list_from_api() -> CommandResult> { + let client = Client::builder() + .timeout(Duration::from_secs(2)) + .build() + .map_err(|e| e.to_string())?; + let response = client + .get(format!("{OLLAMA_BASE_URL}/api/tags")) + .send() + .map_err(|e| e.to_string())?; + let status = response.status(); + if !status.is_success() { + return Err(format!("Ollama tags request failed: {status}")); + } + let payload: Value = response.json().map_err(|e| e.to_string())?; + Ok(payload + .get("models") + .and_then(Value::as_array) + .cloned() + .unwrap_or_default()) +} + fn parse_ollama_list_plain() -> CommandResult { match run_process("ollama", &["list".into()], None, None, None) { Ok(out) => { @@ -2310,12 +2344,37 @@ fn parse_ollama_list_plain() -> CommandResult { #[tauri::command] fn ollama_pull(model: String) -> CommandResult { + if let Ok(value) = ollama_pull_from_api(&model) { + return Ok(value); + } + match run_process("ollama", &["pull".into(), model], None, None, None) { Ok(out) => Ok(json!({ "status": "ok", "msg": out.stdout })), Err(err) => Ok(json!({ "status": "error", "msg": err })), } } +fn ollama_pull_from_api(model: &str) -> CommandResult { + let client = Client::builder() + .timeout(Duration::from_secs(30 * 60)) + .build() + .map_err(|e| e.to_string())?; + let response = client + .post(format!("{OLLAMA_BASE_URL}/api/pull")) + .json(&json!({ "name": model, "stream": false })) + .send() + .map_err(|e| e.to_string())?; + let status = response.status(); + if !status.is_success() { + return Err(format!("Ollama pull request failed: {status}")); + } + let payload: Value = response.json().unwrap_or_else(|_| json!({})); + Ok(json!({ + "status": "ok", + "msg": payload.get("status").and_then(Value::as_str).unwrap_or("model pulled") + })) +} + #[tauri::command] fn get_commit_model(state: tauri::State<'_, AppState>) -> CommandResult { Ok(state diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3e746e3..7cfde19 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -15,7 +15,10 @@ "width": 900, "height": 600, "minWidth": 800, - "minHeight": 500 + "minHeight": 500, + "titleBarStyle": "Transparent", + "hiddenTitle": true, + "backgroundColor": "#fff1f2" } ], "security": {