Skip to content

Commit 4edbee9

Browse files
authored
Merge branch 'main' into main
2 parents 7819290 + fd16175 commit 4edbee9

7 files changed

Lines changed: 60 additions & 3 deletions

File tree

desktop/src/components/layout/AppShell.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function isChatTab(tab: Tab | undefined) {
2828

2929
export function AppShell() {
3030
const fetchSettings = useSettingsStore((s) => s.fetchAll)
31+
const uiZoom = useSettingsStore((s) => s.uiZoom)
3132
const sidebarOpen = useUIStore((s) => s.sidebarOpen)
3233
const toggleSidebar = useUIStore((s) => s.toggleSidebar)
3334
const setSidebarOpen = useUIStore((s) => s.setSidebarOpen)
@@ -194,7 +195,7 @@ export function AppShell() {
194195
}
195196

196197
return (
197-
<div className={`app-shell app-shell-viewport flex overflow-hidden bg-[var(--color-surface)]${isMobileShell ? ' app-shell--mobile' : ''}`}>
198+
<div className={`app-shell app-shell-viewport flex overflow-hidden bg-[var(--color-surface)]${isMobileShell ? ' app-shell--mobile' : ''}`} style={{ zoom: uiZoom }}>
198199
{isMobileShell && effectiveSidebarOpen ? (
199200
<button
200201
type="button"

desktop/src/components/layout/Sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) {
583583
)}
584584

585585
{!isMobile && (
586-
<div className={`border-t border-[var(--color-border)] p-3 ${expanded ? '' : 'flex justify-center'}`}>
586+
<div className={`absolute bottom-0 left-0 right-0 border-t border-[var(--color-border)] p-3 ${expanded ? '' : 'flex justify-center'}`}>
587587
<NavItem
588588
active={activeTabId === SETTINGS_TAB_ID}
589589
collapsed={!expanded}

desktop/src/i18n/locales/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,8 @@ export const en = {
786786
'settings.general.webSearchBraveFreeHint': 'Create an account to generate a Search API key with free usage for testing.',
787787
'settings.general.webSearchHint': 'Auto uses native Claude web search for Claude model names, then falls back to Tavily and Brave keys.',
788788
'settings.general.webSearchSave': 'Save',
789+
'settings.general.uiZoom': 'UI Zoom',
790+
'settings.general.uiZoomDescription': 'Scale the entire interface for better visibility on high-DPI displays.',
789791

790792
// ─── Empty Session ──────────────────────────────────────
791793
'empty.title': 'New session',

desktop/src/i18n/locales/zh.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,8 @@ export const zh: Record<TranslationKey, string> = {
789789
'settings.general.webSearchBraveFreeHint': '注册账号后可创建 Search API Key,免费额度可用于测试。',
790790
'settings.general.webSearchHint': '自动模式会对 Claude 模型名优先使用原生 WebSearch,失败或非 Claude 模型时再使用 Tavily/Brave。',
791791
'settings.general.webSearchSave': '保存',
792+
'settings.general.uiZoom': '界面缩放',
793+
'settings.general.uiZoomDescription': '缩放整个界面,以便在高 DPI 屏幕上获得更好的可见性。',
792794

793795
// ─── Empty Session ──────────────────────────────────────
794796
'empty.title': '新建会话',

desktop/src/pages/Settings.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState, useEffect, useMemo, useRef, type ReactNode } from 'react'
22
import QRCode from 'qrcode'
33
import { Copy, Eye, EyeOff, PowerOff, QrCode, RotateCw } from 'lucide-react'
4-
import { useSettingsStore } from '../stores/settingsStore'
4+
import { useSettingsStore, UI_ZOOM_MIN, UI_ZOOM_MAX, UI_ZOOM_STEP } from '../stores/settingsStore'
55
import { useProviderStore } from '../stores/providerStore'
66
import { useTranslation } from '../i18n'
77
import { Modal } from '../components/shared/Modal'
@@ -1386,6 +1386,8 @@ function GeneralSettings() {
13861386
appModeRequiresRestart,
13871387
fetchAppMode,
13881388
setAppMode: setAppModeAction,
1389+
uiZoom,
1390+
setUiZoom,
13891391
} = useSettingsStore()
13901392
const t = useTranslation()
13911393
const [webSearchDraft, setWebSearchDraft] = useState(webSearch)
@@ -1859,6 +1861,32 @@ function GeneralSettings() {
18591861
</div>
18601862
</div>
18611863
</div>
1864+
{/* UI Zoom */}
1865+
<div className="mt-8">
1866+
<h2 className="text-base font-semibold text-[var(--color-text-primary)] mb-1">{t('settings.general.uiZoom')}</h2>
1867+
<p className="text-sm text-[var(--color-text-tertiary)] mb-3">{t('settings.general.uiZoomDescription')}</p>
1868+
<div className="flex flex-col gap-2">
1869+
<div className="flex items-center justify-end">
1870+
<span className="text-sm text-[var(--color-text-secondary)]">
1871+
{Math.round(uiZoom * 100)}%
1872+
</span>
1873+
</div>
1874+
<div className="flex items-center gap-3">
1875+
<span className="text-xs text-[var(--color-text-tertiary)]">{Math.round(UI_ZOOM_MIN * 100)}%</span>
1876+
<input
1877+
type="range"
1878+
min={UI_ZOOM_MIN}
1879+
max={UI_ZOOM_MAX}
1880+
step={UI_ZOOM_STEP}
1881+
value={uiZoom}
1882+
onChange={(e) => setUiZoom(parseFloat(e.target.value))}
1883+
onMouseUp={(e) => e.currentTarget.blur()}
1884+
className="flex-1"
1885+
/>
1886+
<span className="text-xs text-[var(--color-text-tertiary)]">{Math.round(UI_ZOOM_MAX * 100)}%</span>
1887+
</div>
1888+
</div>
1889+
</div>
18621890

18631891
{/* Confirm dialog for mode switch */}
18641892
<ConfirmDialog

desktop/src/stores/settingsStore.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import type { Locale } from '../i18n'
99
import { useUIStore } from './uiStore'
1010

1111
const LOCALE_STORAGE_KEY = 'cc-haha-locale'
12+
const UI_ZOOM_STORAGE_KEY = 'cc-haha-ui-zoom'
13+
export const UI_ZOOM_MIN = 0.5
14+
export const UI_ZOOM_MAX = 2.0
15+
export const UI_ZOOM_STEP = 0.05
1216
let desktopNotificationsSaveQueue: Promise<void> = Promise.resolve()
1317

1418
function getStoredLocale(): Locale {
@@ -19,6 +23,17 @@ function getStoredLocale(): Locale {
1923
return 'zh'
2024
}
2125

26+
function getStoredUiZoom(): number {
27+
try {
28+
const stored = localStorage.getItem(UI_ZOOM_STORAGE_KEY)
29+
if (stored === null) return 1.0
30+
const parsed = parseFloat(stored)
31+
if (isNaN(parsed)) return 1.0
32+
return Math.min(UI_ZOOM_MAX, Math.max(UI_ZOOM_MIN, parsed))
33+
} catch { /* localStorage unavailable */ }
34+
return 1.0
35+
}
36+
2237
type SettingsStore = {
2338
permissionMode: PermissionMode
2439
currentModel: ModelInfo | null
@@ -34,6 +49,7 @@ type SettingsStore = {
3449
h5Access: H5AccessSettings
3550
h5AccessError: string | null
3651
responseLanguage: string
52+
uiZoom: number
3753
isLoading: boolean
3854
error: string | null
3955

@@ -61,6 +77,7 @@ type SettingsStore = {
6177
setResponseLanguage: (language: string) => Promise<void>
6278
fetchAppMode: () => Promise<void>
6379
setAppMode: (mode: AppMode, portableDir?: string | null) => Promise<void>
80+
setUiZoom: (zoom: number) => void
6481
}
6582

6683
const DEFAULT_H5_ACCESS_SETTINGS: H5AccessSettings = {
@@ -85,11 +102,17 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
85102
h5Access: DEFAULT_H5_ACCESS_SETTINGS,
86103
h5AccessError: null,
87104
responseLanguage: '',
105+
uiZoom: getStoredUiZoom(),
88106
isLoading: false,
89107
error: null,
90108

91109
appMode: { mode: 'default', portableDir: null, defaultPortableDir: null },
92110
appModeRequiresRestart: false,
111+
setUiZoom: (zoom: number) => {
112+
const clamped = Math.min(UI_ZOOM_MAX, Math.max(UI_ZOOM_MIN, zoom))
113+
set({ uiZoom: clamped })
114+
try { localStorage.setItem(UI_ZOOM_STORAGE_KEY, String(clamped)) } catch { /* noop */ }
115+
},
93116

94117
fetchAll: async () => {
95118
set({ isLoading: true, error: null })

desktop/src/types/settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export type UserSettings = {
4343
desktopNotificationsEnabled?: boolean
4444
webSearch?: WebSearchSettings
4545
language?: string
46+
uiZoom?: number
4647
[key: string]: unknown
4748
}
4849

0 commit comments

Comments
 (0)