276 lines
11 KiB
Rust
276 lines
11 KiB
Rust
mod acp;
|
|
mod app_error;
|
|
mod commands;
|
|
mod db;
|
|
mod models;
|
|
mod network;
|
|
mod parsers;
|
|
mod process;
|
|
mod terminal;
|
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
use acp::manager::ConnectionManager;
|
|
use commands::{
|
|
acp as acp_commands, conversations, folder_commands, folders, mcp as mcp_commands,
|
|
system_settings, terminal as terminal_commands, windows,
|
|
};
|
|
use tauri::Manager;
|
|
use terminal::manager::TerminalManager;
|
|
|
|
static APP_QUITTING: AtomicBool = AtomicBool::new(false);
|
|
|
|
fn get_folder_id_from_url(window: &tauri::Window) -> Option<i32> {
|
|
let webview = window.get_webview_window(window.label())?;
|
|
let url = webview.url().ok()?;
|
|
url.query_pairs()
|
|
.find(|(key, _)| key == "id")
|
|
.and_then(|(_, value)| value.parse::<i32>().ok())
|
|
}
|
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run() {
|
|
let _ = fix_path_env::fix();
|
|
|
|
tauri::Builder::default()
|
|
.plugin(tauri_plugin_window_state::Builder::new().build())
|
|
.plugin(tauri_plugin_opener::init())
|
|
.plugin(tauri_plugin_dialog::init())
|
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
|
.plugin(tauri_plugin_process::init())
|
|
.manage(ConnectionManager::new())
|
|
.manage(TerminalManager::new())
|
|
.manage(windows::SettingsWindowState::new())
|
|
.manage(windows::CommitWindowState::new())
|
|
.setup(|app| {
|
|
let app_data_dir = app.path().app_data_dir()?;
|
|
let app_version = env!("CARGO_PKG_VERSION");
|
|
let database =
|
|
tauri::async_runtime::block_on(db::init_database(&app_data_dir, app_version))
|
|
.map_err(|e| e.to_string())?;
|
|
app.manage(database);
|
|
|
|
// Restore and apply saved system proxy settings before any network operation.
|
|
let db = app.state::<db::AppDatabase>();
|
|
match tauri::async_runtime::block_on(system_settings::load_system_proxy_settings(
|
|
&db.conn,
|
|
)) {
|
|
Ok(settings) => {
|
|
let _ = network::proxy::apply_system_proxy_settings(&settings);
|
|
}
|
|
Err(err) => {
|
|
eprintln!("[Settings] failed to load system proxy settings: {err}");
|
|
}
|
|
}
|
|
|
|
// Restore previously open folders or show welcome
|
|
let db = app.state::<db::AppDatabase>();
|
|
let open_folders = tauri::async_runtime::block_on(
|
|
db::service::folder_service::list_open_folders(&db.conn),
|
|
)
|
|
.unwrap_or_default();
|
|
|
|
if open_folders.is_empty() {
|
|
let _ = windows::open_welcome_window(app.handle());
|
|
} else {
|
|
for entry in &open_folders {
|
|
let label = format!("folder-{}", uuid::Uuid::new_v4());
|
|
let url = tauri::WebviewUrl::App(format!("folder?id={}", entry.id).into());
|
|
let builder = tauri::WebviewWindowBuilder::new(app, &label, url)
|
|
.title(&entry.name)
|
|
.inner_size(1260.0, 860.0)
|
|
.min_inner_size(900.0, 600.0);
|
|
let _ = windows::apply_platform_window_style(builder).build();
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
})
|
|
.on_window_event(|window, event| {
|
|
let label = window.label().to_string();
|
|
|
|
if label == "settings"
|
|
&& matches!(
|
|
event,
|
|
tauri::WindowEvent::CloseRequested { .. } | tauri::WindowEvent::Destroyed
|
|
)
|
|
{
|
|
let app = window.app_handle();
|
|
if let Some(state) = app.try_state::<windows::SettingsWindowState>() {
|
|
windows::restore_windows_after_settings(&app, &state);
|
|
}
|
|
}
|
|
|
|
if label.starts_with("commit-")
|
|
&& matches!(
|
|
event,
|
|
tauri::WindowEvent::CloseRequested { .. } | tauri::WindowEvent::Destroyed
|
|
)
|
|
{
|
|
let app = window.app_handle();
|
|
if let Some(state) = app.try_state::<windows::CommitWindowState>() {
|
|
windows::restore_window_after_commit(&app, &state, &label);
|
|
}
|
|
}
|
|
|
|
if let tauri::WindowEvent::CloseRequested { .. } = event {
|
|
if label.starts_with("folder-") {
|
|
let app = window.app_handle();
|
|
if let Some(cm) = app.try_state::<ConnectionManager>() {
|
|
let disconnected =
|
|
tauri::async_runtime::block_on(cm.disconnect_by_owner_window(&label));
|
|
eprintln!(
|
|
"[ACP] folder window closing label={} disconnected_connections={}",
|
|
label, disconnected
|
|
);
|
|
}
|
|
|
|
// Only mark folder as closed if user is closing individual window,
|
|
// not when the entire app is quitting (so folders reopen on next launch)
|
|
if !APP_QUITTING.load(Ordering::Relaxed) {
|
|
if let Some(folder_id) = get_folder_id_from_url(window) {
|
|
if let Some(db) = app.try_state::<db::AppDatabase>() {
|
|
let _ = tauri::async_runtime::block_on(
|
|
db::service::folder_service::set_folder_open(
|
|
&db.conn, folder_id, false,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Kill terminal sessions owned by this folder window.
|
|
if let Some(tm) = app.try_state::<TerminalManager>() {
|
|
let killed = tm.kill_by_owner_window(&label);
|
|
eprintln!(
|
|
"[TERM] folder window closing label={} killed_terminals={}",
|
|
label, killed
|
|
);
|
|
}
|
|
let has_other_folder = app
|
|
.webview_windows()
|
|
.keys()
|
|
.any(|l| l.starts_with("folder-") && *l != label);
|
|
if !has_other_folder && !APP_QUITTING.load(Ordering::Relaxed) {
|
|
let _ = windows::open_welcome_window(app);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.invoke_handler(tauri::generate_handler![
|
|
conversations::list_conversations,
|
|
conversations::get_conversation,
|
|
conversations::list_folder_conversations,
|
|
conversations::import_local_conversations,
|
|
conversations::get_folder_conversation,
|
|
conversations::list_folders,
|
|
conversations::get_stats,
|
|
conversations::get_sidebar_data,
|
|
conversations::create_conversation,
|
|
conversations::update_conversation_status,
|
|
conversations::update_conversation_title,
|
|
conversations::update_conversation_external_id,
|
|
conversations::delete_conversation,
|
|
folders::load_folder_history,
|
|
folders::get_folder,
|
|
folders::add_folder_to_history,
|
|
folders::set_folder_parent_branch,
|
|
folders::remove_folder_from_history,
|
|
folders::create_folder_directory,
|
|
folders::clone_repository,
|
|
folders::get_git_branch,
|
|
folders::git_init,
|
|
folders::git_pull,
|
|
folders::git_fetch,
|
|
folders::git_push,
|
|
folders::git_new_branch,
|
|
folders::git_worktree_add,
|
|
folders::git_checkout,
|
|
folders::git_list_branches,
|
|
folders::git_stash,
|
|
folders::git_stash_pop,
|
|
folders::git_status,
|
|
folders::git_is_tracked,
|
|
folders::git_diff,
|
|
folders::git_diff_with_branch,
|
|
folders::git_show_diff,
|
|
folders::git_show_file,
|
|
folders::git_commit,
|
|
folders::git_rollback_file,
|
|
folders::git_add_files,
|
|
folders::git_list_all_branches,
|
|
folders::git_merge,
|
|
folders::git_rebase,
|
|
folders::git_delete_branch,
|
|
folders::save_folder_opened_conversations,
|
|
folders::start_file_tree_watch,
|
|
folders::stop_file_tree_watch,
|
|
folders::get_file_tree,
|
|
folders::read_file_preview,
|
|
folders::read_file_for_edit,
|
|
folders::save_file_content,
|
|
folders::save_file_copy,
|
|
folders::rename_file_tree_entry,
|
|
folders::delete_file_tree_entry,
|
|
folders::git_log,
|
|
folders::git_commit_branches,
|
|
windows::open_folder_window,
|
|
windows::open_commit_window,
|
|
windows::open_settings_window,
|
|
windows::list_open_folders,
|
|
windows::focus_folder_window,
|
|
system_settings::get_system_proxy_settings,
|
|
system_settings::update_system_proxy_settings,
|
|
system_settings::get_system_language_settings,
|
|
system_settings::update_system_language_settings,
|
|
acp_commands::acp_preflight,
|
|
acp_commands::acp_connect,
|
|
acp_commands::acp_prompt,
|
|
acp_commands::acp_set_mode,
|
|
acp_commands::acp_set_config_option,
|
|
acp_commands::acp_cancel,
|
|
acp_commands::acp_respond_permission,
|
|
acp_commands::acp_disconnect,
|
|
acp_commands::acp_list_connections,
|
|
acp_commands::acp_list_agents,
|
|
acp_commands::acp_clear_binary_cache,
|
|
acp_commands::acp_download_agent_binary,
|
|
acp_commands::acp_detect_agent_local_version,
|
|
acp_commands::acp_prepare_npx_agent,
|
|
acp_commands::acp_prepare_uvx_agent,
|
|
acp_commands::acp_uninstall_agent,
|
|
acp_commands::acp_update_agent_preferences,
|
|
acp_commands::acp_reorder_agents,
|
|
acp_commands::acp_list_agent_skills,
|
|
acp_commands::acp_read_agent_skill,
|
|
acp_commands::acp_save_agent_skill,
|
|
acp_commands::acp_delete_agent_skill,
|
|
folder_commands::list_folder_commands,
|
|
folder_commands::create_folder_command,
|
|
folder_commands::update_folder_command,
|
|
folder_commands::delete_folder_command,
|
|
folder_commands::reorder_folder_commands,
|
|
folder_commands::bootstrap_folder_commands_from_package_json,
|
|
terminal_commands::terminal_spawn,
|
|
terminal_commands::terminal_write,
|
|
terminal_commands::terminal_resize,
|
|
terminal_commands::terminal_kill,
|
|
terminal_commands::terminal_list,
|
|
mcp_commands::mcp_scan_local,
|
|
mcp_commands::mcp_list_marketplaces,
|
|
mcp_commands::mcp_search_marketplace,
|
|
mcp_commands::mcp_get_marketplace_server_detail,
|
|
mcp_commands::mcp_install_from_marketplace,
|
|
mcp_commands::mcp_upsert_local_server,
|
|
mcp_commands::mcp_set_server_apps,
|
|
mcp_commands::mcp_remove_server,
|
|
])
|
|
.build(tauri::generate_context!())
|
|
.expect("error while building tauri application")
|
|
.run(|_app, event| {
|
|
if let tauri::RunEvent::ExitRequested { .. } = event {
|
|
APP_QUITTING.store(true, Ordering::Relaxed);
|
|
}
|
|
});
|
|
}
|