lib.rs
Language Rust6.3 KB165 lines
//! Cortex, the desktop shell: windows, tray, shortcut and the commands the//! interface calls. The logic lives in cortex-core.mod commands;mod state;mod windows;use std::collections::HashMap;use std::sync::Mutex;use cortex_core::secrets::{KeyCache, SecretStore};use cortex_core::storage::Db;use tauri::Manager;use tauri_plugin_global_shortcut::ShortcutState;use state::AppState;fn secret_store() -> Box<dyn SecretStore> {#[cfg(all(feature = "keychain", not(feature = "e2e")))]{Box::new(cortex_core::secrets::KeychainStore)}#[cfg(not(all(feature = "keychain", not(feature = "e2e"))))]{// Tests and builds without the keychain: keys last for the session only.Box::new(cortex_core::secrets::MemoryStore::default())}}/// End-to-end runs only: point Cortex at a stand-in server and a scratch folder.#[cfg(feature = "e2e")]fn e2e_overrides(state: &AppState) {use cortex_core::api::Settings;let db = state.db();if let Ok(url) = std::env::var("CORTEX_SEED_URL") {let mut p = db.get_provider(cortex_core::storage::DEEPINFRA_ID).unwrap();p.base_url = url;db.upsert_provider(&p).unwrap();state.keys.set(cortex_core::storage::DEEPINFRA_ID, "e2e-key").unwrap();let mut s: Settings = db.get_setting(commands::SETTINGS_KEY).ok().flatten().unwrap_or_default();s.default_model_id = "standin/echo".into();s.quick_model_id = "standin/echo".into();s.monthly_budget_usd = Some(1.0);if let Ok(dir) = std::env::var("CORTEX_EXPORT_DIR") {s.vault_dir = Some(dir);}db.set_setting(commands::SETTINGS_KEY, &s).unwrap();}}#[cfg_attr(mobile, tauri::mobile_entry_point)]pub fn run() {let mut builder = tauri::Builder::default();// First, so a second launch just brings the running one forward. Not in// test builds: a test run must never hand itself to an app left running.if !cfg!(feature = "e2e") {builder = builder.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| windows::show_main(app)));}builder = builder.plugin(tauri_plugin_global_shortcut::Builder::new().with_handler(|app, _shortcut, event| {if event.state() == ShortcutState::Pressed {windows::toggle_quick(app);}}).build(),).plugin(tauri_plugin_dialog::init()).plugin(tauri_plugin_opener::init());if std::env::var_os("CORTEX_DATA_DIR").is_none() {builder = builder.plugin(tauri_plugin_window_state::Builder::default().with_denylist(&["quick"]).build());}builder.setup(|app| {let data_dir = match std::env::var_os("CORTEX_DATA_DIR").filter(|_| cfg!(feature = "e2e")) {Some(d) => d.into(),None => app.path().app_data_dir()?,};std::fs::create_dir_all(&data_dir)?;let db = Db::open(&data_dir.join("cortex.db")).map_err(|e| e.to_string())?;let state = AppState {db: Mutex::new(db),keys: KeyCache::new(secret_store()),streams: Mutex::new(HashMap::new()),data_dir,quick_shown_at: Mutex::new(None),dialog_open: Mutex::new(false),tray_spend: Mutex::new(None),};// Attachments added to the composer and never sent.if let Ok(paths) = state.db().delete_stale_drafts(commands::DAY_MS) {commands::remove_unused_files(&state, &paths);}#[cfg(feature = "e2e")]e2e_overrides(&state);app.manage(state);let handle = app.handle().clone();windows::create_windows(&handle)?;let settings = commands::load_settings(&handle.state::<AppState>());windows::apply_theme(&handle, &settings.theme);if settings.material != "sidebar" {windows::apply_material(&handle, &settings.material);}if let Err(err) = windows::create_tray(&handle) {eprintln!("cortex: no tray: {err}"); // e.g. a Linux desktop without a tray host}windows::register_initial_shortcut(&handle);tauri::async_runtime::spawn(commands::refresh_stale(handle));Ok(())}).invoke_handler(tauri::generate_handler![commands::app_info,commands::window_ready,commands::show_main,commands::list_providers,commands::save_provider,commands::delete_provider,commands::set_api_key,commands::clear_api_key,commands::has_api_key,commands::list_models,commands::refresh_models,commands::list_conversations,commands::get_conversation,commands::create_conversation,commands::rename_conversation,commands::set_pinned,commands::set_archived,commands::delete_conversation,commands::set_conversation_model,commands::set_system_prompt,commands::send_message,commands::cancel_stream,commands::truncate_from,commands::estimate_prompt,commands::search,commands::spend_summary,commands::usage_report,commands::list_presets,commands::save_preset,commands::delete_preset,commands::get_settings,commands::save_settings,commands::pick_attachments,commands::attach_bytes,commands::attachment_bytes,commands::discard_attachment,commands::export_conversation,commands::choose_vault_folder,commands::quick_open_in_main,commands::hide_quick,commands::resize_quick,]).build(tauri::generate_context!()).expect("error while building Cortex").run(|app, event| {#[cfg(target_os = "macos")]if let tauri::RunEvent::Reopen { .. } = event {windows::show_main(app);}let _ = (app, event);});}