diff --git a/electron/main.ts b/electron/main.ts index 57a9112..fe7ccb7 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -747,6 +747,10 @@ const getWindowCloseBehavior = (): WindowCloseBehavior => { return behavior === 'tray' || behavior === 'quit' ? behavior : 'ask' } +const isSilentStartupEnabled = (): boolean => { + return configService?.get('silentStartup') === true +} + const requestMainWindowCloseConfirmation = (win: BrowserWindow): void => { if (isClosePromptVisible) return isClosePromptVisible = true @@ -3727,21 +3731,31 @@ function checkForUpdatesOnStartup() { } app.whenReady().then(async () => { - // 立即创建 Splash 窗口,确保用户尽快看到反馈 - createSplashWindow() + // 先初始化配置,以便在启动早期判定是否需要静默启动 + configService = new ConfigService() + applyAutoUpdateChannel('startup') + syncLaunchAtStartupPreference() + const onboardingDone = configService.get('onboardingDone') === true + const startInBackground = onboardingDone && isSilentStartupEnabled() + shouldShowMain = onboardingDone - // 等待 Splash 页面加载完成后再推送进度 - if (splashWindow) { - await new Promise((resolve) => { - if (splashWindow!.webContents.isLoading()) { - splashWindow!.webContents.once('did-finish-load', () => resolve()) - } else { - resolve() - } - }) - splashWindow.webContents - .executeJavaScript(`setVersion(${JSON.stringify(app.getVersion())})`) - .catch(() => {}) + if (!startInBackground) { + // 非静默模式下显示 Splash,提供启动反馈 + createSplashWindow() + + // 等待 Splash 页面加载完成后再推送进度 + if (splashWindow) { + await new Promise((resolve) => { + if (splashWindow!.webContents.isLoading()) { + splashWindow!.webContents.once('did-finish-load', () => resolve()) + } else { + resolve() + } + }) + splashWindow.webContents + .executeJavaScript(`setVersion(${JSON.stringify(app.getVersion())})`) + .catch(() => {}) + } } const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) @@ -3770,13 +3784,7 @@ app.whenReady().then(async () => { }) } - // 初始化配置服务 updateSplashProgress(5, '正在加载配置...') - configService = new ConfigService() - applyAutoUpdateChannel('startup') - syncLaunchAtStartupPreference() - const onboardingDone = configService.get('onboardingDone') === true - shouldShowMain = onboardingDone // 将用户主题配置推送给 Splash 窗口 if (splashWindow && !splashWindow.isDestroyed()) { @@ -3943,6 +3951,8 @@ app.whenReady().then(async () => { if (!onboardingDone) { createOnboardingWindow() + } else if (startInBackground && tray) { + mainWindow?.hide() } else { mainWindow?.show() } diff --git a/electron/services/config.ts b/electron/services/config.ts index 35a382d..ff06ccd 100644 --- a/electron/services/config.ts +++ b/electron/services/config.ts @@ -36,6 +36,7 @@ interface ConfigSchema { language: string logEnabled: boolean launchAtStartup?: boolean + silentStartup?: boolean llmModelPath: string whisperModelName: string whisperModelDir: string @@ -163,6 +164,7 @@ export class ConfigService { themeId: 'cloud-dancer', language: 'zh-CN', logEnabled: false, + silentStartup: false, llmModelPath: '', whisperModelName: 'base', whisperModelDir: '', diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 9727fef..95a4404 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -195,6 +195,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { const [launchAtStartup, setLaunchAtStartup] = useState(false) const [launchAtStartupSupported, setLaunchAtStartupSupported] = useState(isWindows || isMac) const [launchAtStartupReason, setLaunchAtStartupReason] = useState('') + const [silentStartup, setSilentStartup] = useState(false) const [windowCloseBehavior, setWindowCloseBehavior] = useState('ask') const [quoteLayout, setQuoteLayout] = useState('quote-top') const [updateChannel, setUpdateChannel] = useState('stable') @@ -222,6 +223,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { const [isFetchingImageKey, setIsFetchingImageKey] = useState(false) const [isCheckingUpdate, setIsCheckingUpdate] = useState(false) const [isUpdatingLaunchAtStartup, setIsUpdatingLaunchAtStartup] = useState(false) + const [isUpdatingSilentStartup, setIsUpdatingSilentStartup] = useState(false) const [appVersion, setAppVersion] = useState('') const [message, setMessage] = useState<{ text: string; success: boolean } | null>(null) const [showDecryptKey, setShowDecryptKey] = useState(false) @@ -445,6 +447,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { const savedMessagePushFilterList = await configService.getMessagePushFilterList() const contactsResult = await window.electronAPI.chat.getContacts({ lite: true }) const savedLaunchAtStartupStatus = await window.electronAPI.app.getLaunchAtStartupStatus() + const savedSilentStartup = await configService.getSilentStartup() const savedWindowCloseBehavior = await configService.getWindowCloseBehavior() const savedQuoteLayout = await configService.getQuoteLayout() const savedUpdateChannel = await configService.getUpdateChannel() @@ -502,6 +505,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { setLaunchAtStartup(savedLaunchAtStartupStatus.enabled) setLaunchAtStartupSupported(savedLaunchAtStartupStatus.supported) setLaunchAtStartupReason(savedLaunchAtStartupStatus.reason || '') + setSilentStartup(savedSilentStartup) setWindowCloseBehavior(savedWindowCloseBehavior) setQuoteLayout(savedQuoteLayout) if (savedUpdateChannel) { @@ -615,6 +619,21 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { } } + const handleSilentStartupChange = async (enabled: boolean) => { + if (isUpdatingSilentStartup) return + + try { + setIsUpdatingSilentStartup(true) + await configService.setSilentStartup(enabled) + setSilentStartup(enabled) + showMessage(enabled ? '已开启静默启动' : '已关闭静默启动', true) + } catch (e: any) { + showMessage(`设置静默启动失败: ${e?.message || String(e)}`, false) + } finally { + setIsUpdatingSilentStartup(false) + } + } + const refreshWhisperStatus = async (modelDirValue = whisperModelDir) => { try { const result = await window.electronAPI.whisper?.getModelStatus() @@ -1684,6 +1703,35 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
+
+ + + 开启后,无论手动启动还是开机自启动,都会先驻留到系统托盘,不主动显示主窗口。 + +
+ + {isUpdatingSilentStartup + ? '保存中...' + : (silentStartup ? '已开启' : '已关闭')} + + +
+
+ +
+
设置点击关闭按钮后的默认行为;选择“每次询问”时会弹出关闭确认。 diff --git a/src/services/config.ts b/src/services/config.ts index cf9e391..3b0863a 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -15,6 +15,7 @@ export const CONFIG_KEYS = { WINDOW_BOUNDS: 'windowBounds', CACHE_PATH: 'cachePath', LAUNCH_AT_STARTUP: 'launchAtStartup', + SILENT_STARTUP: 'silentStartup', EXPORT_PATH: 'exportPath', AGREEMENT_ACCEPTED: 'agreementAccepted', @@ -321,6 +322,17 @@ export async function setLaunchAtStartup(enabled: boolean): Promise { await config.set(CONFIG_KEYS.LAUNCH_AT_STARTUP, enabled) } +// 获取静默启动偏好 +export async function getSilentStartup(): Promise { + const value = await config.get(CONFIG_KEYS.SILENT_STARTUP) + return value === true +} + +// 设置静默启动偏好 +export async function setSilentStartup(enabled: boolean): Promise { + await config.set(CONFIG_KEYS.SILENT_STARTUP, enabled) +} + // 获取 LLM 模型路径 export async function getLlmModelPath(): Promise { const value = await config.get(CONFIG_KEYS.LLM_MODEL_PATH)