diff --git a/src-tauri/src/app_settings.rs b/src-tauri/src/app_settings.rs index eca0203..54edabe 100644 --- a/src-tauri/src/app_settings.rs +++ b/src-tauri/src/app_settings.rs @@ -178,6 +178,7 @@ pub(crate) fn software_settings( Ok(SoftwareSettings { close_behavior: config.close_behavior, autostart_enabled: app_autostart_enabled(app)?, + start_core_on_launch: config.start_core_on_launch, silent_start_enabled: config.silent_start, }) } @@ -206,13 +207,16 @@ pub(crate) fn save_software_settings( } let config = if previous_config.close_behavior == settings.close_behavior + && previous_config.start_core_on_launch == settings.start_core_on_launch && previous_config.silent_start == settings.silent_start_enabled { previous_config } else { - match gui_config_state - .set_software_preferences(settings.close_behavior, settings.silent_start_enabled) - { + match gui_config_state.set_software_preferences( + settings.close_behavior, + settings.start_core_on_launch, + settings.silent_start_enabled, + ) { Ok(config) => config, Err(error) => { let rollback_error = autostart_changed diff --git a/src-tauri/src/core_config/settings.rs b/src-tauri/src/core_config/settings.rs index 7ffb6a2..3de0c89 100644 --- a/src-tauri/src/core_config/settings.rs +++ b/src-tauri/src/core_config/settings.rs @@ -847,6 +847,9 @@ pub(crate) fn load_or_create_gui_config() -> Result { if presence.close_behavior.is_none() { changed = true; } + if presence.start_core_on_launch.is_none() { + changed = true; + } if presence.silent_start.is_none() { changed = true; } @@ -1178,6 +1181,7 @@ pub(crate) fn write_gui_config_to_path( ("allow-lan", value(config.allow_lan)), ("host", value(config.host.as_str())), ("run-on-startup", value(config.run_on_startup)), + ("start-core-on-launch", value(config.start_core_on_launch)), ("silent-start", value(config.silent_start)), ("close-behavior", value(config.close_behavior.as_str())), ("auth-dir", value(config.auth_dir.as_str())), diff --git a/src-tauri/src/core_runtime.rs b/src-tauri/src/core_runtime.rs index 803005a..77c80a5 100644 --- a/src-tauri/src/core_runtime.rs +++ b/src-tauri/src/core_runtime.rs @@ -1820,6 +1820,10 @@ pub(crate) fn should_start_hidden(config: &GuiConfigFile) -> bool { config.silent_start && cfg!(any(target_os = "windows", target_os = "macos")) } +pub(crate) fn should_start_core_on_launch(config: &GuiConfigFile) -> bool { + config.start_core_on_launch +} + pub(crate) fn configure_initial_main_window( app_handle: &tauri::AppHandle, start_hidden: bool, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index f7be081..d1a6962 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -552,6 +552,7 @@ struct GuiConfigFile { allow_lan: bool, host: String, run_on_startup: bool, + start_core_on_launch: bool, silent_start: bool, close_behavior: WindowsCloseBehavior, window_width: Option, @@ -662,6 +663,7 @@ impl Default for GuiConfigFile { allow_lan: false, host: "127.0.0.1".to_string(), run_on_startup: false, + start_core_on_launch: true, silent_start: false, close_behavior: WindowsCloseBehavior::Ask, window_width: Some(DEFAULT_MAIN_WINDOW_WIDTH), @@ -698,6 +700,7 @@ struct GuiConfigPresence { api_access_remarks: Option>, management_secret_key: Option, close_behavior: Option, + start_core_on_launch: Option, silent_start: Option, usage_statistics_enabled: Option, plugins_enabled: Option, @@ -725,6 +728,7 @@ struct GuiSettings { struct SoftwareSettings { close_behavior: WindowsCloseBehavior, autostart_enabled: bool, + start_core_on_launch: bool, silent_start_enabled: bool, } @@ -733,6 +737,7 @@ struct SoftwareSettings { struct SoftwareSettingsInput { close_behavior: WindowsCloseBehavior, autostart_enabled: bool, + start_core_on_launch: bool, silent_start_enabled: bool, } @@ -1505,10 +1510,12 @@ impl GuiConfigState { fn set_software_preferences( &self, close_behavior: WindowsCloseBehavior, + start_core_on_launch: bool, silent_start: bool, ) -> Result { self.update(|config| { config.close_behavior = close_behavior; + config.start_core_on_launch = start_core_on_launch; config.silent_start = silent_start; Ok(()) }) @@ -1847,7 +1854,7 @@ fn main() { Err(error) => eprintln!("自动安装 CPA 离线内核失败: {error}"), } - if config.run_on_startup { + if should_start_core_on_launch(&config) { if let Err(error) = start_core_process_inner(process_state.inner(), &config) { eprintln!("自动启动 CPA 内核失败: {error}"); } diff --git a/src-tauri/src/tests/app_settings.rs b/src-tauri/src/tests/app_settings.rs index 8149a67..281f1d9 100644 --- a/src-tauri/src/tests/app_settings.rs +++ b/src-tauri/src/tests/app_settings.rs @@ -77,6 +77,7 @@ fn gui_config_defaults_are_stable() { assert!(content.contains("port = 8317")); assert!(content.contains("allow-lan = false")); assert!(content.contains("run-on-startup = false")); + assert!(content.contains("start-core-on-launch = true")); assert!(content.contains("silent-start = false")); assert!(content.contains("close-behavior = \"ask\"")); assert!(content.contains("window-width = 1531")); @@ -119,6 +120,7 @@ fn gui_window_size_round_trips_through_portable_config() { #[test] fn silent_start_defaults_off_and_requires_tray_support() { let legacy = toml::from_str::("port = 8317\n").unwrap(); + assert!(legacy.start_core_on_launch); assert!(!legacy.silent_start); assert!(!should_start_hidden(&legacy)); @@ -132,6 +134,18 @@ fn silent_start_defaults_off_and_requires_tray_support() { ); } +#[test] +fn core_start_on_launch_defaults_on_and_can_be_disabled() { + let legacy = toml::from_str::("port = 8317\n").unwrap(); + assert!(should_start_core_on_launch(&legacy)); + + let disabled = GuiConfigFile { + start_core_on_launch: false, + ..GuiConfigFile::default() + }; + assert!(!should_start_core_on_launch(&disabled)); +} + #[test] fn gui_window_size_is_clamped_and_requires_both_dimensions() { let mut config = GuiConfigFile { diff --git a/src-tauri/src/tests/core_config.rs b/src-tauri/src/tests/core_config.rs index ee0db50..29f1e1d 100644 --- a/src-tauri/src/tests/core_config.rs +++ b/src-tauri/src/tests/core_config.rs @@ -767,6 +767,7 @@ fn startup_preserves_all_user_owned_yaml_and_only_applies_gui_managed_values() { allow_lan: true, host: "0.0.0.0".to_string(), run_on_startup: false, + start_core_on_launch: true, silent_start: false, close_behavior: WindowsCloseBehavior::Ask, window_width: None, diff --git a/src-tauri/src/usage.rs b/src-tauri/src/usage.rs index a225ec5..f2815e1 100644 --- a/src-tauri/src/usage.rs +++ b/src-tauri/src/usage.rs @@ -3024,6 +3024,7 @@ mod tests { allow_lan: false, host: "127.0.0.1".to_string(), run_on_startup: false, + start_core_on_launch: true, silent_start: false, close_behavior: crate::WindowsCloseBehavior::Ask, window_width: None, diff --git a/src/i18n/ja.ts b/src/i18n/ja.ts index 2c83c5d..2fa359b 100644 --- a/src/i18n/ja.ts +++ b/src/i18n/ja.ts @@ -365,6 +365,8 @@ export const jaOverrides = { 'config.software.title': 'ソフトウェア設定', 'config.software.autostart': 'システム起動時に開始', 'config.software.autostartDescription': 'サインイン後に EasyCLIProxyAPI を自動的に起動します', + 'config.software.startCoreOnLaunch': '起動時にコアを開始', + 'config.software.startCoreOnLaunchDescription': 'EasyCLIProxyAPI の起動時に CPA コアを自動的に開始します', 'config.software.silentStart': 'サイレント起動', 'config.software.silentStartDescription': 'メインウィンドウを表示せず、システムトレイで直接起動します', 'config.software.closeBehavior': '終了動作', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 3850d9a..c656185 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -300,6 +300,8 @@ export const en: Record = { 'config.software.title': 'Software Settings', 'config.software.autostart': 'Launch at startup', 'config.software.autostartDescription': 'Automatically launch EasyCLIProxyAPI when you sign in', + 'config.software.startCoreOnLaunch': 'Start Core on Launch', + 'config.software.startCoreOnLaunchDescription': 'Automatically start the CPA core whenever EasyCLIProxyAPI launches', 'config.software.silentStart': 'Silent Launch', 'config.software.silentStartDescription': 'Start without showing the main window and run directly in the system tray', 'config.software.closeBehavior': 'Close behavior', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index e6764ef..6e3c340 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -298,6 +298,8 @@ export const zhCN = { 'config.software.title': '软件设置', 'config.software.autostart': '开机自启', 'config.software.autostartDescription': '登录系统后自动启动 EasyCLIProxyAPI', + 'config.software.startCoreOnLaunch': '启动时启动内核', + 'config.software.startCoreOnLaunchDescription': '每次启动 EasyCLIProxyAPI 时自动启动 CPA 内核', 'config.software.silentStart': '静默启动', 'config.software.silentStartDescription': '启动应用时不显示主窗口,直接在系统托盘运行', 'config.software.closeBehavior': '关闭行为', diff --git a/src/pages/ConfigPanel.tsx b/src/pages/ConfigPanel.tsx index c5b1515..4acf9e2 100644 --- a/src/pages/ConfigPanel.tsx +++ b/src/pages/ConfigPanel.tsx @@ -18,6 +18,7 @@ import { Settings2, ShieldCheck, Sparkles, + Power, Trash2, X, } from 'lucide-react'; @@ -75,6 +76,7 @@ type NetworkDraftDirty = Record; type SoftwareSettings = { closeBehavior: CloseBehavior; autostartEnabled: boolean; + startCoreOnLaunch: boolean; silentStartEnabled: boolean; }; @@ -103,6 +105,7 @@ export function ConfigPanelPage() { const [softwareSettingsLoading, setSoftwareSettingsLoading] = useState(true); const [softwareCloseBehaviorDraft, setSoftwareCloseBehaviorDraft] = useState('ask'); const [softwareAutostartDraft, setSoftwareAutostartDraft] = useState(false); + const [softwareStartCoreDraft, setSoftwareStartCoreDraft] = useState(true); const [softwareSilentStartDraft, setSoftwareSilentStartDraft] = useState(false); const [softwareSavedStatusVisible, setSoftwareSavedStatusVisible] = useState(false); const [loading, setLoading] = useState(true); @@ -237,6 +240,7 @@ export function ConfigPanelPage() { setSoftwareSettings(result); setSoftwareCloseBehaviorDraft(result.closeBehavior); setSoftwareAutostartDraft(result.autostartEnabled); + setSoftwareStartCoreDraft(result.startCoreOnLaunch); setSoftwareSilentStartDraft(result.silentStartEnabled); } catch (error) { setSoftwareSettings(null); @@ -526,6 +530,7 @@ export function ConfigPanelPage() { if ( softwareCloseBehaviorDraft === softwareSettings.closeBehavior && softwareAutostartDraft === softwareSettings.autostartEnabled + && softwareStartCoreDraft === softwareSettings.startCoreOnLaunch && softwareSilentStartDraft === softwareSettings.silentStartEnabled ) return; @@ -535,18 +540,21 @@ export function ConfigPanelPage() { settings: { closeBehavior: softwareCloseBehaviorDraft, autostartEnabled: softwareAutostartDraft, + startCoreOnLaunch: softwareStartCoreDraft, silentStartEnabled: softwareSilentStartDraft, }, }); setSoftwareSettings(result); setSoftwareCloseBehaviorDraft(result.closeBehavior); setSoftwareAutostartDraft(result.autostartEnabled); + setSoftwareStartCoreDraft(result.startCoreOnLaunch); setSoftwareSilentStartDraft(result.silentStartEnabled); setSoftwareSavedStatusVisible(true); showNotice(t('config.notice.softwareUpdated'), 'success'); } catch (error) { setSoftwareCloseBehaviorDraft(softwareSettings.closeBehavior); setSoftwareAutostartDraft(softwareSettings.autostartEnabled); + setSoftwareStartCoreDraft(softwareSettings.startCoreOnLaunch); setSoftwareSilentStartDraft(softwareSettings.silentStartEnabled); setSoftwareSavedStatusVisible(false); showNotice(t('config.error.saveFailed', { error: String(error) }), 'error'); @@ -585,8 +593,11 @@ export function ConfigPanelPage() { && softwareAutostartDraft !== softwareSettings.autostartEnabled; const softwareSilentStartDirty = softwareSettings !== null && softwareSilentStartDraft !== softwareSettings.silentStartEnabled; + const softwareStartCoreDirty = softwareSettings !== null + && softwareStartCoreDraft !== softwareSettings.startCoreOnLaunch; const softwareSettingsDirty = softwareCloseBehaviorDirty || softwareAutostartDirty + || softwareStartCoreDirty || softwareSilentStartDirty; const softwareStatusLabel = softwareSettingsLoading ? t('common.loading') @@ -1272,6 +1283,31 @@ export function ConfigPanelPage() { +
+
+ +
+ {t('config.software.startCoreOnLaunch')} + {t('config.software.startCoreOnLaunchDescription')} +
+
+ +