diff --git a/package.json b/package.json index 9cfa617f..f4369a7d 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@electron-toolkit/utils": "^3.0.0", "@sentry/electron": "^5.2.0", "electron-log": "^5.1.5", + "electron-store": "^8.2.0", "electron-updater": "^6.1.7", "electron-window-state": "^5.0.3" }, diff --git a/src/main/config.ts b/src/main/config.ts new file mode 100644 index 00000000..9219d16d --- /dev/null +++ b/src/main/config.ts @@ -0,0 +1,15 @@ +import Store from 'electron-store' + +export const appConfig = new Store() + +export const titleBarOverlayDark = { + height: 41, + color: '#1f1f1f', + symbolColor: '#ffffff' +} + +export const titleBarOverlayLight = { + height: 41, + color: '#f8f8f8', + symbolColor: '#000000' +} diff --git a/src/main/index.ts b/src/main/index.ts index e9a3ed82..96190795 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,8 +5,9 @@ import installExtension, { REDUX_DEVTOOLS } from 'electron-devtools-installer' import windowStateKeeper from 'electron-window-state' import { join } from 'path' import icon from '../../resources/icon.png?asset' -import AppUpdater from './updater' +import { appConfig, titleBarOverlayDark, titleBarOverlayLight } from './config' import { saveFile } from './event' +import AppUpdater from './updater' function createWindow() { // Load the previous state with fallback to defaults @@ -15,6 +16,8 @@ function createWindow() { defaultHeight: 670 }) + const theme = appConfig.get('theme') || 'light' + // Create the browser window. const mainWindow = new BrowserWindow({ x: mainWindowState.x, @@ -26,11 +29,7 @@ function createWindow() { show: true, autoHideMenuBar: true, titleBarStyle: 'hidden', - titleBarOverlay: { - height: 41, - color: '#1f1f1f', - symbolColor: '#eee' - }, + titleBarOverlay: theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight, trafficLightPosition: { x: 8, y: 12 }, ...(process.platform === 'linux' ? { icon } : {}), webPreferences: { @@ -118,6 +117,12 @@ app.whenReady().then(() => { ipcMain.handle('save-file', saveFile) + ipcMain.handle('set-theme', (_, theme: 'light' | 'dark') => { + appConfig.set('theme', theme) + mainWindow?.setTitleBarOverlay && + mainWindow.setTitleBarOverlay(theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight) + }) + // 触发检查更新(此方法用于被渲染线程调用,例如页面点击检查更新按钮来调用此方法) ipcMain.handle('check-for-update', async () => { autoUpdater.logger?.info('触发检查更新') diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index a08c333c..598056eb 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -12,6 +12,7 @@ declare global { openWebsite: (url: string) => void setProxy: (proxy: string | undefined) => void saveFile: (path: string, content: string) => void + setTheme: (theme: 'light' | 'dark') => void } } } diff --git a/src/preload/index.ts b/src/preload/index.ts index 8f1cb7bf..1b3a34f7 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -7,7 +7,8 @@ const api = { checkForUpdate: () => ipcRenderer.invoke('check-for-update'), openWebsite: (url: string) => ipcRenderer.invoke('open-website', url), setProxy: (proxy: string) => ipcRenderer.invoke('set-proxy', proxy), - saveFile: (path: string, content: string) => ipcRenderer.invoke('save-file', path, content) + saveFile: (path: string, content: string) => ipcRenderer.invoke('save-file', path, content), + setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke('set-theme', theme) } // Use `contextBridge` APIs to expose Electron APIs to diff --git a/src/renderer/index.html b/src/renderer/index.html index b425090a..8c6fbec9 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -4,13 +4,11 @@ Cherry Studio - - - +
diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 4e6ede0f..010e014e 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1,35 +1,37 @@ import store, { persistor } from '@renderer/store' -import { ConfigProvider } from 'antd' import { Provider } from 'react-redux' import { HashRouter, Route, Routes } from 'react-router-dom' import { PersistGate } from 'redux-persist/integration/react' import Sidebar from './components/app/Sidebar' import TopViewContainer from './components/TopView' -import { AntdThemeConfig, getAntdLocale } from './config/antd' import AppsPage from './pages/apps/AppsPage' import HomePage from './pages/home/HomePage' import SettingsPage from './pages/settings/SettingsPage' import TranslatePage from './pages/translate/TranslatePage' +import AntdProvider from './providers/AntdProvider' +import { ThemeProvider } from './providers/ThemeProvider' function App(): JSX.Element { return ( - - - - - - - - } /> - } /> - } /> - } /> - - - - - - + + + + + + + + + } /> + } /> + } /> + } /> + + + + + + + ) } diff --git a/src/renderer/src/assets/fonts/icon-fonts/iconfont.css b/src/renderer/src/assets/fonts/icon-fonts/iconfont.css index 098b60a9..95abc881 100644 --- a/src/renderer/src/assets/fonts/icon-fonts/iconfont.css +++ b/src/renderer/src/assets/fonts/icon-fonts/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 4563475 */ - src: url('iconfont.woff2?t=1722099305424') format('woff2'), - url('iconfont.woff?t=1722099305424') format('woff'), - url('iconfont.ttf?t=1722099305424') format('truetype'); + src: url('iconfont.woff2?t=1722242729348') format('woff2'), + url('iconfont.woff?t=1722242729348') format('woff'), + url('iconfont.ttf?t=1722242729348') format('truetype'); } .iconfont { @@ -13,6 +13,14 @@ -moz-osx-font-smoothing: grayscale; } +.icon-dark1:before { + content: "\e72f"; +} + +.icon-theme-light:before { + content: "\e6b7"; +} + .icon-translate_line:before { content: "\e7de"; } diff --git a/src/renderer/src/assets/fonts/icon-fonts/iconfont.ttf b/src/renderer/src/assets/fonts/icon-fonts/iconfont.ttf index 64e9a4e3..5ea01e35 100644 Binary files a/src/renderer/src/assets/fonts/icon-fonts/iconfont.ttf and b/src/renderer/src/assets/fonts/icon-fonts/iconfont.ttf differ diff --git a/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff b/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff index 44baa8be..d20c02b0 100644 Binary files a/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff and b/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff differ diff --git a/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2 b/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2 index c2e0fb69..fcf48bfc 100644 Binary files a/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2 and b/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2 differ diff --git a/src/renderer/src/assets/styles/index.scss b/src/renderer/src/assets/styles/index.scss index 211f849f..ee07999c 100644 --- a/src/renderer/src/assets/styles/index.scss +++ b/src/renderer/src/assets/styles/index.scss @@ -2,11 +2,6 @@ @import './markdown.scss'; @import './scrollbar.scss'; -// @font-face { -// font-family: 'Playwrite'; -// src: url(../fonts/Playwrite.ttf) format('truetype'); -// } - :root { --color-white: #ffffff; --color-white-soft: #f8f8f8; @@ -28,17 +23,22 @@ --color-background-soft: var(--color-black-soft); --color-background-mute: var(--color-black-mute); - --color-primary: #00b96b; - --color-primary-soft: #00b96b99; - --color-primary-mute: #00b96b33; + --color-primary: #135200; + --color-primary-soft: #13520099; + --color-primary-mute: #13520033; --color-text: var(--color-text-1); --color-icon: #ffffff99; --color-icon-white: #ffffff; --color-border: #ffffff20; --color-error: #f44336; + --color-code-background: #323232; + --color-scrollbar-thumb: rgba(255, 255, 255, 0.15); + --color-scrollbar-thumb-hover: rgba(255, 255, 255, 0.3); --navbar-background: #1f1f1f; + --sidebar-background: #1f1f1f; + --navbar-height: 42px; --sidebar-width: 55px; --assistants-width: 245px; @@ -48,6 +48,44 @@ --input-bar-height: 125px; } +body[theme-mode='light'] { + --color-white: #ffffff; + --color-white-soft: #f8f8f8; + --color-white-mute: #efefef; + + --color-black: #1b1b1f; + --color-black-soft: #262626; + --color-black-mute: #363636; + + --color-gray-1: #8e8e93; + --color-gray-2: #aeaeb2; + --color-gray-3: #c7c7cc; + + --color-text-1: rgba(0, 0, 0, 1); + --color-text-2: rgba(0, 0, 0, 0.6); + --color-text-3: rgba(0, 0, 0, 0.38); + + --color-background: #ffffff; + --color-background-soft: var(--color-white-soft); + --color-background-mute: var(--color-white-mute); + + --color-primary: #00b96b; + --color-primary-soft: #00b96b99; + --color-primary-mute: #00b96b33; + + --color-text: var(--color-text-1); + --color-icon: #00000099; + --color-icon-white: #000000; + --color-border: #00000028; + --color-error: #f44336; + --color-code-background: #e3e3e3; + --color-scrollbar-thumb: rgba(0, 0, 0, 0.15); + --color-scrollbar-thumb-hover: rgba(0, 0, 0, 0.3); + + --navbar-background: #f8f8f8; + --sidebar-background: #f8f8f8; +} + *, *::before, *::after { @@ -68,8 +106,18 @@ body { line-height: 1.6; overflow: hidden; background-size: cover; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', - 'Droid Sans', 'Helvetica Neue', sans-serif; + font-family: + -apple-system, + BlinkMacSystemFont, + 'Microsoft YaHei', + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue' sans-serif; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -97,3 +145,9 @@ body, #inputbar .ant-input { resize: none; } + +.chat-nav-dropdown { + .ant-dropdown-menu { + padding-bottom: 12px; + } +} diff --git a/src/renderer/src/assets/styles/markdown.scss b/src/renderer/src/assets/styles/markdown.scss index ba1ae58e..6e2f4a55 100644 --- a/src/renderer/src/assets/styles/markdown.scss +++ b/src/renderer/src/assets/styles/markdown.scss @@ -1,5 +1,5 @@ .markdown { - color: #f1f1f1; + color: var(--color-text); font-size: 15px; line-height: 1.6; user-select: text; @@ -33,44 +33,36 @@ h1 { font-size: 2em; - color: #fff; } h2 { font-size: 1.5em; - color: #fff; } h3 { font-size: 1.2em; - color: #fff; } h4 { font-size: 1em; - color: #fff; } h5 { font-size: 0.9em; - color: #fff; } h6 { font-size: 0.8em; - color: #fff; } p { margin: 1em 0; - color: #fff; } ul, ol { padding-left: 1.5em; margin: 1em 0; - color: #ccc; } li { diff --git a/src/renderer/src/assets/styles/scrollbar.scss b/src/renderer/src/assets/styles/scrollbar.scss index 29a95c94..10865ced 100644 --- a/src/renderer/src/assets/styles/scrollbar.scss +++ b/src/renderer/src/assets/styles/scrollbar.scss @@ -8,8 +8,8 @@ } ::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.15); + background: var(--color-scrollbar-thumb); &:hover { - background: rgba(255, 255, 255, 0.3); + background: var(--color-scrollbar-thumb-hover); } } diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index 3de3e7ca..8502a967 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -56,7 +56,7 @@ const Container = styled.div` min-width: var(--sidebar-width); min-height: 100%; -webkit-app-region: drag !important; - background-color: #1f1f1f; + background-color: var(--sidebar-background); border-right: 0.5px solid var(--color-border); padding-top: var(--navbar-height); position: relative; @@ -68,7 +68,7 @@ const AvatarImg = styled.img` height: 28px; background-color: var(--color-background-soft); margin: 5px 0; - margin-top: ${isMac ? '16px' : '7px'}; + margin-top: ${isMac ? '16px' : '9px'}; ` const MainMenus = styled.div` display: flex; @@ -102,7 +102,7 @@ const Icon = styled.div` font-size: 17px; } &:hover { - background-color: #ffffff30; + background-color: var(--color-background-soft); cursor: pointer; .iconfont, .anticon { @@ -110,7 +110,7 @@ const Icon = styled.div` } } &.active { - background-color: #ffffff20; + background-color: var(--color-background-mute); .iconfont, .anticon { color: var(--color-icon-white); diff --git a/src/renderer/src/config/antd.ts b/src/renderer/src/config/antd.ts deleted file mode 100644 index 34c3baa5..00000000 --- a/src/renderer/src/config/antd.ts +++ /dev/null @@ -1,26 +0,0 @@ -import store from '@renderer/store' -import { theme, ThemeConfig } from 'antd' -import zhCN from 'antd/locale/zh_CN' - -export const colorPrimary = '#00b96b' - -export const AntdThemeConfig: ThemeConfig = { - token: { - colorPrimary, - borderRadius: 5 - }, - algorithm: [theme.darkAlgorithm] -} - -export function getAntdLocale() { - const language = store.getState().settings.language - - switch (language) { - case 'zh-CN': - return zhCN - case 'en-US': - return undefined - default: - return zhCN - } -} diff --git a/src/renderer/src/hooks/useSettings.ts b/src/renderer/src/hooks/useSettings.ts index 8720e816..b1fec33d 100644 --- a/src/renderer/src/hooks/useSettings.ts +++ b/src/renderer/src/hooks/useSettings.ts @@ -1,5 +1,10 @@ import { useAppDispatch, useAppSelector } from '@renderer/store' -import { setSendMessageShortcut as _setSendMessageShortcut, SendMessageShortcut } from '@renderer/store/settings' +import { + setSendMessageShortcut as _setSendMessageShortcut, + SendMessageShortcut, + setTheme, + ThemeMode +} from '@renderer/store/settings' export function useSettings() { const settings = useAppSelector((state) => state.settings) @@ -9,6 +14,9 @@ export function useSettings() { ...settings, setSendMessageShortcut(shortcut: SendMessageShortcut) { dispatch(_setSendMessageShortcut(shortcut)) + }, + setTheme(theme: ThemeMode) { + dispatch(setTheme(theme)) } } } diff --git a/src/renderer/src/i18n/index.ts b/src/renderer/src/i18n/index.ts index 0a903840..9ebe395f 100644 --- a/src/renderer/src/i18n/index.ts +++ b/src/renderer/src/i18n/index.ts @@ -156,7 +156,11 @@ const resources = { 'about.feedback.button': 'Feedback', 'about.contact.title': '📧 Contact', 'about.contact.button': 'Email', - 'proxy.title': 'Proxy Address' + 'proxy.title': 'Proxy Address', + 'theme.title': 'Theme', + 'theme.dark': 'Dark', + 'theme.light': 'Light', + 'theme.auto': 'Auto' }, translate: { title: 'Translation', @@ -334,7 +338,11 @@ const resources = { 'about.feedback.button': '反馈', 'about.contact.title': '📧 邮件联系', 'about.contact.button': '邮件', - 'proxy.title': '代理地址' + 'proxy.title': '代理地址', + 'theme.title': '主题', + 'theme.dark': '深色主题', + 'theme.light': '浅色主题', + 'theme.auto': '跟随系统' }, translate: { title: '翻译', diff --git a/src/renderer/src/init.ts b/src/renderer/src/init.ts index 9ce61951..8e70a2b0 100644 --- a/src/renderer/src/init.ts +++ b/src/renderer/src/init.ts @@ -2,6 +2,7 @@ import localforage from 'localforage' import KeyvStorage from '@kangfenmao/keyv-storage' import * as Sentry from '@sentry/electron/renderer' import { isProduction, loadScript } from './utils' +import { ThemeMode } from './store/settings' async function initSentry() { if (await isProduction()) { @@ -21,12 +22,12 @@ async function initSentry() { } } -export async function initMermaid() { +export async function initMermaid(theme: ThemeMode) { if (!window.mermaid) { await loadScript('https://unpkg.com/mermaid@10.9.1/dist/mermaid.min.js') window.mermaid.initialize({ startOnLoad: true, - theme: 'dark', + theme: theme === ThemeMode.dark ? 'dark' : 'default', securityLevel: 'loose' }) window.mermaid.contentLoaded() diff --git a/src/renderer/src/pages/apps/AppsPage.tsx b/src/renderer/src/pages/apps/AppsPage.tsx index 932ec215..eea49ace 100644 --- a/src/renderer/src/pages/apps/AppsPage.tsx +++ b/src/renderer/src/pages/apps/AppsPage.tsx @@ -116,12 +116,16 @@ const AssistantCard = styled.div` display: flex; flex-direction: row; margin-bottom: 16px; - background-color: #111; - border: 0.5px solid #151515; + background-color: var(--color-background-soft); + border: 0.5px solid var(--color-border); border-radius: 10px; padding: 15px; position: relative; cursor: pointer; + transition: all 0.2s ease-in-out; + &:hover { + background-color: var(--color-background-mute); + } ` const EmojiHeader = styled.div` width: 25px; @@ -148,7 +152,7 @@ const AssistantName = styled(Title)` -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; - color: #fff; + color: var(--color-white); font-weight: 900; ` diff --git a/src/renderer/src/pages/home/HomePage.tsx b/src/renderer/src/pages/home/HomePage.tsx index 04beb829..6dc7fce3 100644 --- a/src/renderer/src/pages/home/HomePage.tsx +++ b/src/renderer/src/pages/home/HomePage.tsx @@ -9,6 +9,8 @@ import { useShowAssistants, useShowRightSidebar } from '@renderer/hooks/useStore import Navigation from './components/NavigationCenter' import { isMac, isWindows } from '@renderer/config/constant' import { Assistant } from '@renderer/types' +import { useTheme } from '@renderer/providers/ThemeProvider' +import { Switch } from 'antd' let _activeAssistant: Assistant @@ -18,6 +20,7 @@ const HomePage: FC = () => { const { rightSidebarShown, toggleRightSidebar } = useShowRightSidebar() const { showAssistants, toggleShowAssistants } = useShowAssistants() const { defaultAssistant } = useDefaultAssistant() + const { theme, toggleTheme } = useTheme() _activeAssistant = activeAssistant @@ -42,6 +45,12 @@ const HomePage: FC = () => { )} + } + unCheckedChildren={} + defaultChecked={theme === 'dark'} + onChange={toggleTheme} + /> @@ -101,4 +110,12 @@ export const NewButton = styled.div` } ` +const ThemeSwitch = styled(Switch)` + -webkit-app-region: none; + margin-right: 8px; + .icon-theme { + font-size: 14px; + } +` + export default HomePage diff --git a/src/renderer/src/pages/home/components/Assistants.tsx b/src/renderer/src/pages/home/components/Assistants.tsx index f6f5e8ee..cc2d8621 100644 --- a/src/renderer/src/pages/home/components/Assistants.tsx +++ b/src/renderer/src/pages/home/components/Assistants.tsx @@ -96,7 +96,9 @@ const Assistants: FC = ({ activeAssistant, setActiveAssistant, onCreateAs onSwitchAssistant(assistant)} className={assistant.id === activeAssistant?.id ? 'active' : ''}> - {assistant.name || t('assistant.default.name')} + + {assistant.name || t('assistant.default.name')} + @@ -143,13 +145,15 @@ const AssistantItem = styled.div` &.active { background-color: var(--color-background-mute); cursor: pointer; + .name { + font-weight: bolder; + } } ` const AssistantName = styled.div` font-size: 14px; - color: var(--color-text-1); - font-weight: 500; + color: var(--color-text); display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; diff --git a/src/renderer/src/pages/home/components/CodeBlock.tsx b/src/renderer/src/pages/home/components/CodeBlock.tsx index 4d733cba..7b102635 100644 --- a/src/renderer/src/pages/home/components/CodeBlock.tsx +++ b/src/renderer/src/pages/home/components/CodeBlock.tsx @@ -1,11 +1,13 @@ -import React from 'react' -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' -import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism' -import styled from 'styled-components' -import { CopyOutlined } from '@ant-design/icons' -import { useTranslation } from 'react-i18next' -import Mermaid from './Mermaid' +import { CheckOutlined, CopyOutlined } from '@ant-design/icons' import { initMermaid } from '@renderer/init' +import { useTheme } from '@renderer/providers/ThemeProvider' +import { ThemeMode } from '@renderer/store/settings' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' +import { atomDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism' +import styled from 'styled-components' +import Mermaid from './Mermaid' interface CodeBlockProps { children: string @@ -15,16 +17,20 @@ interface CodeBlockProps { const CodeBlock: React.FC = ({ children, className, ...rest }) => { const match = /language-(\w+)/.exec(className || '') + const [copied, setCopied] = useState(false) + const { theme } = useTheme() const { t } = useTranslation() const onCopy = () => { navigator.clipboard.writeText(children) window.message.success({ content: t('message.copied'), key: 'copy-code' }) + setCopied(true) + setTimeout(() => setCopied(false), 2000) } if (match && match[1] === 'mermaid') { - initMermaid() + initMermaid(theme) return } @@ -32,12 +38,13 @@ const CodeBlock: React.FC = ({ children, className, ...rest }) =
{'<' + match[1].toUpperCase() + '>'} - + {!copied && } + {copied && } {String(children).replace(/\n$/, '')} @@ -54,10 +61,10 @@ const CodeHeader = styled.div` display: flex; align-items: center; justify-content: space-between; - color: #fff; + color: var(--color-text); font-size: 14px; font-weight: bold; - background-color: #323232; + background-color: var(--color-code-background); height: 40px; padding: 0 10px; border-top-left-radius: 8px; diff --git a/src/renderer/src/pages/home/components/Inputbar.tsx b/src/renderer/src/pages/home/components/Inputbar.tsx index 5263fc23..4a8746ae 100644 --- a/src/renderer/src/pages/home/components/Inputbar.tsx +++ b/src/renderer/src/pages/home/components/Inputbar.tsx @@ -248,7 +248,7 @@ const ToolbarButton = styled(Button)` &:hover { background-color: var(--color-background-soft); .anticon { - color: white; + color: var(--color-text-1); } } ` @@ -260,7 +260,7 @@ const TextCount = styled.div` font-size: 11px; color: var(--color-text-3); z-index: 10; - background-color: #121212; + background-color: var(--color-background-soft); padding: 2px 8px; border-top-left-radius: 7px; user-select: none; diff --git a/src/renderer/src/pages/home/components/Message.tsx b/src/renderer/src/pages/home/components/Message.tsx index 0a046536..e51229b9 100644 --- a/src/renderer/src/pages/home/components/Message.tsx +++ b/src/renderer/src/pages/home/components/Message.tsx @@ -1,16 +1,23 @@ -import { CopyOutlined, DeleteOutlined, EditOutlined, MenuOutlined, SaveOutlined, SyncOutlined } from '@ant-design/icons' -import Logo from '@renderer/assets/images/logo.png' +import { + CheckOutlined, + CopyOutlined, + DeleteOutlined, + EditOutlined, + MenuOutlined, + SaveOutlined, + SyncOutlined +} from '@ant-design/icons' import { getModelLogo } from '@renderer/config/provider' import { useAssistant } from '@renderer/hooks/useAssistant' import useAvatar from '@renderer/hooks/useAvatar' import { useSettings } from '@renderer/hooks/useSettings' import { EVENT_NAMES, EventEmitter } from '@renderer/services/event' import { Message } from '@renderer/types' -import { firstLetter } from '@renderer/utils' +import { firstLetter, removeLeadingEmoji } from '@renderer/utils' import { Avatar, Dropdown, Tooltip } from 'antd' import dayjs from 'dayjs' import { isEmpty, upperFirst } from 'lodash' -import { FC, useCallback } from 'react' +import { FC, useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Markdown from 'react-markdown' import styled from 'styled-components' @@ -31,6 +38,7 @@ const MessageItem: FC = ({ message, index, showMenu, onDeleteMessage }) = const { assistant } = useAssistant(message.assistantId) const { userName, showMessageDivider, messageFont } = useSettings() const { generating } = useRuntime() + const [copied, setCopied] = useState(false) const isLastMessage = index === 0 const isUserMessage = message.role === 'user' @@ -39,6 +47,8 @@ const MessageItem: FC = ({ message, index, showMenu, onDeleteMessage }) = const onCopy = () => { navigator.clipboard.writeText(message.content) window.message.success({ content: t('message.copied'), key: 'copy-message' }) + setCopied(true) + setTimeout(() => setCopied(false), 2000) } const onDelete = async () => { @@ -105,14 +115,14 @@ const MessageItem: FC = ({ message, index, showMenu, onDeleteMessage }) = {message.role === 'assistant' ? ( - - {firstLetter(message.modelId).toUpperCase()} + + {firstLetter(assistant?.name).toUpperCase()} ) : ( )} - {getUserName()} + {removeLeadingEmoji(getUserName())} {dayjs(message.createdAt).format('MM/DD HH:mm')} @@ -137,25 +147,26 @@ const MessageItem: FC = ({ message, index, showMenu, onDeleteMessage }) = {message.role === 'user' && ( - - + + )} - - + + {!copied && } + {copied && } - - + + {canRegenerate && ( - - + + )} diff --git a/src/renderer/src/pages/home/components/NavigationCenter.tsx b/src/renderer/src/pages/home/components/NavigationCenter.tsx index 7e673650..966c7bd5 100644 --- a/src/renderer/src/pages/home/components/NavigationCenter.tsx +++ b/src/renderer/src/pages/home/components/NavigationCenter.tsx @@ -1,6 +1,5 @@ import { CodeSandboxOutlined } from '@ant-design/icons' import { NavbarCenter } from '@renderer/components/app/Navbar' -import { colorPrimary } from '@renderer/config/antd' import { isMac } from '@renderer/config/constant' import { useAssistant } from '@renderer/hooks/useAssistant' import { useProviders } from '@renderer/hooks/useProvider' @@ -13,6 +12,7 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { NewButton } from '../HomePage' import { getModelLogo } from '@renderer/config/provider' +import { removeLeadingEmoji } from '@renderer/utils' interface Props { activeAssistant: Assistant @@ -34,7 +34,7 @@ const NavigationCenter: FC = ({ activeAssistant }) => { children: p.models.map((m) => ({ key: m.id, label: upperFirst(m.name), - style: m.id === model?.id ? { color: colorPrimary } : undefined, + style: m.id === model?.id ? { color: 'var(--color-primary)' } : undefined, icon: , onClick: () => setModel(m) })) @@ -47,8 +47,11 @@ const NavigationCenter: FC = ({ activeAssistant }) => { )} - {assistant?.name || t('assistant.default.name')} - + {removeLeadingEmoji(assistant?.name) || t('assistant.default.name')} + {model ? upperFirst(model.name) : t('button.select_model')} @@ -69,13 +72,14 @@ const AssistantName = styled.span` ` const DropdownButton = styled(Button)` - font-size: 10px; + font-size: 11px; border-radius: 15px; padding: 0 8px; ` const ModelName = styled.span` margin-left: -2px; + font-weight: bolder; ` export default NavigationCenter diff --git a/src/renderer/src/pages/home/components/RightSidebar.tsx b/src/renderer/src/pages/home/components/RightSidebar.tsx index e02f930c..de809f3f 100644 --- a/src/renderer/src/pages/home/components/RightSidebar.tsx +++ b/src/renderer/src/pages/home/components/RightSidebar.tsx @@ -90,10 +90,10 @@ const Tab = styled.div` align-items: center; font-size: 13px; cursor: pointer; - color: #8a8a8a; + color: var(--color-text-3); border-bottom: 1px solid transparent; &.active { - color: #bbb; + color: var(--color-text-2); font-weight: 600; } ` diff --git a/src/renderer/src/pages/home/components/TopicsTab.tsx b/src/renderer/src/pages/home/components/TopicsTab.tsx index 4e4f233c..286b0f46 100644 --- a/src/renderer/src/pages/home/components/TopicsTab.tsx +++ b/src/renderer/src/pages/home/components/TopicsTab.tsx @@ -130,7 +130,7 @@ const TopicListItem = styled.div` margin-bottom: 5px; cursor: pointer; border-radius: 5px; - font-size: 13px; + font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -138,7 +138,8 @@ const TopicListItem = styled.div` background-color: var(--color-background-soft); } &.active { - background-color: var(--color-background-soft); + background-color: var(--color-background-mute); + font-weight: bolder; } ` diff --git a/src/renderer/src/pages/settings/GeneralSettings.tsx b/src/renderer/src/pages/settings/GeneralSettings.tsx index 2acd2e2f..dfca3f7e 100644 --- a/src/renderer/src/pages/settings/GeneralSettings.tsx +++ b/src/renderer/src/pages/settings/GeneralSettings.tsx @@ -8,14 +8,14 @@ import useAvatar from '@renderer/hooks/useAvatar' import { useAppDispatch } from '@renderer/store' import { setAvatar } from '@renderer/store/runtime' import { useSettings } from '@renderer/hooks/useSettings' -import { setLanguage, setUserName } from '@renderer/store/settings' +import { setLanguage, setUserName, ThemeMode } from '@renderer/store/settings' import { useTranslation } from 'react-i18next' import { setProxyUrl as _setProxyUrl } from '@renderer/store/settings' import i18n from '@renderer/i18n' const GeneralSettings: FC = () => { const avatar = useAvatar() - const { language, proxyUrl: storeProxyUrl, userName } = useSettings() + const { language, proxyUrl: storeProxyUrl, userName, theme, setTheme } = useSettings() const [proxyUrl, setProxyUrl] = useState(storeProxyUrl) const dispatch = useAppDispatch() const { t } = useTranslation() @@ -53,6 +53,20 @@ const GeneralSettings: FC = () => { /> + + {t('settings.theme.title')} +