jan/src-tauri/src/lib.rs
Sam Hoang Van 7df7d8ffa0
feat: Implement Cortex server auto-restart and webview notification (#5074)
* feat: Implement Cortex server auto-restart and webview notification

Implements a robust auto-restart mechanism for the Cortex server (sidecar)
managed by the Tauri backend.

Key changes:

Backend (src-tauri):
- Modified `core/setup.rs` to:
  - Loop sidecar spawning, attempting up to `MAX_RESTARTS` (5) times with a
    `RESTART_DELAY_MS` (5 seconds) between attempts.
  - Monitor the sidecar process for unexpected termination (crashes or
    non-zero exit codes).
  - Reset the restart attempt count to 0 in `AppState` upon a successful
    server spawn.
  - Emit a "cortex_max_restarts_reached" event to the webview if the
    server fails to start after `MAX_RESTARTS`.
- Updated `core/state.rs` to include `cortex_restart_count: Arc<Mutex<u32>>`
  in `AppState` to track restart attempts.
- Added a new Tauri command `reset_cortex_restart_count` in `core/cmd.rs`
  to allow the webview (or other parts of the app) to reset this counter.
- Registered the new command and initialized the `cortex_restart_count`
  in `lib.rs`.

Frontend (web-app):
- Created a new component `CortexFailureDialog.tsx` in
  `src/containers/dialogs/` to:
  - Listen for the "cortex_max_restarts_reached" event from Tauri.
  - Display a dialog informing the user that the local AI engine (Cortex)
    failed to start after multiple attempts.
  - Offer options to "Contact Support" (opens jan.ai/support),
    "Restart Jan" (invokes the `relaunch` Tauri command), or "Okay"
    (dismisses the dialog).
- Integrated the `CortexFailureDialog` into the `RootLayout` in
  `src/routes/__root.tsx` so it's globally available.
- Corrected button variants in `__root.tsx` to use `variant="default"`
  with appropriate classNames for outline styling, resolving TypeScript
  errors.

* refactor: Improve async handling and logging in setup_sidecar function
2025-05-22 23:09:43 +07:00

124 lines
4.9 KiB
Rust

mod core;
use core::{
cmd::get_jan_data_folder_path,
setup::{self, setup_engine_binaries, setup_mcp, setup_sidecar},
state::{generate_app_token, AppState},
utils::download::DownloadManagerState,
};
use std::{collections::HashMap, sync::Arc};
use tauri::Emitter;
use tokio::sync::Mutex;
use reqwest::blocking::Client;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![
// FS commands - Deperecate soon
core::fs::join_path,
core::fs::mkdir,
core::fs::exists_sync,
core::fs::readdir_sync,
core::fs::read_file_sync,
core::fs::rm,
core::fs::file_stat,
// App commands
core::cmd::get_themes,
core::cmd::get_app_configurations,
core::cmd::get_active_extensions,
core::cmd::get_user_home_path,
core::cmd::update_app_configuration,
core::cmd::get_jan_data_folder_path,
core::cmd::get_jan_extensions_path,
core::cmd::relaunch,
core::cmd::open_app_directory,
core::cmd::open_file_explorer,
core::cmd::install_extensions,
core::cmd::read_theme,
core::cmd::app_token,
core::cmd::start_server,
core::cmd::stop_server,
core::cmd::read_logs,
core::cmd::handle_app_update,
core::cmd::change_app_data_folder,
core::cmd::reset_cortex_restart_count,
// MCP commands
core::mcp::get_tools,
core::mcp::call_tool,
core::mcp::restart_mcp_servers,
core::mcp::get_connected_servers,
core::mcp::save_mcp_configs,
core::mcp::get_mcp_configs,
// Threads
core::threads::list_threads,
core::threads::create_thread,
core::threads::modify_thread,
core::threads::delete_thread,
core::threads::list_messages,
core::threads::create_message,
core::threads::modify_message,
core::threads::delete_message,
core::threads::get_thread_assistant,
core::threads::create_thread_assistant,
core::threads::modify_thread_assistant,
// Download
core::utils::download::download_file,
core::utils::download::download_hf_repo,
core::utils::download::cancel_download_task,
])
.manage(AppState {
app_token: Some(generate_app_token()),
mcp_servers: Arc::new(Mutex::new(HashMap::new())),
download_manager: Arc::new(Mutex::new(DownloadManagerState::default())),
cortex_restart_count: Arc::new(Mutex::new(0)),
})
.setup(|app| {
app.handle().plugin(
tauri_plugin_log::Builder::default()
.targets([
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout),
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Webview),
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Folder {
path: get_jan_data_folder_path(app.handle().clone()).join("logs"),
file_name: Some("app".to_string()),
}),
])
.build(),
)?;
app.handle()
.plugin(tauri_plugin_updater::Builder::new().build())?;
// Install extensions
if let Err(e) = setup::install_extensions(app.handle().clone(), false) {
log::error!("Failed to install extensions: {}", e);
}
setup_mcp(app);
setup_sidecar(app).expect("Failed to setup sidecar");
setup_engine_binaries(app).expect("Failed to setup engine binaries");
// TODO(any) need to wire up with frontend
// let handle = app.handle().clone();
// tauri::async_runtime::spawn(async move {
// handle_app_update(handle).await.unwrap();
// });
Ok(())
})
.on_window_event(|window, event| match event {
tauri::WindowEvent::CloseRequested { .. } => {
let client = Client::new();
let url = "http://127.0.0.1:39291/processManager/destroy";
let _ = client.delete(url).send();
window.emit("kill-sidecar", ()).unwrap();
}
_ => {}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}