refactor: Theme improve (#6619)

* refactor(IpcChannel): rename theme change event and streamline theme handling

- Updated the IpcChannel enum to rename 'theme:change' to 'theme:updated' for clarity.
- Refactored theme handling in ipc.ts to utilize a new ThemeService, simplifying theme updates and event broadcasting.
- Adjusted various components to consistently use the updated theme variable naming convention.

* refactor(Theme): standardize theme handling across components

- Updated theme retrieval to use 'actualTheme' instead of 'theme' for consistency.
- Changed default theme setting from 'auto' to 'system' in ConfigManager and related components.
- Adjusted theme handling in various components to reflect the new naming convention and ensure proper theme application.

* fix(Theme): improve theme handling and migration logic

- Added a console log for debugging theme transitions in ThemeProvider.
- Updated ThemeService to ensure theme is set correctly when changed.
- Incremented version number in store configuration to reflect changes.
- Enhanced migration logic to convert 'auto' theme setting to 'system' for better consistency.

* feat(Theme): add getTheme IPC channel and improve theme management

- Introduced a new IPC channel 'App_GetTheme' to retrieve the current theme.
- Updated ThemeService to include a method for getting the current theme.
- Refactored theme initialization in WindowService to ensure proper theme setup.
- Enhanced theme handling in various components to utilize the new theme retrieval method.

* fix(ThemeService): improve theme initialization and retrieval logic

- Set default theme to 'system' and updated theme initialization to handle legacy versions.
- Enhanced getTheme method to return both the current theme and the actual theme based on nativeTheme settings.
- Removed redundant initTheme method from ThemeService and ensured themeService is imported in WindowService for proper initialization.
- Updated ThemeProvider to handle the new structure of the theme retrieval response.

* refactor(Settings): remove theme management from settings

- Eliminated theme-related state and actions from the settings slice.
- Updated useSettings hook to remove theme handling functionality.
- Cleaned up imports by removing unused ThemeMode type.

* refactor(Theme): update theme retrieval in GeneralSettings and HomeWindow

- Restored theme retrieval in GeneralSettings and HomeWindow components.
- Adjusted imports to ensure proper theme management.
- Updated theme condition checks to utilize the ThemeMode enumeration for consistency.

* refactor(Theme): update theme terminology and retrieval in Sidebar and DisplaySettings

- Changed theme label from 'auto' to 'system' in multiple localization files for consistency.
- Updated Sidebar component to reflect the new theme terminology.
- Adjusted DisplaySettings to display the updated theme label.

* refactor(ThemeProvider): initialize theme state from API response

* refactor(ThemeProvider): reset theme state to default values and streamline initialization logic

* refactor(Theme): enhance theme management by incorporating 'system' mode and updating state handling

- Updated ThemeService to include 'system' as a valid theme option.
- Refactored ThemeProvider to utilize useSettings for theme state management and ensure proper initialization.
- Adjusted useSettings to include theme setting functionality.
- Modified settings slice to manage theme state effectively.

* refactor(WindowService, ThemeProvider, Messages, HomeWindow): streamline imports and clean up unused variables

- Removed duplicate import of ThemeService in WindowService.
- Adjusted import order in ThemeProvider for clarity.
- Simplified useSettings destructuring in Messages component.
- Cleaned up unused ThemeMode import in HomeWindow.

* refactor(Theme): standardize theme usage across components by replacing 'actualTheme' with 'theme'

- Updated components to consistently use 'theme' instead of 'actualTheme' for better clarity and maintainability.
- Adjusted ThemeProvider to reflect changes in theme state management.
- Ensured all relevant components are aligned with the new theme structure.

* refactor(Theme): remove unused theme retrieval functionality

- Eliminated the App_GetTheme channel and associated methods from ThemeService and IPC handling.
- Updated components to use the new theme structure, replacing 'actualTheme' with 'settedTheme' for consistency.
- Ensured all theme-related functionalities are streamlined and aligned with the latest changes.

* refactor(Theme): update theme variable usage in ChatFlowHistory and GeneralSettings

- Replaced 'theme' with 'settedTheme' in ChatFlowHistory for consistency with recent theme structure changes.
- Simplified theme destructuring in GeneralSettings by removing unused 'themeMode' variable.
- Ensured alignment with the latest theme management updates across components.

* refactor(Theme): update theme variable in GeneralSettings component

- Replaced 'themeMode' with 'theme' in GeneralSettings for consistency with recent theme structure changes.
- Ensured alignment with the latest theme management updates across components.

---------

Co-authored-by: beyondkmp <beyondkmkp@gmail.com>
This commit is contained in:
beyondkmp 2025-05-30 15:10:58 +08:00 committed by GitHub
parent 0df331cf8a
commit 9f49ce6dc9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 125 additions and 115 deletions

View File

@ -144,7 +144,7 @@ export enum IpcChannel {
// events // events
BackupProgress = 'backup-progress', BackupProgress = 'backup-progress',
ThemeChange = 'theme:change', ThemeUpdated = 'theme:updated',
UpdateDownloadedCancelled = 'update-downloaded-cancelled', UpdateDownloadedCancelled = 'update-downloaded-cancelled',
RestoreProgress = 'restore-progress', RestoreProgress = 'restore-progress',
UpdateError = 'update-error', UpdateError = 'update-error',

View File

@ -6,11 +6,10 @@ import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/pro
import { handleZoomFactor } from '@main/utils/zoom' import { handleZoomFactor } from '@main/utils/zoom'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { Shortcut, ThemeMode } from '@types' import { Shortcut, ThemeMode } from '@types'
import { BrowserWindow, ipcMain, nativeTheme, session, shell } from 'electron' import { BrowserWindow, ipcMain, session, shell } from 'electron'
import log from 'electron-log' import log from 'electron-log'
import { Notification } from 'src/renderer/src/types/notification' import { Notification } from 'src/renderer/src/types/notification'
import { titleBarOverlayDark, titleBarOverlayLight } from './config'
import AppUpdater from './services/AppUpdater' import AppUpdater from './services/AppUpdater'
import BackupManager from './services/BackupManager' import BackupManager from './services/BackupManager'
import { configManager } from './services/ConfigManager' import { configManager } from './services/ConfigManager'
@ -28,6 +27,7 @@ import { searchService } from './services/SearchService'
import { SelectionService } from './services/SelectionService' import { SelectionService } from './services/SelectionService'
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService' import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
import storeSyncService from './services/StoreSyncService' import storeSyncService from './services/StoreSyncService'
import { themeService } from './services/ThemeService'
import { setOpenLinkExternal } from './services/WebviewService' import { setOpenLinkExternal } from './services/WebviewService'
import { windowService } from './services/WindowService' import { windowService } from './services/WindowService'
import { calculateDirectorySize, getResourcePath } from './utils' import { calculateDirectorySize, getResourcePath } from './utils'
@ -122,34 +122,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
// theme // theme
ipcMain.handle(IpcChannel.App_SetTheme, (_, theme: ThemeMode) => { ipcMain.handle(IpcChannel.App_SetTheme, (_, theme: ThemeMode) => {
const updateTitleBarOverlay = () => { themeService.setTheme(theme)
if (!mainWindow?.setTitleBarOverlay) return
const isDark = nativeTheme.shouldUseDarkColors
mainWindow.setTitleBarOverlay(isDark ? titleBarOverlayDark : titleBarOverlayLight)
}
const broadcastThemeChange = () => {
const isDark = nativeTheme.shouldUseDarkColors
const effectiveTheme = isDark ? ThemeMode.dark : ThemeMode.light
BrowserWindow.getAllWindows().forEach((win) => win.webContents.send(IpcChannel.ThemeChange, effectiveTheme))
}
const notifyThemeChange = () => {
updateTitleBarOverlay()
broadcastThemeChange()
}
if (theme === ThemeMode.auto) {
nativeTheme.themeSource = 'system'
nativeTheme.on('updated', notifyThemeChange)
} else {
nativeTheme.themeSource = theme
nativeTheme.off('updated', notifyThemeChange)
}
updateTitleBarOverlay()
configManager.setTheme(theme)
notifyThemeChange()
}) })
ipcMain.handle(IpcChannel.App_HandleZoomFactor, (_, delta: number, reset: boolean = false) => { ipcMain.handle(IpcChannel.App_HandleZoomFactor, (_, delta: number, reset: boolean = false) => {

View File

@ -43,7 +43,7 @@ export class ConfigManager {
} }
getTheme(): ThemeMode { getTheme(): ThemeMode {
return this.get(ConfigKeys.Theme, ThemeMode.auto) return this.get(ConfigKeys.Theme, ThemeMode.system)
} }
setTheme(theme: ThemeMode) { setTheme(theme: ThemeMode) {

View File

@ -0,0 +1,48 @@
import { IpcChannel } from '@shared/IpcChannel'
import { ThemeMode } from '@types'
import { BrowserWindow, nativeTheme } from 'electron'
import { titleBarOverlayDark, titleBarOverlayLight } from '../config'
import { configManager } from './ConfigManager'
class ThemeService {
private theme: ThemeMode = ThemeMode.system
constructor() {
this.theme = configManager.getTheme()
if (this.theme === ThemeMode.dark || this.theme === ThemeMode.light || this.theme === ThemeMode.system) {
nativeTheme.themeSource = this.theme
} else {
// 兼容旧版本
configManager.setTheme(ThemeMode.system)
nativeTheme.themeSource = ThemeMode.system
}
nativeTheme.on('updated', this.themeUpdatadHandler.bind(this))
}
themeUpdatadHandler() {
BrowserWindow.getAllWindows().forEach((win) => {
if (win && !win.isDestroyed() && win.setTitleBarOverlay) {
try {
win.setTitleBarOverlay(nativeTheme.shouldUseDarkColors ? titleBarOverlayDark : titleBarOverlayLight)
} catch (error) {
// don't throw error if setTitleBarOverlay failed
// Because it may be called with some windows have some title bar
}
}
win.webContents.send(IpcChannel.ThemeUpdated, nativeTheme.shouldUseDarkColors ? ThemeMode.dark : ThemeMode.light)
})
}
setTheme(theme: ThemeMode) {
if (theme === this.theme) {
return
}
this.theme = theme
nativeTheme.themeSource = theme
configManager.setTheme(theme)
}
}
export const themeService = new ThemeService()

View File

@ -1,8 +1,10 @@
// just import the themeService to ensure the theme is initialized
import './ThemeService'
import { is } from '@electron-toolkit/utils' import { is } from '@electron-toolkit/utils'
import { isDev, isLinux, isMac, isWin } from '@main/constant' import { isDev, isLinux, isMac, isWin } from '@main/constant'
import { getFilesDir } from '@main/utils/file' import { getFilesDir } from '@main/utils/file'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { ThemeMode } from '@types'
import { app, BrowserWindow, nativeTheme, shell } from 'electron' import { app, BrowserWindow, nativeTheme, shell } from 'electron'
import Logger from 'electron-log' import Logger from 'electron-log'
import windowStateKeeper from 'electron-window-state' import windowStateKeeper from 'electron-window-state'
@ -45,13 +47,6 @@ export class WindowService {
maximize: false maximize: false
}) })
const theme = configManager.getTheme()
if (theme === ThemeMode.auto) {
nativeTheme.themeSource = 'system'
} else {
nativeTheme.themeSource = theme
}
this.mainWindow = new BrowserWindow({ this.mainWindow = new BrowserWindow({
x: mainWindowState.x, x: mainWindowState.x,
y: mainWindowState.y, y: mainWindowState.y,

View File

@ -1,7 +1,7 @@
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
import { electronAPI } from '@electron-toolkit/preload' import { electronAPI } from '@electron-toolkit/preload'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { FileType, KnowledgeBaseParams, KnowledgeItem, MCPServer, Shortcut, WebDavConfig } from '@types' import { FileType, KnowledgeBaseParams, KnowledgeItem, MCPServer, Shortcut, ThemeMode, WebDavConfig } from '@types'
import { contextBridge, ipcRenderer, OpenDialogOptions, shell, webUtils } from 'electron' import { contextBridge, ipcRenderer, OpenDialogOptions, shell, webUtils } from 'electron'
import { Notification } from 'src/renderer/src/types/notification' import { Notification } from 'src/renderer/src/types/notification'
import { CreateDirectoryOptions } from 'webdav' import { CreateDirectoryOptions } from 'webdav'
@ -20,7 +20,7 @@ const api = {
setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchToTray, isActive), setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchToTray, isActive),
setTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTray, isActive), setTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTray, isActive),
setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, isActive), setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, isActive),
setTheme: (theme: 'light' | 'dark' | 'auto') => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme), setTheme: (theme: ThemeMode) => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme),
handleZoomFactor: (delta: number, reset: boolean = false) => handleZoomFactor: (delta: number, reset: boolean = false) =>
ipcRenderer.invoke(IpcChannel.App_HandleZoomFactor, delta, reset), ipcRenderer.invoke(IpcChannel.App_HandleZoomFactor, delta, reset),
setAutoUpdate: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetAutoUpdate, isActive), setAutoUpdate: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetAutoUpdate, isActive),

View File

@ -9,6 +9,7 @@ import { useMinapps } from '@renderer/hooks/useMinapps'
import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor' import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor'
import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime' import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { ThemeMode } from '@renderer/types'
import { isEmoji } from '@renderer/utils' import { isEmoji } from '@renderer/utils'
import type { MenuProps } from 'antd' import type { MenuProps } from 'antd'
import { Avatar, Dropdown, Tooltip } from 'antd' import { Avatar, Dropdown, Tooltip } from 'antd'
@ -44,7 +45,7 @@ const Sidebar: FC = () => {
const { pathname } = useLocation() const { pathname } = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const { theme, settingTheme, toggleTheme } = useTheme() const { theme, settedTheme, toggleTheme } = useTheme()
const avatar = useAvatar() const avatar = useAvatar()
const { t } = useTranslation() const { t } = useTranslation()
@ -104,13 +105,13 @@ const Sidebar: FC = () => {
</Icon> </Icon>
</Tooltip> </Tooltip>
<Tooltip <Tooltip
title={t('settings.theme.title') + ': ' + t(`settings.theme.${settingTheme}`)} title={t('settings.theme.title') + ': ' + t(`settings.theme.${settedTheme}`)}
mouseEnterDelay={0.8} mouseEnterDelay={0.8}
placement="right"> placement="right">
<Icon theme={theme} onClick={() => toggleTheme()}> <Icon theme={theme} onClick={() => toggleTheme()}>
{settingTheme === 'dark' ? ( {settedTheme === ThemeMode.dark ? (
<Moon size={20} className="icon" /> <Moon size={20} className="icon" />
) : settingTheme === 'light' ? ( ) : settedTheme === ThemeMode.light ? (
<Sun size={20} className="icon" /> <Sun size={20} className="icon" />
) : ( ) : (
<SunMoon size={20} className="icon" /> <SunMoon size={20} className="icon" />

View File

@ -7,13 +7,13 @@ import React, { createContext, PropsWithChildren, use, useEffect, useState } fro
interface ThemeContextType { interface ThemeContextType {
theme: ThemeMode theme: ThemeMode
settingTheme: ThemeMode settedTheme: ThemeMode
toggleTheme: () => void toggleTheme: () => void
} }
const ThemeContext = createContext<ThemeContextType>({ const ThemeContext = createContext<ThemeContextType>({
theme: ThemeMode.auto, theme: ThemeMode.system,
settingTheme: ThemeMode.auto, settedTheme: ThemeMode.dark,
toggleTheme: () => {} toggleTheme: () => {}
}) })
@ -21,53 +21,50 @@ interface ThemeProviderProps extends PropsWithChildren {
defaultTheme?: ThemeMode defaultTheme?: ThemeMode
} }
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children, defaultTheme }) => { export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
const { theme, setTheme } = useSettings() // 用户设置的主题
const [effectiveTheme, setEffectiveTheme] = useState(theme) const { theme: settedTheme, setTheme: setSettedTheme } = useSettings()
const [actualTheme, setActualTheme] = useState<ThemeMode>(
window.matchMedia('(prefers-color-scheme: dark)').matches ? ThemeMode.dark : ThemeMode.light
)
const { initUserTheme } = useUserTheme() const { initUserTheme } = useUserTheme()
const toggleTheme = () => { const toggleTheme = () => {
// 主题顺序是light, dark, auto, 所以需要先判断当前主题,然后取下一个主题 const nextTheme = {
switch (theme) { [ThemeMode.light]: ThemeMode.dark,
case ThemeMode.light: [ThemeMode.dark]: ThemeMode.system,
setTheme(ThemeMode.dark) [ThemeMode.system]: ThemeMode.light
break }[settedTheme]
case ThemeMode.dark: setSettedTheme(nextTheme || ThemeMode.system)
setTheme(ThemeMode.auto)
break
case ThemeMode.auto:
setTheme(ThemeMode.light)
break
}
} }
useEffect(() => { useEffect(() => {
window.api?.setTheme(defaultTheme || theme) // Set initial theme and OS attributes on body
}, [defaultTheme, theme])
useEffect(() => {
document.body.setAttribute('theme-mode', effectiveTheme)
}, [effectiveTheme])
useEffect(() => {
document.body.setAttribute('os', isMac ? 'mac' : 'windows') document.body.setAttribute('os', isMac ? 'mac' : 'windows')
const themeChangeListenerRemover = window.electron.ipcRenderer.on( document.body.setAttribute('theme-mode', actualTheme)
IpcChannel.ThemeChange,
(_, realTheam: ThemeMode) => { // if theme is old auto, then set theme to system
setEffectiveTheme(realTheam) // we can delete this after next big release
} if (settedTheme !== ThemeMode.dark && settedTheme !== ThemeMode.light && settedTheme !== ThemeMode.system) {
) setSettedTheme(ThemeMode.system)
return () => { }
themeChangeListenerRemover()
}
})
useEffect(() => {
initUserTheme() initUserTheme()
// eslint-disable-next-line react-hooks/exhaustive-deps
// listen for theme updates from main process
const cleanup = window.electron.ipcRenderer.on(IpcChannel.ThemeUpdated, (_, actualTheme: ThemeMode) => {
document.body.setAttribute('theme-mode', actualTheme)
setActualTheme(actualTheme)
})
return cleanup
}, []) }, [])
return <ThemeContext value={{ theme: effectiveTheme, settingTheme: theme, toggleTheme }}>{children}</ThemeContext> useEffect(() => {
window.api.setTheme(settedTheme)
}, [settedTheme])
return <ThemeContext value={{ theme: actualTheme, settedTheme, toggleTheme }}>{children}</ThemeContext>
} }
export const useTheme = () => use(ThemeContext) export const useTheme = () => use(ThemeContext)

View File

@ -1692,7 +1692,7 @@
"zoom_out": "Zoom Out", "zoom_out": "Zoom Out",
"zoom_reset": "Reset Zoom" "zoom_reset": "Reset Zoom"
}, },
"theme.auto": "Auto", "theme.system": "System",
"theme.dark": "Dark", "theme.dark": "Dark",
"theme.light": "Light", "theme.light": "Light",
"theme.title": "Theme", "theme.title": "Theme",

View File

@ -1681,7 +1681,7 @@
"zoom_out": "ズームアウト", "zoom_out": "ズームアウト",
"zoom_reset": "ズームをリセット" "zoom_reset": "ズームをリセット"
}, },
"theme.auto": "自動", "theme.system": "システム",
"theme.dark": "ダーク", "theme.dark": "ダーク",
"theme.light": "ライト", "theme.light": "ライト",
"theme.title": "テーマ", "theme.title": "テーマ",

View File

@ -1681,7 +1681,7 @@
"zoom_out": "Уменьшить", "zoom_out": "Уменьшить",
"zoom_reset": "Сбросить масштаб" "zoom_reset": "Сбросить масштаб"
}, },
"theme.auto": "Автоматически", "theme.system": "Системная",
"theme.dark": "Темная", "theme.dark": "Темная",
"theme.light": "Светлая", "theme.light": "Светлая",
"theme.title": "Тема", "theme.title": "Тема",

View File

@ -1691,7 +1691,7 @@
"zoom_out": "缩小界面", "zoom_out": "缩小界面",
"zoom_reset": "重置缩放" "zoom_reset": "重置缩放"
}, },
"theme.auto": "自动", "theme.system": "系统",
"theme.dark": "深色", "theme.dark": "深色",
"theme.light": "浅色", "theme.light": "浅色",
"theme.title": "主题", "theme.title": "主题",

View File

@ -1684,7 +1684,7 @@
"zoom_reset": "重設縮放", "zoom_reset": "重設縮放",
"exit_fullscreen": "退出螢幕" "exit_fullscreen": "退出螢幕"
}, },
"theme.auto": "自動", "theme.system": "系統",
"theme.dark": "深色", "theme.dark": "深色",
"theme.light": "淺色", "theme.light": "淺色",
"theme.title": "主題", "theme.title": "主題",

View File

@ -1478,7 +1478,7 @@
"zoom_out": "Σμικρύνση εμφάνισης", "zoom_out": "Σμικρύνση εμφάνισης",
"zoom_reset": "Επαναφορά εμφάνισης" "zoom_reset": "Επαναφορά εμφάνισης"
}, },
"theme.auto": "Αυτόματο", "theme.system": "Σύστημα",
"theme.dark": "Σκοτεινό", "theme.dark": "Σκοτεινό",
"theme.light": "Φωτεινό", "theme.light": "Φωτεινό",
"theme.title": "Θέμα", "theme.title": "Θέμα",

View File

@ -1477,7 +1477,7 @@
"zoom_out": "Reducir interfaz", "zoom_out": "Reducir interfaz",
"zoom_reset": "Restablecer zoom" "zoom_reset": "Restablecer zoom"
}, },
"theme.auto": "Automático", "theme.system": "Sistema",
"theme.dark": "Oscuro", "theme.dark": "Oscuro",
"theme.light": "Claro", "theme.light": "Claro",
"theme.title": "Tema", "theme.title": "Tema",

View File

@ -1478,7 +1478,7 @@
"zoom_out": "Réduire l'interface", "zoom_out": "Réduire l'interface",
"zoom_reset": "Réinitialiser le zoom" "zoom_reset": "Réinitialiser le zoom"
}, },
"theme.auto": "Automatique", "theme.system": "Système",
"theme.dark": "Sombre", "theme.dark": "Sombre",
"theme.light": "Clair", "theme.light": "Clair",
"theme.title": "Thème", "theme.title": "Thème",

View File

@ -1480,7 +1480,7 @@
"zoom_out": "Diminuir interface", "zoom_out": "Diminuir interface",
"zoom_reset": "Redefinir zoom" "zoom_reset": "Redefinir zoom"
}, },
"theme.auto": "Automático", "theme.system": "Sistema",
"theme.dark": "Escuro", "theme.dark": "Escuro",
"theme.light": "Claro", "theme.light": "Claro",
"theme.title": "Tema", "theme.title": "Tema",

View File

@ -199,7 +199,7 @@ const ChatFlowHistory: FC<ChatFlowHistoryProps> = ({ conversationId }) => {
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([]) const [edges, setEdges, onEdgesChange] = useEdgesState<any>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const { userName } = useSettings() const { userName } = useSettings()
const { theme } = useTheme() const { settedTheme } = useTheme()
const topicId = conversationId const topicId = conversationId
@ -491,7 +491,7 @@ const ChatFlowHistory: FC<ChatFlowHistoryProps> = ({ conversationId }) => {
}} }}
proOptions={{ hideAttribution: true }} proOptions={{ hideAttribution: true }}
className="react-flow-container" className="react-flow-container"
colorMode={theme === 'auto' ? 'system' : theme}> colorMode={settedTheme}>
<Controls showInteractive={false} /> <Controls showInteractive={false} />
<MiniMap <MiniMap
nodeStrokeWidth={3} nodeStrokeWidth={3}

View File

@ -54,8 +54,6 @@ const ColorCircle = styled.div<{ color: string; isActive?: boolean }>`
const DisplaySettings: FC = () => { const DisplaySettings: FC = () => {
const { const {
setTheme,
theme,
windowStyle, windowStyle,
setWindowStyle, setWindowStyle,
topicPosition, topicPosition,
@ -65,10 +63,11 @@ const DisplaySettings: FC = () => {
pinTopicsToTop, pinTopicsToTop,
customCss, customCss,
sidebarIcons, sidebarIcons,
setTheme,
assistantIconType, assistantIconType,
userTheme userTheme
} = useSettings() } = useSettings()
const { theme: themeMode } = useTheme() const { theme, settedTheme } = useTheme()
const { t } = useTranslation() const { t } = useTranslation()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const [currentZoom, setCurrentZoom] = useState(1.0) const [currentZoom, setCurrentZoom] = useState(1.0)
@ -121,11 +120,11 @@ const DisplaySettings: FC = () => {
) )
}, },
{ {
value: ThemeMode.auto, value: ThemeMode.system,
label: ( label: (
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<SyncOutlined /> <SyncOutlined />
<span>{t('settings.theme.auto')}</span> <span>{t('settings.theme.system')}</span>
</div> </div>
) )
} }
@ -168,13 +167,13 @@ const DisplaySettings: FC = () => {
) )
return ( return (
<SettingContainer theme={themeMode}> <SettingContainer theme={theme}>
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
<SettingTitle>{t('settings.display.title')}</SettingTitle> <SettingTitle>{t('settings.display.title')}</SettingTitle>
<SettingDivider /> <SettingDivider />
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.theme.title')}</SettingRowTitle> <SettingRowTitle>{t('settings.theme.title')}</SettingRowTitle>
<Segmented value={theme} shape="round" onChange={setTheme} options={themeOptions} /> <Segmented value={settedTheme} shape="round" onChange={setTheme} options={themeOptions} />
</SettingRow> </SettingRow>
<SettingDivider /> <SettingDivider />
<SettingRow> <SettingRow>

View File

@ -19,7 +19,6 @@ const GeneralSettings: FC = () => {
const { const {
language, language,
proxyUrl: storeProxyUrl, proxyUrl: storeProxyUrl,
theme,
setLaunch, setLaunch,
setTray, setTray,
launchOnBoot, launchOnBoot,
@ -30,7 +29,7 @@ const GeneralSettings: FC = () => {
enableDataCollection enableDataCollection
} = useSettings() } = useSettings()
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl) const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
const { theme: themeMode } = useTheme() const { theme } = useTheme()
const updateTray = (isShowTray: boolean) => { const updateTray = (isShowTray: boolean) => {
setTray(isShowTray) setTray(isShowTray)
@ -116,7 +115,7 @@ const GeneralSettings: FC = () => {
} }
return ( return (
<SettingContainer theme={themeMode}> <SettingContainer theme={theme}>
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
<SettingTitle>{t('settings.general.title')}</SettingTitle> <SettingTitle>{t('settings.general.title')}</SettingTitle>
<SettingDivider /> <SettingDivider />

View File

@ -198,7 +198,7 @@ export const initialState: SettingsState = {
launchToTray: false, launchToTray: false,
trayOnClose: true, trayOnClose: true,
tray: true, tray: true,
theme: ThemeMode.auto, theme: ThemeMode.system,
userTheme: { userTheme: {
colorPrimary: '#00b96b' colorPrimary: '#00b96b'
}, },

View File

@ -317,7 +317,7 @@ export enum FileTypes {
export enum ThemeMode { export enum ThemeMode {
light = 'light', light = 'light',
dark = 'dark', dark = 'dark',
auto = 'auto' system = 'system'
} }
export type LanguageVarious = 'zh-CN' | 'zh-TW' | 'el-GR' | 'en-US' | 'es-ES' | 'fr-FR' | 'ja-JP' | 'pt-PT' | 'ru-RU' export type LanguageVarious = 'zh-CN' | 'zh-TW' | 'el-GR' | 'en-US' | 'es-ES' | 'fr-FR' | 'ja-JP' | 'pt-PT' | 'ru-RU'

View File

@ -1,4 +1,5 @@
import { isMac } from '@renderer/config/constant' import { isMac } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useDefaultAssistant, useDefaultModel } from '@renderer/hooks/useAssistant' import { useDefaultAssistant, useDefaultModel } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
@ -9,6 +10,7 @@ import store from '@renderer/store'
import { upsertManyBlocks } from '@renderer/store/messageBlock' import { upsertManyBlocks } from '@renderer/store/messageBlock'
import { updateOneBlock, upsertOneBlock } from '@renderer/store/messageBlock' import { updateOneBlock, upsertOneBlock } from '@renderer/store/messageBlock'
import { newMessagesActions } from '@renderer/store/newMessage' import { newMessagesActions } from '@renderer/store/newMessage'
import { ThemeMode } from '@renderer/types'
import { Chunk, ChunkType } from '@renderer/types/chunk' import { Chunk, ChunkType } from '@renderer/types/chunk'
import { AssistantMessageStatus } from '@renderer/types/newMessage' import { AssistantMessageStatus } from '@renderer/types/newMessage'
import { MessageBlockStatus } from '@renderer/types/newMessage' import { MessageBlockStatus } from '@renderer/types/newMessage'
@ -43,7 +45,8 @@ const HomeWindow: FC = () => {
const { defaultModel, quickAssistantModel } = useDefaultModel() const { defaultModel, quickAssistantModel } = useDefaultModel()
// 如果 quickAssistantModel 未設定,則使用 defaultModel // 如果 quickAssistantModel 未設定,則使用 defaultModel
const model = quickAssistantModel || defaultModel const model = quickAssistantModel || defaultModel
const { language, readClipboardAtStartup, windowStyle, theme } = useSettings() const { language, readClipboardAtStartup, windowStyle } = useSettings()
const { theme } = useTheme()
const { t } = useTranslation() const { t } = useTranslation()
const inputBarRef = useRef<HTMLDivElement>(null) const inputBarRef = useRef<HTMLDivElement>(null)
const featureMenusRef = useRef<FeatureMenusRef>(null) const featureMenusRef = useRef<FeatureMenusRef>(null)
@ -258,12 +261,7 @@ const HomeWindow: FC = () => {
const backgroundColor = () => { const backgroundColor = () => {
// ONLY MAC: when transparent style + light theme: use vibrancy effect // ONLY MAC: when transparent style + light theme: use vibrancy effect
// because the dark style under mac's vibrancy effect has not been implemented // because the dark style under mac's vibrancy effect has not been implemented
if ( if (isMac && windowStyle === 'transparent' && theme === ThemeMode.light) {
isMac &&
windowStyle === 'transparent' &&
theme === 'light' &&
!window.matchMedia('(prefers-color-scheme: dark)').matches
) {
return 'transparent' return 'transparent'
} }