From 2ea7080a7e0e6a33f296f1222c1c27c95d3eac71 Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Fri, 24 Jul 2026 16:03:12 +0800 Subject: [PATCH] feat: auto-install the bundled kernel version and add an X button to dismiss the launch popup --- src-tauri/src/main.rs | 48 +++++++++++++++++++++++++++++++++++++++++++ src/App.tsx | 11 ++++++++++ src/styles.css | 33 +++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index c5e9f8f..44eeb51 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5627,6 +5627,33 @@ fn install_bundled_core( result } +fn core_needs_bundled_bootstrap(install_dir: &Path) -> bool { + find_core_binary(install_dir).is_none() +} + +fn auto_install_bundled_core_if_missing(app: &tauri::AppHandle) -> Result { + let install_dir = core_install_dir()?; + if !core_needs_bundled_bootstrap(&install_dir) { + return Ok(false); + } + + let (info, archive_path) = bundled_core_archive()? + .ok_or_else(|| "未检测到 CPA 内核,且当前发行包没有匹配的离线内核".to_string())?; + let window = app + .get_webview_window("main") + .map(|webview| webview.as_ref().window()) + .ok_or_else(|| "无法获取主窗口,不能自动安装离线内核".to_string())?; + let state = app.state::(); + state.start(CancellationToken::new(), Some(info.version.clone()))?; + let result = install_bundled_core_inner(&window, state.inner(), &info, &archive_path); + if result.is_err() { + let _ = cleanup_core_work_dirs(); + } + state.finish(&window, result.clone()); + result?; + Ok(true) +} + #[tauri::command] fn cancel_core_install(state: tauri::State<'_, CoreDownloadState>) { state.cancel(); @@ -10545,6 +10572,12 @@ fn main() { return; }; + match auto_install_bundled_core_if_missing(&core_app) { + Ok(true) => eprintln!("未检测到 CPA 内核,已自动安装内置离线版本"), + Ok(false) => {} + Err(error) => eprintln!("自动安装 CPA 离线内核失败: {error}"), + } + if config.run_on_startup { if let Err(error) = start_core_process_inner(process_state.inner(), &config) { eprintln!("自动启动 CPA 内核失败: {error}"); @@ -12612,6 +12645,21 @@ mod tests { fs::remove_dir_all(root).unwrap(); } + #[test] + fn bundled_core_bootstrap_runs_only_when_no_core_binary_exists() { + let root = agent_test_home("bundled-bootstrap-detection"); + let install_dir = root.join("cpa-core"); + + assert!(core_needs_bundled_bootstrap(&install_dir)); + + let existing_version = install_dir.join("existing-version"); + fs::create_dir_all(&existing_version).unwrap(); + fs::write(existing_version.join(core_binary_name()), b"existing core").unwrap(); + + assert!(!core_needs_bundled_bootstrap(&install_dir)); + fs::remove_dir_all(root).unwrap(); + } + #[test] fn bundled_core_locations_include_macos_app_resources() { let contents_dir = agent_test_home("bundled-macos-resources") diff --git a/src/App.tsx b/src/App.tsx index b6dc049..000a626 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,6 +18,7 @@ import { PackageOpen, ServerCog, Settings, + X, } from 'lucide-react'; import appLogo from './assets/logo.jpg'; import { CoreRuntimeProvider, useCoreRuntime } from './coreRuntime'; @@ -387,6 +388,16 @@ function AppContent() { } }} > +
{t('app.close.eyebrow')}

{t('app.close.title')}

diff --git a/src/styles.css b/src/styles.css index 185bba2..0b8feed 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1542,6 +1542,7 @@ input:focus-visible { } .close-dialog { + position: relative; display: grid; gap: 18px; width: min(430px, 100%); @@ -1556,6 +1557,38 @@ input:focus-visible { .close-dialog-heading { display: grid; gap: 5px; + padding-right: 34px; +} + +.close-dialog-dismiss { + position: absolute; + top: 12px; + right: 12px; + display: grid; + width: 32px; + height: 32px; + padding: 0; + place-items: center; + border: 1px solid transparent; + border-radius: 7px; + background: transparent; + color: #7a746c; +} + +.close-dialog-dismiss:hover:not(:disabled) { + border-color: #d5d2cb; + background: #f0ede6; + color: #2d2a26; +} + +.close-dialog-dismiss:focus-visible { + outline: 3px solid rgba(66, 99, 127, 0.3); + outline-offset: 2px; +} + +.close-dialog-dismiss:disabled { + cursor: not-allowed; + opacity: 0.45; } .close-dialog-heading span {