diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1baef020b..d5e607ff6 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -8,8 +8,7 @@ use core::{ }; use reqwest::Client; use std::{collections::HashMap, sync::Arc}; -use tauri::{Emitter, Manager}; - +use tauri::{Emitter, Manager, RunEvent}; use tokio::sync::Mutex; #[cfg_attr(mobile, tauri::mobile_entry_point)] @@ -22,7 +21,8 @@ pub fn run() { // when defining deep link schemes at runtime, you must also check `argv` here })); } - builder + + let app = builder .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_dialog::init()) @@ -143,12 +143,46 @@ pub fn run() { cleanup_processes(state).await; }); } + let client = Client::new(); let url = "http://127.0.0.1:39291/processManager/destroy"; let _ = client.delete(url).send(); } _ => {} }) - .run(tauri::generate_context!()) + .build(tauri::generate_context!()) .expect("error while running tauri application"); + + // Handle app lifecycle events + app.run(|app, event| match event { + RunEvent::Exit => { + // This is called when the app is actually exiting (e.g., macOS dock quit) + // We can't prevent this, so run cleanup quickly + let app_handle = app.clone(); + tokio::task::block_in_place(|| { + tauri::async_runtime::block_on(async { + let state = app_handle.state::(); + + // Hide window immediately + if let Some(window) = app_handle.get_webview_window("main") { + let _ = window.hide(); + let _ = window.emit("kill-mcp-servers", ()); + } + + // Quick cleanup with shorter timeout + cleanup_processes(state).await; + + // Stop HTTP server with shorter timeout + let client = Client::new(); + let url = "http://127.0.0.1:39291/processManager/destroy"; + let _ = tokio::time::timeout( + tokio::time::Duration::from_secs(2), + client.delete(url).send(), + ) + .await; + }); + }); + } + _ => {} + }); } diff --git a/web-app/src/hooks/__tests__/useThreads.test.ts b/web-app/src/hooks/__tests__/useThreads.test.ts index 3c7915206..12caa5b59 100644 --- a/web-app/src/hooks/__tests__/useThreads.test.ts +++ b/web-app/src/hooks/__tests__/useThreads.test.ts @@ -88,7 +88,7 @@ describe('useThreads', () => { }) expect(Object.keys(result.current.threads)).toHaveLength(2) - expect(result.current.threads['thread1'].model.id).toEqual('thread1:free') + expect(result.current.threads['thread1'].model.id).toEqual('thread1/free') expect(result.current.threads['thread1'].model.provider).toEqual('llamacpp') expect(result.current.threads['thread2'].model.id).toEqual('thread2/test') expect(result.current.threads['thread2'].model.provider).toEqual('llamacpp') diff --git a/web-app/src/hooks/useThreads.ts b/web-app/src/hooks/useThreads.ts index 140972720..83bd320c8 100644 --- a/web-app/src/hooks/useThreads.ts +++ b/web-app/src/hooks/useThreads.ts @@ -44,9 +44,11 @@ export const useThreads = create()((set, get) => ({ 'llamacpp' ), // Cortex migration: take first two parts of the ID (the last is file name which is not needed) - id: !thread.model?.id.endsWith(':free') - ? thread.model?.id.split(':').slice(0, 2).join(sep()) - : thread.model?.id, + id: + thread.model.provider === 'llama.cpp' || + thread.model.provider === 'llamacpp' + ? thread.model?.id.split(':').slice(0, 2).join(sep()) + : thread.model?.id, } : undefined, } diff --git a/web-app/src/routes/settings/providers/$providerName.tsx b/web-app/src/routes/settings/providers/$providerName.tsx index e3dc7af6c..326500273 100644 --- a/web-app/src/routes/settings/providers/$providerName.tsx +++ b/web-app/src/routes/settings/providers/$providerName.tsx @@ -133,22 +133,6 @@ function ProviderDetail() { // Note: settingsChanged event is now handled globally in GlobalEventHandler // This ensures all screens receive the event intermediately - // Auto-refresh models for non-predefined providers - useEffect(() => { - if ( - provider && - provider.provider !== 'llamacpp' && - !predefinedProviders.some((p) => p.provider === provider.provider) && - provider.base_url - ) { - // Auto-refresh models every 10 seconds for remote providers - const intervalId = setInterval(() => { - handleRefreshModels() - }, 10000) - return () => clearInterval(intervalId) - } - }, [provider]) - const handleJoyrideCallback = (data: CallBackProps) => { const { status } = data