feat: implement launch at startup functionality in zn-ai
- Added a new setting for "launch at startup" in the GeneralSettingsPanel. - Integrated the setting with the existing settings store and IPC mechanisms. - Implemented platform-specific logic for enabling/disabling startup behavior in the main process. - Created a new service for managing launch at startup settings, including Linux desktop entry creation. - Added unit tests for the new functionality and ensured existing tests are updated accordingly. - Updated i18n messages for the new setting in English, Chinese, and Japanese.
This commit is contained in:
@@ -433,6 +433,8 @@ export const messages: I18nMessages = {
|
||||
description: 'Customize the look and feel of the application.',
|
||||
themeSection: 'Theme Settings',
|
||||
languageSection: 'Language',
|
||||
launchAtStartupTitle: 'Launch at startup',
|
||||
launchAtStartupDescription: 'Start the app automatically after you sign in',
|
||||
gatewayTitle: 'Gateway',
|
||||
gatewayDescription: 'View the current Gateway state and basic runtime controls.',
|
||||
gatewayStatusLabel: 'Status',
|
||||
@@ -911,6 +913,8 @@ export const messages: I18nMessages = {
|
||||
description: '自定义应用的外观与使用体验。',
|
||||
themeSection: '主题设置',
|
||||
languageSection: '语言',
|
||||
launchAtStartupTitle: '开机自动启动',
|
||||
launchAtStartupDescription: '登录系统后自动启动应用',
|
||||
gatewayTitle: '网关',
|
||||
gatewayDescription: '查看当前网关状态与基础运行控制。',
|
||||
gatewayStatusLabel: '状态',
|
||||
@@ -1389,6 +1393,8 @@ export const messages: I18nMessages = {
|
||||
description: 'アプリの見た目と操作感をカスタマイズします。',
|
||||
themeSection: 'テーマ設定',
|
||||
languageSection: '言語',
|
||||
launchAtStartupTitle: 'システム起動時に自動起動',
|
||||
launchAtStartupDescription: 'システムにログインした後、アプリを自動的に起動します',
|
||||
gatewayTitle: 'ゲートウェイ',
|
||||
gatewayDescription: '現在のゲートウェイ状態と基本的なランタイム操作を確認します。',
|
||||
gatewayStatusLabel: '状態',
|
||||
|
||||
@@ -10,8 +10,10 @@ import ToggleSwitch from './ToggleSwitch';
|
||||
type GeneralSettingsPanelProps = {
|
||||
themeMode: ThemeMode;
|
||||
language: LanguageCode;
|
||||
launchAtStartup: boolean;
|
||||
onThemeChange: (theme: ThemeMode) => void | Promise<void>;
|
||||
onLanguageChange: (language: LanguageCode) => void | Promise<void>;
|
||||
onLaunchAtStartupChange: (enabled: boolean) => void | Promise<void>;
|
||||
gatewayState: GatewaySettingState;
|
||||
updateState: SettingUpdateState;
|
||||
};
|
||||
@@ -83,8 +85,10 @@ function getGatewayStatusMeta(
|
||||
export default function GeneralSettingsPanel({
|
||||
themeMode,
|
||||
language,
|
||||
launchAtStartup,
|
||||
onThemeChange,
|
||||
onLanguageChange,
|
||||
onLaunchAtStartupChange,
|
||||
gatewayState,
|
||||
updateState,
|
||||
}: GeneralSettingsPanelProps) {
|
||||
@@ -160,6 +164,24 @@ export default function GeneralSettingsPanel({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 box-border flex w-full items-center justify-between gap-4 border-b border-dashed border-[#E5E8EE] py-5 dark:border-gray-700">
|
||||
<div>
|
||||
<div className="mb-1 text-[16px] text-[#171717] dark:text-gray-100">
|
||||
{t('settings.general.launchAtStartupTitle')}
|
||||
</div>
|
||||
<div className="text-[14px] text-[#99A0AE] dark:text-gray-500">
|
||||
{t('settings.general.launchAtStartupDescription')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ToggleSwitch
|
||||
checked={launchAtStartup}
|
||||
onChange={(nextValue) => {
|
||||
void onLaunchAtStartupChange(nextValue);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-10">
|
||||
<div className="mb-6 text-[24px] font-medium text-[#171717] dark:text-gray-100">
|
||||
{t('settings.general.gatewayTitle')}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Suspense, lazy, useEffect } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useI18n } from '../../i18n';
|
||||
import { updateLanguage, updateThemeMode, useSettingsStore } from '../../stores';
|
||||
import { updateLanguage, updateLaunchAtStartup, updateThemeMode, useSettingsStore } from '../../stores';
|
||||
import type { LanguageCode, ThemeMode } from '../../types/runtime';
|
||||
import AccountSettingsPanel from './components/AccountSettingsPanel';
|
||||
import GeneralSettingsPanel from './components/GeneralSettingsPanel';
|
||||
@@ -18,6 +18,7 @@ export default function SettingPage() {
|
||||
const { t } = useI18n();
|
||||
const themeMode = useSettingsStore((state) => state.themeMode);
|
||||
const language = useSettingsStore((state) => state.language);
|
||||
const launchAtStartup = useSettingsStore((state) => state.launchAtStartup);
|
||||
const updateState = useSettingUpdateState();
|
||||
const gatewayState = useGatewaySettingState();
|
||||
const rawView = new URLSearchParams(location.search).get('view');
|
||||
@@ -45,6 +46,10 @@ export default function SettingPage() {
|
||||
await updateLanguage(nextLanguage);
|
||||
};
|
||||
|
||||
const handleLaunchAtStartupChange = async (nextValue: boolean) => {
|
||||
await updateLaunchAtStartup(nextValue);
|
||||
};
|
||||
|
||||
const handleViewChange = (nextView: SettingView) => {
|
||||
if (nextView === currentView) return;
|
||||
|
||||
@@ -93,8 +98,10 @@ export default function SettingPage() {
|
||||
<GeneralSettingsPanel
|
||||
themeMode={themeMode}
|
||||
language={language}
|
||||
launchAtStartup={launchAtStartup}
|
||||
onThemeChange={handleThemeChange}
|
||||
onLanguageChange={handleLanguageChange}
|
||||
onLaunchAtStartupChange={handleLaunchAtStartupChange}
|
||||
gatewayState={gatewayState}
|
||||
updateState={updateState}
|
||||
/>
|
||||
|
||||
@@ -12,6 +12,8 @@ export {
|
||||
updateLanguage as setLanguage,
|
||||
updateFontSize as setFontSize,
|
||||
updateMinimizeToTray as setMinimizeToTray,
|
||||
updateLaunchAtStartup,
|
||||
updateLaunchAtStartup as setLaunchAtStartup,
|
||||
updatePrimaryColor as setPrimaryColor,
|
||||
updateGatewayAutoStart,
|
||||
updateGatewayAutoStart as setGatewayAutoStart,
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface SettingsState {
|
||||
primaryColor: string;
|
||||
fontSize: number;
|
||||
minimizeToTray: boolean;
|
||||
launchAtStartup: boolean;
|
||||
providerId: string | null;
|
||||
defaultModel: string | null;
|
||||
gatewayAutoStart: boolean;
|
||||
@@ -72,6 +73,7 @@ function createInitialState(): SettingsState {
|
||||
primaryColor: '#1677ff',
|
||||
fontSize: 14,
|
||||
minimizeToTray: false,
|
||||
launchAtStartup: false,
|
||||
providerId: null,
|
||||
defaultModel: null,
|
||||
gatewayAutoStart: true,
|
||||
@@ -205,11 +207,12 @@ async function hydrate(): Promise<SettingsState> {
|
||||
const systemTheme = detectSystemTheme();
|
||||
const systemLanguage = detectSystemLanguage();
|
||||
|
||||
const [themeMode, language, fontSize, minimizeToTray, primaryColor, providerId, defaultModel, gatewayAutoStart] = await Promise.all([
|
||||
const [themeMode, language, fontSize, minimizeToTray, launchAtStartup, primaryColor, providerId, defaultModel, gatewayAutoStart] = await Promise.all([
|
||||
readThemeMode(),
|
||||
readConfigValue<LanguageCode>(CONFIG_KEYS.LANGUAGE, systemLanguage),
|
||||
readConfigValue<number>(CONFIG_KEYS.FONT_SIZE, 14),
|
||||
readConfigValue<boolean>(CONFIG_KEYS.MINIMIZE_TO_TRAY, false),
|
||||
readConfigValue<boolean>(CONFIG_KEYS.LAUNCH_AT_STARTUP, false),
|
||||
readConfigValue<string>(CONFIG_KEYS.PRIMARY_COLOR, '#1677ff'),
|
||||
readConfigValue<string | null>(CONFIG_KEYS.PROVIDER, null),
|
||||
readConfigValue<string | null>(CONFIG_KEYS.DEFAULT_MODEL, null),
|
||||
@@ -231,6 +234,7 @@ async function hydrate(): Promise<SettingsState> {
|
||||
primaryColor: primaryColor ?? '#1677ff',
|
||||
fontSize: fontSize ?? 14,
|
||||
minimizeToTray: Boolean(minimizeToTray),
|
||||
launchAtStartup: Boolean(launchAtStartup),
|
||||
providerId: providerId ?? null,
|
||||
defaultModel: defaultModel ?? null,
|
||||
gatewayAutoStart: Boolean(gatewayAutoStart),
|
||||
@@ -306,6 +310,21 @@ async function setMinimizeToTray(minimizeToTray: boolean, persist = true): Promi
|
||||
return state;
|
||||
}
|
||||
|
||||
async function setLaunchAtStartup(launchAtStartup: boolean, persist = true): Promise<SettingsState> {
|
||||
const next = Boolean(launchAtStartup);
|
||||
if (state.launchAtStartup === next && state.initialized) return state;
|
||||
|
||||
patchState({
|
||||
launchAtStartup: next,
|
||||
});
|
||||
|
||||
if (persist) {
|
||||
await writeConfigValue(CONFIG_KEYS.LAUNCH_AT_STARTUP, next);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
async function setPrimaryColor(primaryColor: string, persist = true): Promise<SettingsState> {
|
||||
const next = primaryColor || '#1677ff';
|
||||
if (state.primaryColor === next && state.initialized) return state;
|
||||
@@ -353,6 +372,7 @@ export const settingsStore = {
|
||||
setLanguage,
|
||||
setFontSize,
|
||||
setMinimizeToTray,
|
||||
setLaunchAtStartup,
|
||||
setPrimaryColor,
|
||||
setGatewayAutoStart,
|
||||
hostApiFetch,
|
||||
@@ -391,6 +411,10 @@ export async function updateMinimizeToTray(minimizeToTray: boolean, persist = tr
|
||||
return setMinimizeToTray(minimizeToTray, persist);
|
||||
}
|
||||
|
||||
export async function updateLaunchAtStartup(launchAtStartup: boolean, persist = true): Promise<SettingsState> {
|
||||
return setLaunchAtStartup(launchAtStartup, persist);
|
||||
}
|
||||
|
||||
export async function updateGatewayAutoStart(gatewayAutoStart: boolean, persist = true): Promise<SettingsState> {
|
||||
return setGatewayAutoStart(gatewayAutoStart, persist);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export const CONFIG_KEYS = {
|
||||
LANGUAGE: 'language',
|
||||
FONT_SIZE: 'fontSize',
|
||||
MINIMIZE_TO_TRAY: 'minimizeToTray',
|
||||
LAUNCH_AT_STARTUP: 'launchAtStartup',
|
||||
PROVIDER: 'provider',
|
||||
DEFAULT_MODEL: 'defaultModel',
|
||||
GATEWAY_AUTO_START: 'gatewayAutoStart',
|
||||
@@ -46,6 +47,7 @@ export interface ConfigValueMap {
|
||||
language: LanguageCode;
|
||||
fontSize: number;
|
||||
minimizeToTray: boolean;
|
||||
launchAtStartup: boolean;
|
||||
provider: string | null;
|
||||
defaultModel: string | null;
|
||||
gatewayAutoStart: boolean;
|
||||
|
||||
Reference in New Issue
Block a user