mattrguerra/cortexmainPrivate on GitHub, published here in full

lib.rs

Language Rust6.3 KB165 lines

src-tauri/src/lib.rs

  1. //! Cortex, the desktop shell: windows, tray, shortcut and the commands the
  2. //! interface calls. The logic lives in cortex-core.
  3. mod commands;
  4. mod state;
  5. mod windows;
  6. use std::collections::HashMap;
  7. use std::sync::Mutex;
  8. use cortex_core::secrets::{KeyCache, SecretStore};
  9. use cortex_core::storage::Db;
  10. use tauri::Manager;
  11. use tauri_plugin_global_shortcut::ShortcutState;
  12. use state::AppState;
  13. fn secret_store() -> Box<dyn SecretStore> {
  14. #[cfg(all(feature = "keychain", not(feature = "e2e")))]
  15. {
  16. Box::new(cortex_core::secrets::KeychainStore)
  17. }
  18. #[cfg(not(all(feature = "keychain", not(feature = "e2e"))))]
  19. {
  20. // Tests and builds without the keychain: keys last for the session only.
  21. Box::new(cortex_core::secrets::MemoryStore::default())
  22. }
  23. }
  24. /// End-to-end runs only: point Cortex at a stand-in server and a scratch folder.
  25. #[cfg(feature = "e2e")]
  26. fn e2e_overrides(state: &AppState) {
  27. use cortex_core::api::Settings;
  28. let db = state.db();
  29. if let Ok(url) = std::env::var("CORTEX_SEED_URL") {
  30. let mut p = db.get_provider(cortex_core::storage::DEEPINFRA_ID).unwrap();
  31. p.base_url = url;
  32. db.upsert_provider(&p).unwrap();
  33. state.keys.set(cortex_core::storage::DEEPINFRA_ID, "e2e-key").unwrap();
  34. let mut s: Settings = db.get_setting(commands::SETTINGS_KEY).ok().flatten().unwrap_or_default();
  35. s.default_model_id = "standin/echo".into();
  36. s.quick_model_id = "standin/echo".into();
  37. s.monthly_budget_usd = Some(1.0);
  38. if let Ok(dir) = std::env::var("CORTEX_EXPORT_DIR") {
  39. s.vault_dir = Some(dir);
  40. }
  41. db.set_setting(commands::SETTINGS_KEY, &s).unwrap();
  42. }
  43. }
  44. #[cfg_attr(mobile, tauri::mobile_entry_point)]
  45. pub fn run() {
  46. let mut builder = tauri::Builder::default();
  47. // First, so a second launch just brings the running one forward. Not in
  48. // test builds: a test run must never hand itself to an app left running.
  49. if !cfg!(feature = "e2e") {
  50. builder = builder.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| windows::show_main(app)));
  51. }
  52. builder = builder
  53. .plugin(
  54. tauri_plugin_global_shortcut::Builder::new()
  55. .with_handler(|app, _shortcut, event| {
  56. if event.state() == ShortcutState::Pressed {
  57. windows::toggle_quick(app);
  58. }
  59. })
  60. .build(),
  61. )
  62. .plugin(tauri_plugin_dialog::init())
  63. .plugin(tauri_plugin_opener::init());
  64. if std::env::var_os("CORTEX_DATA_DIR").is_none() {
  65. builder = builder.plugin(tauri_plugin_window_state::Builder::default().with_denylist(&["quick"]).build());
  66. }
  67. builder
  68. .setup(|app| {
  69. let data_dir = match std::env::var_os("CORTEX_DATA_DIR").filter(|_| cfg!(feature = "e2e")) {
  70. Some(d) => d.into(),
  71. None => app.path().app_data_dir()?,
  72. };
  73. std::fs::create_dir_all(&data_dir)?;
  74. let db = Db::open(&data_dir.join("cortex.db")).map_err(|e| e.to_string())?;
  75. let state = AppState {
  76. db: Mutex::new(db),
  77. keys: KeyCache::new(secret_store()),
  78. streams: Mutex::new(HashMap::new()),
  79. data_dir,
  80. quick_shown_at: Mutex::new(None),
  81. dialog_open: Mutex::new(false),
  82. tray_spend: Mutex::new(None),
  83. };
  84. // Attachments added to the composer and never sent.
  85. if let Ok(paths) = state.db().delete_stale_drafts(commands::DAY_MS) {
  86. commands::remove_unused_files(&state, &paths);
  87. }
  88. #[cfg(feature = "e2e")]
  89. e2e_overrides(&state);
  90. app.manage(state);
  91. let handle = app.handle().clone();
  92. windows::create_windows(&handle)?;
  93. let settings = commands::load_settings(&handle.state::<AppState>());
  94. windows::apply_theme(&handle, &settings.theme);
  95. if settings.material != "sidebar" {
  96. windows::apply_material(&handle, &settings.material);
  97. }
  98. if let Err(err) = windows::create_tray(&handle) {
  99. eprintln!("cortex: no tray: {err}"); // e.g. a Linux desktop without a tray host
  100. }
  101. windows::register_initial_shortcut(&handle);
  102. tauri::async_runtime::spawn(commands::refresh_stale(handle));
  103. Ok(())
  104. })
  105. .invoke_handler(tauri::generate_handler![
  106. commands::app_info,
  107. commands::window_ready,
  108. commands::show_main,
  109. commands::list_providers,
  110. commands::save_provider,
  111. commands::delete_provider,
  112. commands::set_api_key,
  113. commands::clear_api_key,
  114. commands::has_api_key,
  115. commands::list_models,
  116. commands::refresh_models,
  117. commands::list_conversations,
  118. commands::get_conversation,
  119. commands::create_conversation,
  120. commands::rename_conversation,
  121. commands::set_pinned,
  122. commands::set_archived,
  123. commands::delete_conversation,
  124. commands::set_conversation_model,
  125. commands::set_system_prompt,
  126. commands::send_message,
  127. commands::cancel_stream,
  128. commands::truncate_from,
  129. commands::estimate_prompt,
  130. commands::search,
  131. commands::spend_summary,
  132. commands::usage_report,
  133. commands::list_presets,
  134. commands::save_preset,
  135. commands::delete_preset,
  136. commands::get_settings,
  137. commands::save_settings,
  138. commands::pick_attachments,
  139. commands::attach_bytes,
  140. commands::attachment_bytes,
  141. commands::discard_attachment,
  142. commands::export_conversation,
  143. commands::choose_vault_folder,
  144. commands::quick_open_in_main,
  145. commands::hide_quick,
  146. commands::resize_quick,
  147. ])
  148. .build(tauri::generate_context!())
  149. .expect("error while building Cortex")
  150. .run(|app, event| {
  151. #[cfg(target_os = "macos")]
  152. if let tauri::RunEvent::Reopen { .. } = event {
  153. windows::show_main(app);
  154. }
  155. let _ = (app, event);
  156. });
  157. }