1
0

Enhance Ollama integration via API and update settings UI

This commit is contained in:
2026-05-04 21:47:44 +02:00
parent 0e12ac604e
commit 0dcc24f173
3 changed files with 74 additions and 12 deletions

View File

@@ -264,7 +264,7 @@
if (res.status === 'no-cli') { if (res.status === 'no-cli') {
container.innerHTML = ` container.innerHTML = `
<div style="color:red;font-weight:bold;margin:1em 0;"> <div style="color:red;font-weight:bold;margin:1em 0;">
You need to install Ollama to use the cool stuff !! Ollama is not reachable. Start Ollama or install it to use the cool stuff !!
</div>`; </div>`;
return; return;
} }

View File

@@ -24,7 +24,9 @@ use std::{
use tauri::{ use tauri::{
menu::{Menu, MenuItem, PredefinedMenuItem, Submenu}, menu::{Menu, MenuItem, PredefinedMenuItem, Submenu},
tray::{MouseButton, MouseButtonState, TrayIcon, TrayIconBuilder, TrayIconEvent}, 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; 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_NAME_STATUS_CHARS: usize = 6_000;
const MAX_SQUASH_DIFFSTAT_CHARS: usize = 4_000; const MAX_SQUASH_DIFFSTAT_CHARS: usize = 4_000;
const MAX_SQUASH_COMMIT_MESSAGE_CHARS: usize = 160; 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"]; 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)) .timeout(Duration::from_millis(700))
.build() .build()
.map_err(|e| e.to_string())?; .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(()); return Ok(());
} }
@@ -709,7 +713,7 @@ fn ensure_ollama_running() -> CommandResult<()> {
for _ in 0..10 { for _ in 0..10 {
thread::sleep(Duration::from_millis(500)); 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(()); return Ok(());
} }
} }
@@ -730,7 +734,7 @@ fn stream_ollama(
.build() .build()
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let mut response = client let mut response = client
.post("http://127.0.0.1:11434/api/generate") .post(format!("{OLLAMA_BASE_URL}/api/generate"))
.json(&json!({ .json(&json!({
"model": model, "model": model,
"prompt": prompt, "prompt": prompt,
@@ -1800,12 +1804,17 @@ fn open_settings_window(app: &AppHandle) -> CommandResult<()> {
win.set_focus().map_err(|e| e.to_string())?; win.set_focus().map_err(|e| e.to_string())?;
return Ok(()); return Ok(());
} }
WebviewWindowBuilder::new(app, "settings", WebviewUrl::App("settings.html".into())) let builder =
.title("Einstellungen") WebviewWindowBuilder::new(app, "settings", WebviewUrl::App("settings.html".into()))
.inner_size(600.0, 500.0) .title("Einstellungen")
.resizable(false) .inner_size(600.0, 500.0)
.build() .resizable(false)
.map_err(|e| e.to_string())?; .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(()) Ok(())
} }
@@ -2263,6 +2272,10 @@ fn set_monitoring(
#[tauri::command] #[tauri::command]
fn ollama_list() -> CommandResult<Value> { fn ollama_list() -> CommandResult<Value> {
if let Ok(models) = ollama_list_from_api() {
return Ok(json!({ "status": "ok", "models": models }));
}
let output = run_process( let output = run_process(
"ollama", "ollama",
&["list".into(), "--json".into()], &["list".into(), "--json".into()],
@@ -2287,6 +2300,27 @@ fn ollama_list() -> CommandResult<Value> {
} }
} }
fn ollama_list_from_api() -> CommandResult<Vec<Value>> {
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<Value> { fn parse_ollama_list_plain() -> CommandResult<Value> {
match run_process("ollama", &["list".into()], None, None, None) { match run_process("ollama", &["list".into()], None, None, None) {
Ok(out) => { Ok(out) => {
@@ -2310,12 +2344,37 @@ fn parse_ollama_list_plain() -> CommandResult<Value> {
#[tauri::command] #[tauri::command]
fn ollama_pull(model: String) -> CommandResult<Value> { fn ollama_pull(model: String) -> CommandResult<Value> {
if let Ok(value) = ollama_pull_from_api(&model) {
return Ok(value);
}
match run_process("ollama", &["pull".into(), model], None, None, None) { match run_process("ollama", &["pull".into(), model], None, None, None) {
Ok(out) => Ok(json!({ "status": "ok", "msg": out.stdout })), Ok(out) => Ok(json!({ "status": "ok", "msg": out.stdout })),
Err(err) => Ok(json!({ "status": "error", "msg": err })), Err(err) => Ok(json!({ "status": "error", "msg": err })),
} }
} }
fn ollama_pull_from_api(model: &str) -> CommandResult<Value> {
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] #[tauri::command]
fn get_commit_model(state: tauri::State<'_, AppState>) -> CommandResult<String> { fn get_commit_model(state: tauri::State<'_, AppState>) -> CommandResult<String> {
Ok(state Ok(state

View File

@@ -15,7 +15,10 @@
"width": 900, "width": 900,
"height": 600, "height": 600,
"minWidth": 800, "minWidth": 800,
"minHeight": 500 "minHeight": 500,
"titleBarStyle": "Transparent",
"hiddenTitle": true,
"backgroundColor": "#fff1f2"
} }
], ],
"security": { "security": {