From eadadda071d2aedeaef313b1185d68bdb73e4db2 Mon Sep 17 00:00:00 2001 From: digua Date: Mon, 28 Sep 2026 16:53:42 +0800 Subject: [PATCH] feat(electron): add persistent UI scale setting --- apps/desktop/main/ipc/window.ts | 23 +++++++++- apps/desktop/main/window/main-window.ts | 3 ++ apps/desktop/preload/apis/core.ts | 4 ++ apps/desktop/preload/index.d.ts | 2 + packages/config/src/schema.ts | 1 + packages/config/src/set-config-field.test.ts | 17 +++++++ .../Overview/OverviewIdentityCard.vue | 4 +- .../common/Settings/BasicSettingsTab.vue | 46 +++++++++++++++++++ .../common/Settings/SettingsDialogShell.vue | 2 +- src/i18n/locales/en-US/common.json | 2 + src/i18n/locales/en-US/settings.json | 1 + src/i18n/locales/ja-JP/common.json | 2 + src/i18n/locales/ja-JP/settings.json | 1 + src/i18n/locales/zh-CN/common.json | 2 + src/i18n/locales/zh-CN/settings.json | 1 + src/i18n/locales/zh-TW/common.json | 2 + src/i18n/locales/zh-TW/settings.json | 1 + src/services/platform/browser.ts | 8 ++++ src/services/platform/cli-web.ts | 8 ++++ src/services/platform/electron.ts | 8 ++++ src/services/platform/types.ts | 2 + 21 files changed, 136 insertions(+), 4 deletions(-) diff --git a/apps/desktop/main/ipc/window.ts b/apps/desktop/main/ipc/window.ts index 119a9642..1bc68049 100644 --- a/apps/desktop/main/ipc/window.ts +++ b/apps/desktop/main/ipc/window.ts @@ -4,7 +4,8 @@ import { ipcMain, app, dialog, clipboard, shell, nativeTheme } from 'electron' import * as fs from 'fs/promises' -import { loadConfig, setConfigField } from '@openchatlab/config' +import { desktopConfigSchema, loadConfig, setConfigField } from '@openchatlab/config' +import { appLogger } from '@openchatlab/node-runtime' import type { DesktopCloseBehavior } from '@openchatlab/shared-types' import type { IpcContext } from './types' import { simulateUpdateDialog, manualCheckForUpdates } from '../update/manager' @@ -224,6 +225,26 @@ export function registerWindowHandlers(ctx: IpcContext): void { } }) + // ==================== Desktop UI scale ==================== + ipcMain.handle('app:getDesktopUiScale', () => loadConfig().desktop.ui_scale) + + ipcMain.handle('app:setDesktopUiScale', (_, scale: number) => { + const parsed = desktopConfigSchema.shape.ui_scale.safeParse(scale) + if (typeof scale !== 'number' || !parsed.success) { + return { success: false, error: 'Unsupported desktop UI scale' } + } + + try { + setConfigField('desktop.ui_scale', String(parsed.data)) + win.webContents.setZoomFactor(parsed.data) + appLogger.info('window', `Desktop UI scale changed to ${parsed.data}`) + return { success: true } + } catch (error) { + appLogger.error('window', 'Failed to change desktop UI scale', error) + return { success: false, error: error instanceof Error ? error.message : String(error) } + } + }) + // ==================== Windows 关闭行为 ==================== ipcMain.handle('app:getDesktopCloseBehavior', () => { return loadConfig().desktop.close_behavior diff --git a/apps/desktop/main/window/main-window.ts b/apps/desktop/main/window/main-window.ts index 887bdd49..18e98b45 100644 --- a/apps/desktop/main/window/main-window.ts +++ b/apps/desktop/main/window/main-window.ts @@ -28,6 +28,7 @@ export async function createMainWindow(paths: MainWindowPaths): Promise { + // Reapply the preference after navigation, overriding Chromium's per-origin zoom cache. + win.webContents.setZoomFactor(loadConfig().desktop.ui_scale) setTimeout(() => { currentMainWindow?.webContents.send('app-started') }, 500) diff --git a/apps/desktop/preload/apis/core.ts b/apps/desktop/preload/apis/core.ts index 757081b8..7f0809a9 100644 --- a/apps/desktop/preload/apis/core.ts +++ b/apps/desktop/preload/apis/core.ts @@ -123,5 +123,9 @@ export const extendedApi = { setDesktopCloseBehavior: (behavior: DesktopCloseBehavior): Promise<{ success: boolean; error?: string }> => { return ipcRenderer.invoke('app:setDesktopCloseBehavior', behavior) }, + getDesktopUiScale: (): Promise => ipcRenderer.invoke('app:getDesktopUiScale'), + setDesktopUiScale: (scale: number): Promise<{ success: boolean; error?: string }> => { + return ipcRenderer.invoke('app:setDesktopUiScale', scale) + }, }, } diff --git a/apps/desktop/preload/index.d.ts b/apps/desktop/preload/index.d.ts index c968f576..93a34e8b 100644 --- a/apps/desktop/preload/index.d.ts +++ b/apps/desktop/preload/index.d.ts @@ -194,6 +194,8 @@ interface Api { setOpenAtLogin: (enabled: boolean) => Promise<{ success: boolean; error?: string }> getDesktopCloseBehavior: () => Promise setDesktopCloseBehavior: (behavior: DesktopCloseBehavior) => Promise<{ success: boolean; error?: string }> + getDesktopUiScale: () => Promise + setDesktopUiScale: (scale: number) => Promise<{ success: boolean; error?: string }> } } diff --git a/packages/config/src/schema.ts b/packages/config/src/schema.ts index f43f4434..5324c42c 100644 --- a/packages/config/src/schema.ts +++ b/packages/config/src/schema.ts @@ -48,6 +48,7 @@ export const cliConfigSchema = z.object({ export const desktopConfigSchema = z.object({ close_behavior: z.enum(['background', 'quit']).default('background'), + ui_scale: z.number().min(0.9).max(1.5).default(1), }) export const configSchema = z.object({ diff --git a/packages/config/src/set-config-field.test.ts b/packages/config/src/set-config-field.test.ts index a7d3bd97..bc6875dd 100644 --- a/packages/config/src/set-config-field.test.ts +++ b/packages/config/src/set-config-field.test.ts @@ -45,6 +45,23 @@ describe('cli config section', () => { }) describe('setConfigField', () => { + it('defaults old desktop configs to 100% and persists UI scale without changing other preferences', () => { + mkdirSync(join(tempHome, '.chatlab'), { recursive: true }) + writeFileSync(configToml, '[desktop]\nclose_behavior = "quit"\n[locale]\nlang = "en-US"\n', 'utf-8') + + assert.equal(loadConfig().desktop.ui_scale, 1) + setConfigField('desktop.ui_scale', '1.25') + + const config = loadConfig() + assert.equal(config.desktop.ui_scale, 1.25) + assert.equal(config.desktop.close_behavior, 'quit') + assert.equal(config.locale.lang, 'en-US') + + const saved = readFileSync(configToml, 'utf-8') + assert.throws(() => setConfigField('desktop.ui_scale', '0')) + assert.equal(readFileSync(configToml, 'utf-8'), saved) + }) + it('persists the Windows close behavior', () => { const result = setConfigField('desktop.close_behavior', 'background') diff --git a/src/components/analysis/Overview/OverviewIdentityCard.vue b/src/components/analysis/Overview/OverviewIdentityCard.vue index edd33650..c5433439 100644 --- a/src/components/analysis/Overview/OverviewIdentityCard.vue +++ b/src/components/analysis/Overview/OverviewIdentityCard.vue @@ -273,8 +273,8 @@ onUnmounted(() => { {{ session.name }} -
-
+
+
diff --git a/src/components/common/Settings/BasicSettingsTab.vue b/src/components/common/Settings/BasicSettingsTab.vue index e5e11283..957bfa27 100644 --- a/src/components/common/Settings/BasicSettingsTab.vue +++ b/src/components/common/Settings/BasicSettingsTab.vue @@ -4,6 +4,8 @@ import { computed, ref, onMounted } from 'vue' import { useI18n } from 'vue-i18n' import { useLayoutStore } from '@/stores/layout' import { useSettingsStore } from '@/stores/settings' +import { useToast } from '@/composables/useToast' +import { reportError } from '@/services/log-report' import { useColorMode } from '@vueuse/core' import { availableLocales, type LocaleType } from '@/i18n' import NetworkSettingsSection from './NetworkSettingsSection.vue' @@ -14,6 +16,7 @@ import { INSIGHT_CARD_THEMES, type InsightCardThemeId } from '@/utils/insight-ca import type { DesktopCloseBehavior } from '@openchatlab/shared-types' const { t } = useI18n() +const toast = useToast() withDefaults( defineProps<{ @@ -39,6 +42,12 @@ const isWindowsDesktop = IS_ELECTRON && typeof navigator !== 'undefined' && navigator.platform.toLowerCase().includes('win') const desktopCloseBehavior = ref('background') let savedDesktopCloseBehavior: DesktopCloseBehavior = 'background' +const desktopUiScale = ref(1) +const savingUiScale = ref(false) +const uiScaleOptions = [0.9, 1, 1.1, 1.25, 1.5].map((value) => ({ + label: `${Math.round(value * 100)}%`, + value, +})) onMounted(async () => { if (!IS_ELECTRON) { @@ -52,6 +61,13 @@ onMounted(async () => { isPackaged.value = false } + try { + desktopUiScale.value = await usePlatformService().getDesktopUiScale() + } catch (error) { + reportError(`Failed to load desktop UI scale: ${error}`, error instanceof Error ? error.stack : undefined) + toast.fail(t('common.loadFailed')) + } + if (isWindowsDesktop) { try { savedDesktopCloseBehavior = await usePlatformService().getDesktopCloseBehavior() @@ -84,6 +100,20 @@ async function handleDesktopCloseBehaviorChange(value: string | number) { } } +async function handleDesktopUiScaleChange(value: number) { + savingUiScale.value = true + try { + const result = await usePlatformService().setDesktopUiScale(value) + if (!result.success) throw new Error(result.error || 'Failed to save desktop UI scale') + desktopUiScale.value = value + } catch (error) { + reportError(`Failed to save desktop UI scale: ${error}`, error instanceof Error ? error.stack : undefined) + toast.fail(t('common.saveFailed')) + } finally { + savingUiScale.value = false + } +} + // Color Mode const colorMode = useColorMode({ emitAuto: true, @@ -215,6 +245,22 @@ const desktopCloseBehaviorOptions = computed(() => [
+
diff --git a/src/components/common/Settings/SettingsDialogShell.vue b/src/components/common/Settings/SettingsDialogShell.vue index 61c80d52..eb20a6b5 100644 --- a/src/components/common/Settings/SettingsDialogShell.vue +++ b/src/components/common/Settings/SettingsDialogShell.vue @@ -38,7 +38,7 @@ const currentTab = computed({ @update:open="emit('update:open', $event)" >