fix: system theme

This commit is contained in:
jialin
2025-05-14 20:13:54 +08:00
parent 18cb82a0fe
commit 5488a8ea82
13 changed files with 99 additions and 95 deletions
+3 -1
View File
@@ -3,13 +3,15 @@ import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils'; import { atomWithStorage } from 'jotai/utils';
type UserSettings = { type UserSettings = {
theme: 'light' | 'realDark' | 'auto'; theme: 'light' | 'realDark';
mode: 'light' | 'realDark' | 'auto';
colorPrimary: string; colorPrimary: string;
isDarkTheme: boolean; isDarkTheme: boolean;
}; };
export const userSettingsAtom = atomWithStorage<UserSettings>('userSettings', { export const userSettingsAtom = atomWithStorage<UserSettings>('userSettings', {
theme: 'light', theme: 'light',
mode: 'auto',
isDarkTheme: false, isDarkTheme: false,
colorPrimary: colorPrimary colorPrimary: colorPrimary
}); });
+30 -18
View File
@@ -9,6 +9,15 @@ export default function useUserSettings() {
const { light, dark, colorPrimary } = themeConfig; const { light, dark, colorPrimary } = themeConfig;
const [userSettings, setUserSettings] = useAtom(userSettingsHelperAtom); const [userSettings, setUserSettings] = useAtom(userSettingsHelperAtom);
const getCurrentTheme = (mode: Theme): 'light' | 'realDark' => {
if (mode === 'auto') {
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'realDark'
: 'light';
}
return mode;
};
const setHtmlThemeAttr = (theme: string) => { const setHtmlThemeAttr = (theme: string) => {
const html = document.querySelector('html'); const html = document.querySelector('html');
if (html) { if (html) {
@@ -17,39 +26,42 @@ export default function useUserSettings() {
}; };
const themeData = useMemo(() => { const themeData = useMemo(() => {
const themeTokens = userSettings.theme === 'realDark' ? dark : light; const baseTokens = userSettings.theme === 'realDark' ? dark : light;
themeTokens.token.colorPrimary = userSettings.colorPrimary || colorPrimary;
return { return {
...themeTokens ...baseTokens,
token: {
...baseTokens.token,
colorPrimary: userSettings.colorPrimary || colorPrimary
}
}; };
}, [userSettings.theme, userSettings.colorPrimary]); }, [userSettings.theme, userSettings.colorPrimary]);
const setTheme = (theme: Theme) => { const setTheme = (mode: Theme) => {
setHtmlThemeAttr(theme); const currentTheme = getCurrentTheme(mode);
setHtmlThemeAttr(currentTheme);
setUserSettings({ setUserSettings({
theme: theme, theme: currentTheme,
isDarkTheme: theme === 'realDark' mode: mode,
isDarkTheme: currentTheme === 'realDark'
}); });
}; };
useEffect(() => { useEffect(() => {
setHtmlThemeAttr(userSettings.theme); setTheme(userSettings.mode);
}, [userSettings.theme]); }, []);
useEffect(() => { useEffect(() => {
const handleChange = (e: MediaQueryListEvent) => { if (userSettings.mode !== 'auto') return;
setTheme(e.matches ? 'realDark' : 'light');
};
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', handleChange);
if (userSettings.theme === 'auto') { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
setTheme(mediaQuery.matches ? 'realDark' : 'light'); const handleChange = () => {
} setTheme('auto');
};
mediaQuery.addEventListener('change', handleChange);
return () => { return () => {
mediaQuery.removeEventListener('change', handleChange); mediaQuery.removeEventListener('change', handleChange);
}; };
}, [userSettings.theme]); }, [userSettings.mode]);
return { return {
userSettings, userSettings,
+2 -1
View File
@@ -243,5 +243,6 @@ export default {
'common.appearance.darkmode': 'Dark Mode', 'common.appearance.darkmode': 'Dark Mode',
'common.appearance.lightmode': 'Light Mode', 'common.appearance.lightmode': 'Light Mode',
'common.appearance.tips': 'Default follows system preference.', 'common.appearance.tips': 'Default follows system preference.',
'common.button.forgotpassword': 'Forgot password?' 'common.button.forgotpassword': 'Forgot password?',
'common.appearance.theme': 'Theme'
}; };
+3 -2
View File
@@ -149,7 +149,7 @@ export default {
'models.table.apiAccessInfo.gotoCreate': 'Go to Create', 'models.table.apiAccessInfo.gotoCreate': 'Go to Create',
'models.search.parts': '{n} parts', 'models.search.parts': '{n} parts',
'models.search.evaluate.error': 'An error occurred during evaluation: ', 'models.search.evaluate.error': 'An error occurred during evaluation: ',
'models.ollama.deprecated.title': 'Ollama Model Source Will Be Removed', 'models.ollama.deprecated.title': 'Deprecation Notice',
'models.ollama.deprecated.current': 'models.ollama.deprecated.current':
'<span class="bold-text">Current Version (v0.6.1): </span>Ollama models are currently available for use.', '<span class="bold-text">Current Version (v0.6.1): </span>Ollama models are currently available for use.',
'models.ollama.deprecated.upcoming': 'models.ollama.deprecated.upcoming':
@@ -157,5 +157,6 @@ export default {
'models.ollama.deprecated.following': 'models.ollama.deprecated.following':
'<span class="bold-text">Following the v0.7.0 update,</span> all previously deployed models will continue to work as expected.', '<span class="bold-text">Following the v0.7.0 update,</span> all previously deployed models will continue to work as expected.',
'models.ollama.deprecated.issue': 'models.ollama.deprecated.issue':
'See the related issue: <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">#1979 on GitHub</a>.' 'See the related issue: <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">#1979 on GitHub</a>.',
'models.ollama.deprecated.notice': `The Ollama model source has been deprecated as of v0.6.1. For more information, see the <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">related GitHub issue</a>.`
}; };
+3 -1
View File
@@ -243,7 +243,8 @@ export default {
'common.appearance.darkmode': 'Dark Mode', 'common.appearance.darkmode': 'Dark Mode',
'common.appearance.lightmode': 'Light Mode', 'common.appearance.lightmode': 'Light Mode',
'common.appearance.tips': 'Default follows system preference.', 'common.appearance.tips': 'Default follows system preference.',
'common.button.forgotpassword': 'Forgot password?' 'common.button.forgotpassword': 'Forgot password?',
'common.appearance.theme': 'Theme'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -256,4 +257,5 @@ export default {
// 7. 'common.appearance.lightmode': 'Light Mode', // 7. 'common.appearance.lightmode': 'Light Mode',
// 8. 'common.appearance.tips': 'Default follows system preference.' // 8. 'common.appearance.tips': 'Default follows system preference.'
// 9. 'common.button.forgotpassword': 'Forgot Password?' // 9. 'common.button.forgotpassword': 'Forgot Password?'
// 10. 'common.appearance.theme': 'Theme'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+6 -7
View File
@@ -149,7 +149,7 @@ export default {
'models.table.apiAccessInfo.gotoCreate': '作成に移動', 'models.table.apiAccessInfo.gotoCreate': '作成に移動',
'models.search.parts': '{n} 部分', 'models.search.parts': '{n} 部分',
'models.search.evaluate.error': '評価中にエラーが発生しました: ', 'models.search.evaluate.error': '評価中にエラーが発生しました: ',
'models.ollama.deprecated.title': 'Ollama Model Source Will Be Removed', 'models.ollama.deprecated.title': 'Deprecation Notice',
'models.ollama.deprecated.current': 'models.ollama.deprecated.current':
'<span class="bold-text">Current Version (v0.6.1): </span>Ollama models are currently available for use.', '<span class="bold-text">Current Version (v0.6.1): </span>Ollama models are currently available for use.',
'models.ollama.deprecated.upcoming': 'models.ollama.deprecated.upcoming':
@@ -157,13 +157,12 @@ export default {
'models.ollama.deprecated.following': 'models.ollama.deprecated.following':
'<span class="bold-text">Following the v0.7.0 update,</span> all previously deployed models will continue to work as expected.', '<span class="bold-text">Following the v0.7.0 update,</span> all previously deployed models will continue to work as expected.',
'models.ollama.deprecated.issue': 'models.ollama.deprecated.issue':
'See the related issue: <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">#1979 on GitHub</a>.' 'See the related issue: <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">#1979 on GitHub</a>.',
'models.ollama.deprecated.notice': `The Ollama model source has been deprecated as of v0.6.1. For more information, see the <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">related GitHub issue</a>.`
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'models.ollama.deprecated.title': 'Ollama Model Source Will Be Removed', // 1. 'models.ollama.deprecated.title': 'Deprecation Notice',
// 2. 'models.ollama.deprecated.current':'<span class="bold-text">Current Version (v0.6.1): </span>Ollama models are currently available for use.', // 2. 'models.ollama.deprecated.notice': `The Ollama model source has been deprecated as of v0.6.1. For more information, see the <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">related GitHub issue</a>.`
// 3. 'models.ollama.deprecated.upcoming': '<span class="bold-text">Upcoming Version (v0.7.0): </span>The Ollama model source will be removed from the UI.',
// 4. 'models.ollama.deprecated.following': '<span class="bold-text">Following the v0.7.0 update,</span> all previously deployed models will continue to work as expected.',
// 5. 'models.ollama.deprecated.issue': 'See the related issue: <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">#1979 on GitHub</a>.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+5 -3
View File
@@ -237,13 +237,15 @@ export default {
'common.appearance.dark': 'Темная', 'common.appearance.dark': 'Темная',
'common.appearance.light': 'Светлая', 'common.appearance.light': 'Светлая',
'common.appearance.system': 'Как в системе', 'common.appearance.system': 'Как в системе',
'common.exception.404': 'Извините, страница, которую вы посетили, не существует.', 'common.exception.404':
'Извините, страница, которую вы посетили, не существует.',
'common.appearance.darkmode': 'Тёмная тема', 'common.appearance.darkmode': 'Тёмная тема',
'common.appearance.lightmode': 'Светлая тема', 'common.appearance.lightmode': 'Светлая тема',
'common.appearance.tips': 'По умолчанию соответствует системным настройкам.', 'common.appearance.tips': 'По умолчанию соответствует системным настройкам.',
'common.button.forgotpassword': 'Забыли пароль?' 'common.button.forgotpassword': 'Забыли пароль?',
'common.appearance.theme': 'Theme'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'common.appearance.theme': 'Theme'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+5 -2
View File
@@ -150,7 +150,7 @@ export default {
'models.table.apiAccessInfo.gotoCreate': 'Перейти к созданию', 'models.table.apiAccessInfo.gotoCreate': 'Перейти к созданию',
'models.search.parts': '{n} частей', 'models.search.parts': '{n} частей',
'models.search.evaluate.error': 'Возникла ошибка при вычислении: ', 'models.search.evaluate.error': 'Возникла ошибка при вычислении: ',
'models.ollama.deprecated.title': 'Источник моделей Ollama будет удалён', 'models.ollama.deprecated.title': 'Deprecation Notice',
'models.ollama.deprecated.current': 'models.ollama.deprecated.current':
'<span class="bold-text">Текущая версия (v0.6.1): </span>Модели Ollama в настоящее время доступны для использования.', '<span class="bold-text">Текущая версия (v0.6.1): </span>Модели Ollama в настоящее время доступны для использования.',
'models.ollama.deprecated.upcoming': 'models.ollama.deprecated.upcoming':
@@ -158,9 +158,12 @@ export default {
'models.ollama.deprecated.following': 'models.ollama.deprecated.following':
'<span class="bold-text">После обновления до версии (v0.7.0),</span> все ранее развёрнутые модели продолжат работать в обычном режиме.', '<span class="bold-text">После обновления до версии (v0.7.0),</span> все ранее развёрнутые модели продолжат работать в обычном режиме.',
'models.ollama.deprecated.issue': 'models.ollama.deprecated.issue':
'См. связанную проблему: <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">#1979 on GitHub</a>.' 'См. связанную проблему: <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">#1979 on GitHub</a>.',
'models.ollama.deprecated.notice': `The Ollama model source has been deprecated as of v0.6.1. For more information, see the <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">related GitHub issue</a>.`
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'models.ollama.deprecated.title': 'Deprecation Notice',
// 2. 'models.ollama.deprecated.notice': `The Ollama model source has been deprecated as of v0.6.1. For more information, see the <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">related GitHub issue</a>.`
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+2 -1
View File
@@ -238,5 +238,6 @@ export default {
'common.appearance.darkmode': '深色模式', 'common.appearance.darkmode': '深色模式',
'common.appearance.lightmode': '浅色模式', 'common.appearance.lightmode': '浅色模式',
'common.appearance.tips': '默认跟随系统设置', 'common.appearance.tips': '默认跟随系统设置',
'common.button.forgotpassword': '忘记密码?' 'common.button.forgotpassword': '忘记密码?',
'common.appearance.theme': '主题'
}; };
+3 -2
View File
@@ -142,7 +142,7 @@ export default {
'models.table.apiAccessInfo.gotoCreate': '去创建', 'models.table.apiAccessInfo.gotoCreate': '去创建',
'models.search.parts': '{n} 个文件', 'models.search.parts': '{n} 个文件',
'models.search.evaluate.error': '评估过程中发生了错误:', 'models.search.evaluate.error': '评估过程中发生了错误:',
'models.ollama.deprecated.title': 'Ollama 模型源即将被移除', 'models.ollama.deprecated.title': '弃用通知',
'models.ollama.deprecated.current': 'models.ollama.deprecated.current':
'<span class="bold-text">当前版本(v0.6.1):</span>Ollama 模型目前仍可使用。', '<span class="bold-text">当前版本(v0.6.1):</span>Ollama 模型目前仍可使用。',
'models.ollama.deprecated.upcoming': 'models.ollama.deprecated.upcoming':
@@ -150,5 +150,6 @@ export default {
'models.ollama.deprecated.following': 'models.ollama.deprecated.following':
'<span class="bold-text">在 v0.7.0 更新后:</span> 所有先前部署的模型仍将正常运行。', '<span class="bold-text">在 v0.7.0 更新后:</span> 所有先前部署的模型仍将正常运行。',
'models.ollama.deprecated.issue': 'models.ollama.deprecated.issue':
'参见 GitHub 上的问题 <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">#1979</a>。' '参见 GitHub 上的问题 <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">#1979</a>。',
'models.ollama.deprecated.notice': `Ollama 模型来源自 v0.6.1 起已被弃用。更多信息请参见相关的 <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">GitHub 问题</a>。`
}; };
+3 -28
View File
@@ -55,37 +55,12 @@ const RenderMessage = () => {
const intl = useIntl(); const intl = useIntl();
return ( return (
<Tips> <Tips>
<ul>
<li>
<span
dangerouslySetInnerHTML={{
__html: intl.formatMessage({
id: 'models.ollama.deprecated.current'
})
}}
></span>
</li>
<li>
<span
dangerouslySetInnerHTML={{
__html: intl.formatMessage({
id: 'models.ollama.deprecated.upcoming'
})
}}
></span>
</li>
</ul>
<div className="notice"> <div className="notice">
<span
dangerouslySetInnerHTML={{
__html: intl.formatMessage({
id: 'models.ollama.deprecated.following'
})
}}
></span>
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: intl.formatMessage({ id: 'models.ollama.deprecated.issue' }) __html: intl.formatMessage({
id: 'models.ollama.deprecated.notice'
})
}} }}
></div> ></div>
</div> </div>
@@ -217,7 +217,6 @@ const MessageBody: React.FC<MessageBodyProps> = forwardRef(
autoSize={{ minRows: 1 }} autoSize={{ minRows: 1 }}
style={{ borderRadius: 'var(--border-radius-mini)' }} style={{ borderRadius: 'var(--border-radius-mini)' }}
readOnly={loading} readOnly={loading}
onKeyDown={handleKeyDown}
onChange={handleMessageChange} onChange={handleMessageChange}
onPaste={handleOnPaste} onPaste={handleOnPaste}
/> />
@@ -244,7 +243,6 @@ const MessageBody: React.FC<MessageBodyProps> = forwardRef(
variant="filled" variant="filled"
autoSize={{ minRows: 1 }} autoSize={{ minRows: 1 }}
style={{ borderRadius: 'var(--border-radius-mini)' }} style={{ borderRadius: 'var(--border-radius-mini)' }}
onKeyDown={handleKeyDown}
onChange={handleEdit} onChange={handleEdit}
onPaste={handleOnPaste} onPaste={handleOnPaste}
/> />
+34 -27
View File
@@ -1,7 +1,7 @@
import useUserSettings from '@/hooks/use-user-settings'; import useUserSettings from '@/hooks/use-user-settings';
import { MoonOutlined, SunOutlined } from '@ant-design/icons'; import { MoonOutlined, SunOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Switch } from 'antd'; import { Select } from 'antd';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -14,9 +14,11 @@ const Wrapper = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
max-width: 220px; max-width: 300px;
} }
.theme-label { .theme-label {
display: flex;
align-items: center;
font-size: 16px; font-size: 16px;
font-weight: var(--font-weight-500); font-weight: var(--font-weight-500);
} }
@@ -25,46 +27,51 @@ const Wrapper = styled.div`
} }
`; `;
const ThemeOptions = [
{
key: 'realDark',
label: 'common.appearance.dark',
icon: <MoonOutlined />
},
{
key: 'light',
label: 'common.appearance.light',
icon: <SunOutlined />
}
];
const Appearance: React.FC = () => { const Appearance: React.FC = () => {
const { setTheme, userSettings } = useUserSettings(); const { setTheme, userSettings } = useUserSettings();
console.log('userSettings', userSettings);
const intl = useIntl(); const intl = useIntl();
const handleOnChange = (checked: boolean) => { const ThemeOptions = [
if (checked) { {
setTheme('realDark'); value: 'light',
} else { label: intl.formatMessage({ id: 'common.appearance.light' }),
setTheme('light'); icon: <SunOutlined />
},
{
value: 'realDark',
label: intl.formatMessage({ id: 'common.appearance.dark' }),
icon: <MoonOutlined />
},
{
value: 'auto',
label: intl.formatMessage({ id: 'common.appearance.system' }),
icon: <SunOutlined />
} }
];
const handleOnChange = (value: 'light' | 'realDark' | 'auto') => {
setTheme(value);
}; };
const isDarkMode = useMemo(() => { const theme = useMemo(() => {
const darkMode = userSettings?.theme === 'realDark'; return userSettings?.theme || 'auto';
return darkMode;
}, [userSettings?.theme]); }, [userSettings?.theme]);
return ( return (
<Wrapper> <Wrapper>
<div className="theme"> <div className="theme">
<span className="theme-label"> <span className="theme-label">
{intl.formatMessage({ id: 'common.appearance.darkmode' })} <span>{intl.formatMessage({ id: 'common.appearance.theme' })}</span>
</span> </span>
<Switch checked={isDarkMode} onChange={handleOnChange} /> <Select
</div> value={userSettings.mode}
<div className="tips"> options={ThemeOptions}
{intl.formatMessage({ id: 'common.appearance.tips' })} onChange={handleOnChange}
style={{ width: 200 }}
></Select>
</div> </div>
</Wrapper> </Wrapper>
); );