main.rs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
  2. mod commands;
  3. mod panic_guard;
  4. mod proxy;
  5. mod types;
  6. #[tauri::command]
  7. fn set_proxy_env(config: proxy::ProxyConfig) -> String {
  8. let summary = proxy::apply_proxy_env(&config);
  9. eprintln!("[proxy] live update: {summary}");
  10. summary
  11. }
  12. #[tauri::command]
  13. fn log_error(message: String, stack: String, component_stack: String) {
  14. eprintln!("[frontend-error] message: {}", message);
  15. if !stack.is_empty() {
  16. eprintln!("[frontend-error] stack: {}", stack);
  17. }
  18. if !component_stack.is_empty() {
  19. eprintln!("[frontend-error] component-stack: {}", component_stack);
  20. }
  21. }
  22. #[tauri::command]
  23. fn log_diagnostic(message: String) {
  24. eprintln!("[frontend-diagnostic] message: {}", message);
  25. }
  26. fn main() {
  27. tauri::Builder::default()
  28. .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
  29. use tauri::Manager;
  30. if let Some(window) = app.get_webview_window("main") {
  31. let _ = window.unminimize();
  32. let _ = window.show();
  33. let _ = window.set_focus();
  34. }
  35. }))
  36. .plugin(tauri_plugin_opener::init())
  37. .plugin(tauri_plugin_dialog::init())
  38. .plugin(tauri_plugin_store::Builder::default().build())
  39. .plugin(tauri_plugin_http::init())
  40. .plugin(tauri_plugin_updater::Builder::new().build())
  41. .setup(|app| {
  42. use tauri::Manager;
  43. if let Ok(dir) = app.path().resource_dir() {
  44. commands::fs::set_resource_dir_hint(dir);
  45. }
  46. if let Ok(dir) = app.path().app_data_dir() {
  47. let store_path = dir.join("app-state.json");
  48. eprintln!("[proxy] reading from {}", store_path.display());
  49. let summary = proxy::apply_proxy_env_from_store(&store_path);
  50. eprintln!("[proxy] {summary}");
  51. } else {
  52. eprintln!("[proxy] could not resolve app_data_dir");
  53. }
  54. app.manage(commands::claude_cli::ClaudeCliState::default());
  55. app.manage(commands::codex_cli::CodexCliState::default());
  56. app.manage(commands::cursor_cli::CursorProxyState::default());
  57. app.manage(commands::file_sync::FileSyncState::default());
  58. app.manage(commands::mcp_stdio::McpStdioState::default());
  59. app.manage(commands::writing_wake_lock::WritingWakeLockManager::default());
  60. Ok(())
  61. })
  62. .invoke_handler(tauri::generate_handler![
  63. commands::fs::read_file,
  64. commands::fs::write_file,
  65. commands::fs::write_file_if_absent,
  66. commands::fs::write_export_file,
  67. commands::fs::write_file_atomic,
  68. commands::fs::list_directory,
  69. commands::fs::copy_file,
  70. commands::fs::copy_directory,
  71. commands::fs::preprocess_file,
  72. commands::fs::delete_file,
  73. commands::fs::find_related_wiki_pages,
  74. commands::fs::create_directory,
  75. commands::fs::file_exists,
  76. commands::fs::get_file_modified_time,
  77. commands::fs::get_file_size,
  78. commands::fs::get_file_md5,
  79. commands::fs::read_file_as_base64,
  80. commands::fs::get_executable_dir,
  81. commands::fs::get_resource_dir,
  82. commands::project::create_project,
  83. commands::project::open_project,
  84. commands::project::open_project_folder,
  85. commands::project::open_file_location,
  86. commands::vectorstore::vector_upsert,
  87. commands::vectorstore::vector_search,
  88. commands::vectorstore::vector_delete,
  89. commands::vectorstore::vector_count,
  90. commands::vectorstore::vector_upsert_chunks,
  91. commands::vectorstore::vector_search_chunks,
  92. commands::vectorstore::vector_delete_page,
  93. commands::vectorstore::vector_count_chunks,
  94. commands::vectorstore::vector_legacy_row_count,
  95. commands::vectorstore::vector_drop_legacy,
  96. commands::claude_cli::claude_cli_detect,
  97. commands::claude_cli::claude_cli_spawn,
  98. commands::claude_cli::claude_cli_kill,
  99. commands::codex_cli::codex_cli_detect,
  100. commands::codex_cli::codex_cli_spawn,
  101. commands::codex_cli::codex_cli_kill,
  102. commands::cursor_cli::cursor_cli_detect,
  103. commands::cursor_cli::cursor_proxy_status,
  104. commands::cursor_cli::cursor_proxy_ensure,
  105. commands::cursor_cli::cursor_proxy_stop,
  106. commands::extract_images::extract_pdf_images_cmd,
  107. commands::extract_images::extract_office_images_cmd,
  108. commands::extract_images::extract_and_save_pdf_images_cmd,
  109. commands::extract_images::extract_and_save_office_images_cmd,
  110. commands::file_sync::start_project_file_watcher,
  111. commands::file_sync::stop_project_file_watcher,
  112. commands::file_sync::rescan_project_files,
  113. commands::file_sync::get_file_change_queue,
  114. commands::file_sync::retry_file_change_task,
  115. commands::file_sync::ignore_file_change_task,
  116. commands::mcp_stdio::mcp_stdio_spawn,
  117. commands::mcp_stdio::mcp_stdio_write,
  118. commands::mcp_stdio::mcp_stdio_read,
  119. commands::mcp_stdio::mcp_stdio_kill,
  120. commands::backup::export_backup,
  121. commands::backup::import_backup,
  122. commands::backup::read_backup_manifest,
  123. commands::writing_wake_lock::acquire_writing_wake_lock,
  124. commands::writing_wake_lock::release_writing_wake_lock,
  125. set_proxy_env,
  126. log_error,
  127. log_diagnostic,
  128. ])
  129. .on_window_event(|window, event| {
  130. if let tauri::WindowEvent::CloseRequested { api, .. } = event {
  131. #[cfg(target_os = "macos")]
  132. {
  133. let _ = window.hide();
  134. api.prevent_close();
  135. }
  136. #[cfg(not(target_os = "macos"))]
  137. {
  138. use tauri::Manager;
  139. api.prevent_close();
  140. let win = window.clone();
  141. let app = window.app_handle().clone();
  142. tauri::async_runtime::spawn(async move {
  143. use tauri_plugin_dialog::DialogExt;
  144. let confirmed = app
  145. .dialog()
  146. .message("确定要退出小说写作助手吗?")
  147. .title("确认退出")
  148. .kind(tauri_plugin_dialog::MessageDialogKind::Warning)
  149. .blocking_show();
  150. if confirmed {
  151. let _ = win.destroy();
  152. }
  153. });
  154. }
  155. }
  156. })
  157. .build(tauri::generate_context!())
  158. .unwrap_or_else(|e| {
  159. let msg = format!("应用程序启动失败: {e}");
  160. eprintln!("{msg}");
  161. #[cfg(windows)]
  162. {
  163. use std::ffi::OsStr;
  164. use std::os::windows::ffi::OsStrExt;
  165. extern "system" {
  166. fn MessageBoxW(
  167. hwnd: *mut std::ffi::c_void,
  168. lp_text: *const u16,
  169. lp_caption: *const u16,
  170. u_type: u32,
  171. ) -> i32;
  172. }
  173. fn to_wide(s: &str) -> Vec<u16> {
  174. OsStr::new(s).encode_wide().chain(std::iter::once(0)).collect()
  175. }
  176. let text = to_wide(&msg);
  177. let caption = to_wide("启动错误");
  178. unsafe {
  179. MessageBoxW(
  180. std::ptr::null_mut(),
  181. text.as_ptr(),
  182. caption.as_ptr(),
  183. 0x10,
  184. );
  185. }
  186. }
  187. std::process::exit(1);
  188. })
  189. .run(|app, event| {
  190. #[cfg(target_os = "macos")]
  191. if let tauri::RunEvent::Reopen { has_visible_windows, .. } = event {
  192. if !has_visible_windows {
  193. use tauri::Manager;
  194. if let Some(window) = app.get_webview_window("main") {
  195. let _ = window.show();
  196. let _ = window.set_focus();
  197. }
  198. }
  199. }
  200. let _ = (app, event);
  201. });
  202. }