2025-03-03 17:54:00 +08:00
|
|
|
mod assistant;
|
2024-12-14 15:38:32 +08:00
|
|
|
mod autostart;
|
2025-02-06 11:45:37 +08:00
|
|
|
mod common;
|
2025-05-30 17:18:52 +08:00
|
|
|
mod extension;
|
2025-02-12 16:15:55 +08:00
|
|
|
mod search;
|
2025-02-06 11:45:37 +08:00
|
|
|
mod server;
|
2025-05-15 09:17:03 +08:00
|
|
|
mod settings;
|
2025-03-03 17:54:00 +08:00
|
|
|
mod setup;
|
2025-01-20 19:38:59 +08:00
|
|
|
mod shortcut;
|
2025-02-06 11:45:37 +08:00
|
|
|
mod util;
|
2025-01-10 14:59:03 +08:00
|
|
|
|
2025-02-07 16:31:05 +08:00
|
|
|
use crate::common::register::SearchSourceRegistry;
|
2025-06-19 20:58:54 +08:00
|
|
|
use crate::common::{CHECK_WINDOW_LABEL, MAIN_WINDOW_LABEL, SETTINGS_WINDOW_LABEL};
|
2025-02-06 11:45:37 +08:00
|
|
|
use crate::server::servers::{load_or_insert_default_server, load_servers_token};
|
2025-08-22 09:19:32 +08:00
|
|
|
use crate::util::prevent_default;
|
2025-08-13 15:33:30 +08:00
|
|
|
use autostart::change_autostart;
|
2025-02-25 16:22:22 +08:00
|
|
|
use lazy_static::lazy_static;
|
|
|
|
|
use std::sync::Mutex;
|
2025-05-09 17:54:58 +08:00
|
|
|
use std::sync::OnceLock;
|
2025-05-12 09:33:37 +08:00
|
|
|
use tauri::plugin::TauriPlugin;
|
2025-07-29 21:55:03 +08:00
|
|
|
use tauri::{AppHandle, Emitter, Manager, PhysicalPosition, WebviewWindow, WindowEvent};
|
2025-01-20 19:38:59 +08:00
|
|
|
use tauri_plugin_autostart::MacosLauncher;
|
2025-02-06 11:45:37 +08:00
|
|
|
|
|
|
|
|
/// Tauri store name
|
|
|
|
|
pub(crate) const COCO_TAURI_STORE: &str = "coco_tauri_store";
|
2024-10-13 17:41:16 +08:00
|
|
|
|
2025-02-25 16:22:22 +08:00
|
|
|
lazy_static! {
|
|
|
|
|
static ref PREVIOUS_MONITOR_NAME: Mutex<Option<String>> = Mutex::new(None);
|
|
|
|
|
}
|
2025-05-09 17:54:58 +08:00
|
|
|
/// To allow us to access tauri's `AppHandle` when its context is inaccessible,
|
|
|
|
|
/// store it globally. It will be set in `init()`.
|
2025-07-22 14:43:27 +08:00
|
|
|
///
|
|
|
|
|
/// # WARNING
|
|
|
|
|
///
|
|
|
|
|
/// You may find this work, but the usage is discouraged and should be generally
|
|
|
|
|
/// avoided. If you do need it, always be careful that it may not be set() when
|
|
|
|
|
/// you access it.
|
2025-05-09 17:54:58 +08:00
|
|
|
pub(crate) static GLOBAL_TAURI_APP_HANDLE: OnceLock<AppHandle> = OnceLock::new();
|
|
|
|
|
|
2024-11-30 15:29:00 +08:00
|
|
|
#[tauri::command]
|
2025-03-24 14:39:08 +08:00
|
|
|
async fn change_window_height(handle: AppHandle, height: u32) {
|
2025-02-08 07:38:02 +08:00
|
|
|
let window: WebviewWindow = handle.get_webview_window(MAIN_WINDOW_LABEL).unwrap();
|
2024-11-30 15:29:00 +08:00
|
|
|
|
|
|
|
|
let mut size = window.outer_size().unwrap();
|
|
|
|
|
size.height = height;
|
|
|
|
|
window.set_size(size).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-26 10:08:55 +08:00
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
struct ThemeChangedPayload {
|
2025-03-24 14:55:35 +08:00
|
|
|
#[allow(dead_code)]
|
2024-12-26 10:08:55 +08:00
|
|
|
is_dark_mode: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-10 14:59:03 +08:00
|
|
|
#[derive(Clone, serde::Serialize)]
|
2025-03-24 14:55:35 +08:00
|
|
|
#[allow(dead_code)]
|
2025-01-10 14:59:03 +08:00
|
|
|
struct Payload {
|
|
|
|
|
args: Vec<String>,
|
|
|
|
|
cwd: String,
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-13 17:41:16 +08:00
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
|
|
|
pub fn run() {
|
2025-03-24 14:55:35 +08:00
|
|
|
let ctx = tauri::generate_context!();
|
2025-02-23 13:09:38 +08:00
|
|
|
|
2025-02-19 13:02:22 +08:00
|
|
|
let mut app_builder = tauri::Builder::default();
|
2025-06-09 14:46:06 +08:00
|
|
|
// Set up logger first
|
|
|
|
|
app_builder = app_builder.plugin(set_up_tauri_logger());
|
2025-02-19 13:02:22 +08:00
|
|
|
|
|
|
|
|
#[cfg(desktop)]
|
|
|
|
|
{
|
2025-08-03 14:38:47 +08:00
|
|
|
app_builder =
|
|
|
|
|
app_builder.plugin(tauri_plugin_single_instance::init(|_app, _argv, _cwd| {}));
|
2025-02-19 13:02:22 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-21 18:57:32 +08:00
|
|
|
app_builder = app_builder
|
|
|
|
|
.plugin(tauri_plugin_http::init())
|
2024-10-13 17:41:16 +08:00
|
|
|
.plugin(tauri_plugin_shell::init())
|
2024-12-11 10:45:54 +08:00
|
|
|
.plugin(tauri_plugin_autostart::init(
|
2025-06-09 14:46:06 +08:00
|
|
|
MacosLauncher::LaunchAgent,
|
2024-12-11 10:45:54 +08:00
|
|
|
None,
|
|
|
|
|
))
|
2025-01-10 14:59:03 +08:00
|
|
|
.plugin(tauri_plugin_deep_link::init())
|
2025-03-03 17:54:00 +08:00
|
|
|
.plugin(tauri_plugin_store::Builder::default().build())
|
|
|
|
|
.plugin(tauri_plugin_dialog::init())
|
2025-03-05 12:22:33 +08:00
|
|
|
.plugin(tauri_plugin_fs_pro::init())
|
|
|
|
|
.plugin(tauri_plugin_macos_permissions::init())
|
2025-03-07 12:42:55 +08:00
|
|
|
.plugin(tauri_plugin_screenshots::init())
|
2025-03-11 10:36:42 +08:00
|
|
|
.plugin(tauri_plugin_process::init())
|
2025-07-24 16:05:36 +08:00
|
|
|
.plugin(
|
|
|
|
|
tauri_plugin_updater::Builder::new()
|
|
|
|
|
.default_version_comparator(crate::util::updater::custom_version_comparator)
|
|
|
|
|
.build(),
|
|
|
|
|
)
|
2025-06-10 17:26:19 +08:00
|
|
|
.plugin(tauri_plugin_windows_version::init())
|
2025-08-22 09:19:32 +08:00
|
|
|
.plugin(tauri_plugin_opener::init())
|
|
|
|
|
.plugin(prevent_default::init());
|
2025-02-18 08:26:52 +08:00
|
|
|
|
|
|
|
|
// Conditional compilation for macOS
|
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
|
{
|
|
|
|
|
app_builder = app_builder.plugin(tauri_nspanel::init());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let app = app_builder
|
2024-11-30 15:29:00 +08:00
|
|
|
.invoke_handler(tauri::generate_handler![
|
|
|
|
|
change_window_height,
|
2025-01-20 19:38:59 +08:00
|
|
|
shortcut::change_shortcut,
|
|
|
|
|
shortcut::unregister_shortcut,
|
|
|
|
|
shortcut::get_current_shortcut,
|
2024-12-14 15:38:32 +08:00
|
|
|
change_autostart,
|
2025-03-07 12:42:55 +08:00
|
|
|
show_coco,
|
2024-12-24 18:24:53 +08:00
|
|
|
hide_coco,
|
2025-03-07 12:42:55 +08:00
|
|
|
show_settings,
|
2025-06-19 20:58:54 +08:00
|
|
|
show_check,
|
|
|
|
|
hide_check,
|
2025-02-06 11:45:37 +08:00
|
|
|
server::servers::add_coco_server,
|
|
|
|
|
server::servers::remove_coco_server,
|
|
|
|
|
server::servers::list_coco_servers,
|
|
|
|
|
server::servers::logout_coco_server,
|
|
|
|
|
server::servers::refresh_coco_server_info,
|
2025-02-24 18:14:43 +08:00
|
|
|
server::servers::enable_server,
|
|
|
|
|
server::servers::disable_server,
|
2025-02-06 11:45:37 +08:00
|
|
|
server::auth::handle_sso_callback,
|
|
|
|
|
server::profile::get_user_profiles,
|
2025-04-24 19:00:16 +08:00
|
|
|
server::datasource::datasource_search,
|
|
|
|
|
server::datasource::mcp_server_search,
|
2025-02-06 11:45:37 +08:00
|
|
|
server::connector::get_connectors_by_server,
|
2025-02-07 16:31:05 +08:00
|
|
|
search::query_coco_fusion,
|
2025-02-25 15:01:32 +08:00
|
|
|
assistant::chat_history,
|
2025-07-07 19:41:29 +08:00
|
|
|
assistant::chat_create,
|
|
|
|
|
assistant::chat_chat,
|
2025-02-25 15:01:32 +08:00
|
|
|
assistant::session_chat_history,
|
|
|
|
|
assistant::open_session_chat,
|
|
|
|
|
assistant::close_session_chat,
|
|
|
|
|
assistant::cancel_session_chat,
|
2025-04-02 14:03:40 +08:00
|
|
|
assistant::delete_session_chat,
|
|
|
|
|
assistant::update_session_chat,
|
2025-04-20 21:27:25 +08:00
|
|
|
assistant::assistant_search,
|
2025-05-27 16:29:43 +08:00
|
|
|
assistant::assistant_get,
|
|
|
|
|
assistant::assistant_get_multi,
|
2025-02-06 11:45:37 +08:00
|
|
|
// server::get_coco_server_datasources,
|
2025-02-21 18:57:32 +08:00
|
|
|
// server::get_coco_server_connectors,
|
2025-03-28 13:50:14 +08:00
|
|
|
get_app_search_source,
|
|
|
|
|
server::attachment::upload_attachment,
|
2025-07-31 15:36:03 +08:00
|
|
|
server::attachment::get_attachment_by_ids,
|
2025-03-28 13:50:14 +08:00
|
|
|
server::attachment::delete_attachment,
|
2025-04-11 11:48:48 +08:00
|
|
|
server::transcription::transcription,
|
2025-05-07 18:09:19 +08:00
|
|
|
server::system_settings::get_system_settings,
|
2025-05-30 17:18:52 +08:00
|
|
|
extension::built_in::application::get_app_list,
|
|
|
|
|
extension::built_in::application::get_app_search_path,
|
|
|
|
|
extension::built_in::application::get_app_metadata,
|
|
|
|
|
extension::built_in::application::add_app_search_path,
|
|
|
|
|
extension::built_in::application::remove_app_search_path,
|
2025-06-29 10:27:02 +08:00
|
|
|
extension::built_in::application::reindex_applications,
|
2025-07-29 16:30:12 +08:00
|
|
|
extension::quicklink_link_arguments,
|
2025-05-30 17:18:52 +08:00
|
|
|
extension::list_extensions,
|
|
|
|
|
extension::enable_extension,
|
|
|
|
|
extension::disable_extension,
|
|
|
|
|
extension::set_extension_alias,
|
|
|
|
|
extension::register_extension_hotkey,
|
|
|
|
|
extension::unregister_extension_hotkey,
|
|
|
|
|
extension::is_extension_enabled,
|
2025-07-29 10:26:47 +08:00
|
|
|
extension::third_party::install::store::search_extension,
|
2025-08-05 18:08:00 +08:00
|
|
|
extension::third_party::install::store::extension_detail,
|
2025-07-29 10:26:47 +08:00
|
|
|
extension::third_party::install::store::install_extension_from_store,
|
|
|
|
|
extension::third_party::install::local_extension::install_local_extension,
|
2025-07-09 16:28:59 +08:00
|
|
|
extension::third_party::uninstall_extension,
|
2025-05-15 09:17:03 +08:00
|
|
|
settings::set_allow_self_signature,
|
|
|
|
|
settings::get_allow_self_signature,
|
2025-05-30 17:18:52 +08:00
|
|
|
assistant::ask_ai,
|
|
|
|
|
crate::common::document::open,
|
2025-07-16 09:11:53 +08:00
|
|
|
extension::built_in::file_search::config::get_file_system_config,
|
|
|
|
|
extension::built_in::file_search::config::set_file_system_config,
|
2025-07-10 10:16:51 +08:00
|
|
|
server::synthesize::synthesize,
|
2025-07-21 20:39:16 +08:00
|
|
|
util::file::get_file_icon,
|
2025-08-18 15:38:13 +08:00
|
|
|
setup::backend_setup,
|
|
|
|
|
util::app_lang::update_app_lang,
|
2024-11-30 15:29:00 +08:00
|
|
|
])
|
|
|
|
|
.setup(|app| {
|
2025-06-09 14:46:06 +08:00
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
|
{
|
|
|
|
|
log::trace!("hiding Dock icon on macOS");
|
|
|
|
|
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
|
|
|
|
log::trace!("Dock icon should be hidden now");
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 17:36:37 +08:00
|
|
|
|
2025-08-13 15:33:30 +08:00
|
|
|
/* ----------- This code must be executed on the main thread and must not be relocated. ----------- */
|
|
|
|
|
let app_handle = app.app_handle();
|
|
|
|
|
let main_window = app_handle.get_webview_window(MAIN_WINDOW_LABEL).unwrap();
|
|
|
|
|
let settings_window = app_handle
|
|
|
|
|
.get_webview_window(SETTINGS_WINDOW_LABEL)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let check_window = app_handle.get_webview_window(CHECK_WINDOW_LABEL).unwrap();
|
2025-06-19 20:58:54 +08:00
|
|
|
setup::default(
|
2025-08-13 15:33:30 +08:00
|
|
|
app_handle,
|
2025-06-19 20:58:54 +08:00
|
|
|
main_window.clone(),
|
|
|
|
|
settings_window.clone(),
|
|
|
|
|
check_window.clone(),
|
|
|
|
|
);
|
2025-08-13 15:33:30 +08:00
|
|
|
/* ----------- This code must be executed on the main thread and must not be relocated. ----------- */
|
2025-02-06 11:45:37 +08:00
|
|
|
|
2024-11-30 15:29:00 +08:00
|
|
|
Ok(())
|
2025-02-12 16:15:55 +08:00
|
|
|
})
|
|
|
|
|
.on_window_event(|window, event| match event {
|
|
|
|
|
WindowEvent::CloseRequested { api, .. } => {
|
2025-05-17 12:01:18 +08:00
|
|
|
//dbg!("Close requested event received");
|
2025-02-12 16:15:55 +08:00
|
|
|
window.hide().unwrap();
|
|
|
|
|
api.prevent_close();
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
})
|
|
|
|
|
.build(ctx)
|
2024-10-13 17:41:16 +08:00
|
|
|
.expect("error while running tauri application");
|
2025-05-15 09:17:03 +08:00
|
|
|
|
2025-02-09 21:51:03 +08:00
|
|
|
app.run(|app_handle, event| match event {
|
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
|
tauri::RunEvent::Reopen {
|
|
|
|
|
has_visible_windows,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2025-05-17 12:01:18 +08:00
|
|
|
// dbg!(
|
|
|
|
|
// "Reopen event received: has_visible_windows = {}",
|
|
|
|
|
// has_visible_windows
|
|
|
|
|
// );
|
2025-02-09 21:51:03 +08:00
|
|
|
if has_visible_windows {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
let _ = app_handle;
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-10-13 17:41:16 +08:00
|
|
|
}
|
2024-11-30 15:29:00 +08:00
|
|
|
|
2025-07-29 21:55:03 +08:00
|
|
|
pub async fn init(app_handle: &AppHandle) {
|
2025-02-06 11:45:37 +08:00
|
|
|
// Await the async functions to load the servers and tokens
|
|
|
|
|
if let Err(err) = load_or_insert_default_server(app_handle).await {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::error!("Failed to load servers: {}", err);
|
2025-02-06 11:45:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Err(err) = load_servers_token(app_handle).await {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::error!("Failed to load server tokens: {}", err);
|
2025-02-06 11:45:37 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-21 20:39:16 +08:00
|
|
|
let coco_servers = server::servers::get_all_servers().await;
|
2025-02-07 16:31:05 +08:00
|
|
|
|
|
|
|
|
// Get the registry from Tauri's state
|
2025-03-24 14:55:35 +08:00
|
|
|
// let registry: State<SearchSourceRegistry> = app_handle.state::<SearchSourceRegistry>();
|
2025-02-07 16:31:05 +08:00
|
|
|
|
|
|
|
|
for server in coco_servers {
|
2025-03-11 10:36:42 +08:00
|
|
|
crate::server::servers::try_register_server_to_search_source(app_handle.clone(), &server)
|
|
|
|
|
.await;
|
2025-02-07 16:31:05 +08:00
|
|
|
}
|
2025-03-06 20:11:17 +08:00
|
|
|
|
2025-06-09 14:46:06 +08:00
|
|
|
extension::built_in::pizza_engine_runtime::start_pizza_engine_runtime().await;
|
2025-01-02 14:41:54 +08:00
|
|
|
}
|
2024-11-30 15:29:00 +08:00
|
|
|
|
2025-03-07 12:42:55 +08:00
|
|
|
#[tauri::command]
|
2025-07-29 21:55:03 +08:00
|
|
|
async fn show_coco(app_handle: AppHandle) {
|
2025-06-13 18:06:21 +08:00
|
|
|
if let Some(window) = app_handle.get_webview_window(MAIN_WINDOW_LABEL) {
|
2025-03-24 17:19:19 +08:00
|
|
|
move_window_to_active_monitor(&window);
|
|
|
|
|
|
|
|
|
|
let _ = window.show();
|
|
|
|
|
let _ = window.unminimize();
|
2025-09-04 11:24:47 +08:00
|
|
|
|
|
|
|
|
// The Window Management (WM) extension (macOS-only) controls the
|
|
|
|
|
// frontmost window. Setting focus on macOS makes Coco the frontmost
|
|
|
|
|
// window, which means the WM extension would control Coco instead of other
|
|
|
|
|
// windows, which is not what we want.
|
|
|
|
|
//
|
|
|
|
|
// On Linux/Windows, however, setting focus is a necessity to ensure that
|
|
|
|
|
// users open Coco's window, then they can start typing, without needing
|
|
|
|
|
// to click on the window.
|
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
2025-03-24 17:19:19 +08:00
|
|
|
let _ = window.set_focus();
|
2025-05-07 18:09:19 +08:00
|
|
|
|
|
|
|
|
let _ = app_handle.emit("show-coco", ());
|
2025-03-24 17:19:19 +08:00
|
|
|
}
|
2024-12-24 18:24:53 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-24 17:19:19 +08:00
|
|
|
#[tauri::command]
|
2025-07-29 21:55:03 +08:00
|
|
|
async fn hide_coco(app: AppHandle) {
|
2025-06-13 18:06:21 +08:00
|
|
|
if let Some(window) = app.get_webview_window(MAIN_WINDOW_LABEL) {
|
2025-03-24 17:19:19 +08:00
|
|
|
if let Err(err) = window.hide() {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::error!("Failed to hide the window: {}", err);
|
2025-03-24 17:19:19 +08:00
|
|
|
} else {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::debug!("Window successfully hidden.");
|
2025-03-24 17:19:19 +08:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::error!("Main window not found.");
|
2024-12-17 11:20:50 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-29 21:55:03 +08:00
|
|
|
fn move_window_to_active_monitor(window: &WebviewWindow) {
|
2025-05-17 12:01:18 +08:00
|
|
|
//dbg!("Moving window to active monitor");
|
2025-02-11 14:53:46 +08:00
|
|
|
// Try to get the available monitors, handle failure gracefully
|
|
|
|
|
let available_monitors = match window.available_monitors() {
|
|
|
|
|
Ok(monitors) => monitors,
|
|
|
|
|
Err(e) => {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::error!("Failed to get monitors: {}", e);
|
2025-02-11 14:53:46 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Attempt to get the cursor position, handle failure gracefully
|
|
|
|
|
let cursor_position = match window.cursor_position() {
|
|
|
|
|
Ok(pos) => Some(pos),
|
|
|
|
|
Err(e) => {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::error!("Failed to get cursor position: {}", e);
|
2025-02-11 14:53:46 +08:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Find the monitor that contains the cursor or default to the primary monitor
|
|
|
|
|
let target_monitor = if let Some(cursor_position) = cursor_position {
|
|
|
|
|
// Convert cursor position to integers
|
|
|
|
|
let cursor_x = cursor_position.x.round() as i32;
|
|
|
|
|
let cursor_y = cursor_position.y.round() as i32;
|
|
|
|
|
|
|
|
|
|
// Find the monitor that contains the cursor
|
|
|
|
|
available_monitors.into_iter().find(|monitor| {
|
|
|
|
|
let monitor_position = monitor.position();
|
|
|
|
|
let monitor_size = monitor.size();
|
|
|
|
|
|
|
|
|
|
cursor_x >= monitor_position.x
|
|
|
|
|
&& cursor_x <= monitor_position.x + monitor_size.width as i32
|
|
|
|
|
&& cursor_y >= monitor_position.y
|
|
|
|
|
&& cursor_y <= monitor_position.y + monitor_size.height as i32
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Use the target monitor or default to the primary monitor
|
|
|
|
|
let monitor = match target_monitor.or_else(|| window.primary_monitor().ok().flatten()) {
|
|
|
|
|
Some(monitor) => monitor,
|
|
|
|
|
None => {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::error!("No monitor found!");
|
2025-02-11 14:53:46 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-02-25 16:22:22 +08:00
|
|
|
if let Some(name) = monitor.name() {
|
|
|
|
|
let previous_monitor_name = PREVIOUS_MONITOR_NAME.lock().unwrap();
|
|
|
|
|
|
|
|
|
|
if let Some(ref prev_name) = *previous_monitor_name {
|
|
|
|
|
if name.to_string() == *prev_name {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::debug!("Currently on the same monitor");
|
2025-02-25 16:22:22 +08:00
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-11 14:53:46 +08:00
|
|
|
let monitor_position = monitor.position();
|
|
|
|
|
let monitor_size = monitor.size();
|
|
|
|
|
|
|
|
|
|
// Get the current size of the window
|
|
|
|
|
let window_size = match window.inner_size() {
|
|
|
|
|
Ok(size) => size,
|
|
|
|
|
Err(e) => {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::error!("Failed to get window size: {}", e);
|
2025-02-11 14:53:46 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let window_width = window_size.width as i32;
|
|
|
|
|
let window_height = window_size.height as i32;
|
|
|
|
|
|
|
|
|
|
// Calculate the new position to center the window on the monitor
|
|
|
|
|
let window_x = monitor_position.x + (monitor_size.width as i32 - window_width) / 2;
|
|
|
|
|
let window_y = monitor_position.y + (monitor_size.height as i32 - window_height) / 2;
|
|
|
|
|
|
|
|
|
|
// Move the window to the new position
|
|
|
|
|
if let Err(e) = window.set_position(PhysicalPosition::new(window_x, window_y)) {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::error!("Failed to move window: {}", e);
|
2025-02-11 14:53:46 +08:00
|
|
|
}
|
2025-02-25 16:22:22 +08:00
|
|
|
|
|
|
|
|
if let Some(name) = monitor.name() {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::debug!("Window moved to monitor: {}", name);
|
2025-02-25 16:22:22 +08:00
|
|
|
|
|
|
|
|
let mut previous_monitor = PREVIOUS_MONITOR_NAME.lock().unwrap();
|
|
|
|
|
*previous_monitor = Some(name.to_string());
|
|
|
|
|
}
|
2025-02-11 14:53:46 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-06 17:50:44 +08:00
|
|
|
#[tauri::command]
|
2025-07-22 14:43:27 +08:00
|
|
|
async fn get_app_search_source(app_handle: AppHandle) -> Result<(), String> {
|
2025-03-06 17:50:44 +08:00
|
|
|
let _ = server::connector::refresh_all_connectors(&app_handle).await;
|
|
|
|
|
let _ = server::datasource::refresh_all_datasources(&app_handle).await;
|
2025-03-06 20:11:17 +08:00
|
|
|
|
|
|
|
|
Ok(())
|
2025-03-06 17:50:44 +08:00
|
|
|
}
|
2025-03-07 12:42:55 +08:00
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
async fn show_settings(app_handle: AppHandle) {
|
2025-06-13 18:06:21 +08:00
|
|
|
log::debug!("settings menu item was clicked");
|
|
|
|
|
let window = app_handle
|
|
|
|
|
.get_webview_window(SETTINGS_WINDOW_LABEL)
|
|
|
|
|
.expect("we have a settings window");
|
|
|
|
|
|
|
|
|
|
window.show().unwrap();
|
|
|
|
|
window.unminimize().unwrap();
|
|
|
|
|
window.set_focus().unwrap();
|
2025-04-16 19:06:31 +08:00
|
|
|
}
|
2025-05-07 18:09:19 +08:00
|
|
|
|
2025-06-19 20:58:54 +08:00
|
|
|
#[tauri::command]
|
|
|
|
|
async fn show_check(app_handle: AppHandle) {
|
|
|
|
|
log::debug!("check menu item was clicked");
|
|
|
|
|
let window = app_handle
|
|
|
|
|
.get_webview_window(CHECK_WINDOW_LABEL)
|
|
|
|
|
.expect("we have a check window");
|
|
|
|
|
|
|
|
|
|
window.show().unwrap();
|
|
|
|
|
window.unminimize().unwrap();
|
|
|
|
|
window.set_focus().unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
async fn hide_check(app_handle: AppHandle) {
|
|
|
|
|
log::debug!("check window was closed");
|
|
|
|
|
let window = &app_handle
|
|
|
|
|
.get_webview_window(CHECK_WINDOW_LABEL)
|
|
|
|
|
.expect("we have a check window");
|
|
|
|
|
|
|
|
|
|
window.hide().unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-12 09:33:37 +08:00
|
|
|
/// Log format:
|
|
|
|
|
///
|
|
|
|
|
/// ```text
|
|
|
|
|
/// [time] [log level] [file module:line] message
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// ```text
|
|
|
|
|
/// [05-11 17:00:00] [INF] [coco_lib:625] Coco-AI started
|
|
|
|
|
/// ```
|
|
|
|
|
fn set_up_tauri_logger() -> TauriPlugin<tauri::Wry> {
|
|
|
|
|
use log::Level;
|
2025-05-20 12:54:07 +08:00
|
|
|
use log::LevelFilter;
|
|
|
|
|
use tauri_plugin_log::Builder;
|
|
|
|
|
|
|
|
|
|
/// Coco-AI app's default log level.
|
|
|
|
|
const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Info;
|
|
|
|
|
const LOG_LEVEL_ENV_VAR: &str = "COCO_LOG";
|
2025-05-12 09:33:37 +08:00
|
|
|
|
|
|
|
|
fn format_log_level(level: Level) -> &'static str {
|
|
|
|
|
match level {
|
|
|
|
|
Level::Trace => "TRC",
|
|
|
|
|
Level::Debug => "DBG",
|
|
|
|
|
Level::Info => "INF",
|
|
|
|
|
Level::Warn => "WAR",
|
|
|
|
|
Level::Error => "ERR",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn format_target_and_line(record: &log::Record) -> String {
|
|
|
|
|
let mut str = record.target().to_string();
|
|
|
|
|
if let Some(line) = record.line() {
|
|
|
|
|
str.push(':');
|
|
|
|
|
str.push_str(&line.to_string());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
str
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-20 12:54:07 +08:00
|
|
|
/// Allow us to configure dynamic log levels via environment variable `COCO_LOG`.
|
|
|
|
|
///
|
|
|
|
|
/// Generally, it mirros the behavior of `env_logger`. Syntax: `COCO_LOG=[target][=][level][,...]`
|
|
|
|
|
///
|
|
|
|
|
/// * If this environment variable is not set, use the default log level.
|
|
|
|
|
/// * If it is set, respect it:
|
|
|
|
|
///
|
|
|
|
|
/// * `COCO_LOG=coco_lib` turns on all logging for the `coco_lib` module, which is
|
|
|
|
|
/// equivalent to `COCO_LOG=coco_lib=trace`
|
|
|
|
|
/// * `COCO_LOG=trace` turns on all logging for the application, regardless of its name
|
|
|
|
|
/// * `COCO_LOG=TRACE` turns on all logging for the application, regardless of its name (same as previous)
|
|
|
|
|
/// * `COCO_LOG=reqwest=debug` turns on debug logging for `reqwest`
|
|
|
|
|
/// * `COCO_LOG=trace,tauri=off` turns on all the logging except for the logs come from `tauri`
|
|
|
|
|
/// * `COCO_LOG=off` turns off all logging for the application
|
|
|
|
|
/// * `COCO_LOG=` Since the value is empty, turns off all logging for the application as well
|
|
|
|
|
fn dynamic_log_level(mut builder: Builder) -> Builder {
|
|
|
|
|
let Some(log_levels) = std::env::var_os(LOG_LEVEL_ENV_VAR) else {
|
|
|
|
|
return builder.level(DEFAULT_LOG_LEVEL);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
builder = builder.level(LevelFilter::Off);
|
|
|
|
|
|
|
|
|
|
let log_levels = log_levels.into_string().unwrap_or_else(|e| {
|
|
|
|
|
panic!(
|
|
|
|
|
"The value '{}' set in environment varaible '{}' is not UTF-8 encoded",
|
|
|
|
|
// Cannot use `.display()` here becuase that requires MSRV 1.87.0
|
|
|
|
|
e.to_string_lossy(),
|
|
|
|
|
LOG_LEVEL_ENV_VAR
|
|
|
|
|
)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// COCO_LOG=[target][=][level][,...]
|
|
|
|
|
let target_log_levels = log_levels.split(',');
|
|
|
|
|
for target_log_level in target_log_levels {
|
|
|
|
|
#[allow(clippy::collapsible_else_if)]
|
|
|
|
|
if let Some(char_index) = target_log_level.chars().position(|c| c == '=') {
|
|
|
|
|
let (target, equal_sign_and_level) = target_log_level.split_at(char_index);
|
|
|
|
|
// Remove the equal sign, we know it takes 1 byte
|
|
|
|
|
let level = &equal_sign_and_level[1..];
|
|
|
|
|
|
|
|
|
|
if let Ok(level) = level.parse::<LevelFilter>() {
|
|
|
|
|
// Here we have to call `.to_string()` because `Cow<'static, str>` requires `&'static str`
|
|
|
|
|
builder = builder.level_for(target.to_string(), level);
|
|
|
|
|
} else {
|
|
|
|
|
panic!(
|
|
|
|
|
"log level '{}' set in '{}={}' is invalid",
|
|
|
|
|
level, target, level
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if let Ok(level) = target_log_level.parse::<LevelFilter>() {
|
|
|
|
|
// This is a level
|
|
|
|
|
builder = builder.level(level);
|
|
|
|
|
} else {
|
|
|
|
|
// This is a target, enable all the logging
|
|
|
|
|
//
|
|
|
|
|
// Here we have to call `.to_string()` because `Cow<'static, str>` requires `&'static str`
|
|
|
|
|
builder = builder.level_for(target_log_level.to_string(), LevelFilter::Trace);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-03 11:18:28 +08:00
|
|
|
// When running the built binary, set `COCO_LOG` to `coco_lib=trace` to capture all logs
|
|
|
|
|
// that come from Coco in the log file, which helps with debugging.
|
|
|
|
|
if !tauri::is_dev() {
|
2025-07-21 20:39:16 +08:00
|
|
|
// We have absolutely no guarantee that we (We have control over the Rust
|
|
|
|
|
// code, but definitely no idea about the libc C code, all the shared objects
|
|
|
|
|
// that we will link) will not concurrently read/write `envp`, so just use unsafe.
|
|
|
|
|
unsafe {
|
|
|
|
|
std::env::set_var("COCO_LOG", "coco_lib=trace");
|
|
|
|
|
}
|
2025-06-03 11:18:28 +08:00
|
|
|
}
|
|
|
|
|
|
2025-05-20 12:54:07 +08:00
|
|
|
let mut builder = tauri_plugin_log::Builder::new();
|
|
|
|
|
builder = builder.format(|out, message, record| {
|
|
|
|
|
let now = chrono::Local::now().format("%m-%d %H:%M:%S");
|
|
|
|
|
let level = format_log_level(record.level());
|
|
|
|
|
let target_and_line = format_target_and_line(record);
|
|
|
|
|
out.finish(format_args!(
|
|
|
|
|
"[{}] [{}] [{}] {}",
|
|
|
|
|
now, level, target_and_line, message
|
|
|
|
|
));
|
|
|
|
|
});
|
|
|
|
|
builder = dynamic_log_level(builder);
|
|
|
|
|
|
|
|
|
|
builder.build()
|
2025-05-12 09:33:37 +08:00
|
|
|
}
|