diff --git a/web/index.html b/web/index.html index db97eb0..43d8c04 100644 --- a/web/index.html +++ b/web/index.html @@ -1,13 +1,20 @@ - +
+{JSON.stringify(selectedLog.details, null, 2)})} @@ -152,4 +152,4 @@ const LogsPage = memo(function LogsPage() { ); }); -export default LogsPage; \ No newline at end of file +export default LogsPage; diff --git a/web/src/pages/SetupPage.tsx b/web/src/pages/SetupPage.tsx index a477260..f3ce15f 100644 --- a/web/src/pages/SetupPage.tsx +++ b/web/src/pages/SetupPage.tsx @@ -190,7 +190,7 @@ const SetupPage = () => { height: '100vh', alignItems: 'center', justifyContent: 'center', - background: 'linear-gradient(to right, #f0f2f5, #d7d7d7)' + background: 'linear-gradient(to right, var(--ant-color-bg-layout, #f0f2f5), var(--ant-color-fill-secondary, #d7d7d7))' }}> @@ -235,4 +235,4 @@ const SetupPage = () => { ); }; -export default SetupPage; \ No newline at end of file +export default SetupPage; diff --git a/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx b/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx index 2cd465d..4f087ce 100644 --- a/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx +++ b/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx @@ -1,9 +1,11 @@ -import { Form, Input, Button, message, Tabs, Space, Card, Select, Modal } from 'antd'; +import { Form, Input, Button, message, Tabs, Space, Card, Select, Modal, Radio, InputNumber } from 'antd'; import { useEffect, useState } from 'react'; import PageCard from '../../components/PageCard'; import { getAllConfig, setConfig } from '../../api/config'; import { vectorDBApi } from '../../api/vectorDB'; -import { AppstoreOutlined, RobotOutlined, DatabaseOutlined } from '@ant-design/icons'; +import { AppstoreOutlined, RobotOutlined, DatabaseOutlined, SkinOutlined } from '@ant-design/icons'; +import { useTheme } from '../../contexts/ThemeContext'; +import '../../styles/settings-tabs.css'; const APP_CONFIG_KEYS: {key: string, label: string, default?: string}[] = [ { key: 'APP_NAME', label: '应用名称' }, @@ -26,10 +28,20 @@ const EMBED_CONFIG_KEYS = [ const ALL_AI_KEYS = [...VISION_CONFIG_KEYS, ...EMBED_CONFIG_KEYS]; +// Theme related config keys +const THEME_KEYS = { + MODE: 'THEME_MODE', + PRIMARY: 'THEME_PRIMARY_COLOR', + RADIUS: 'THEME_BORDER_RADIUS', + TOKENS: 'THEME_CUSTOM_TOKENS', + CSS: 'THEME_CUSTOM_CSS', +}; + export default function SystemSettingsPage() { const [loading, setLoading] = useState(false); const [config, setConfigState] = useState| null>(null); - const [activeTab, setActiveTab] = useState('app'); + const [activeTab, setActiveTab] = useState('appearance'); + const { refreshTheme, previewTheme } = useTheme(); useEffect(() => { getAllConfig().then((data) => setConfigState(data as Record )); @@ -43,12 +55,23 @@ export default function SystemSettingsPage() { } message.success('保存成功'); setConfigState({ ...config, ...values }); + // trigger theme refresh if related keys changed + if (Object.keys(values).some(k => Object.values(THEME_KEYS).includes(k))) { + await refreshTheme(); + } } catch (e: any) { message.error(e.message || '保存失败'); } setLoading(false); }; + // 离开“外观设置”时,恢复后端持久化配置(取消未保存的预览) + useEffect(() => { + if (activeTab !== 'appearance') { + refreshTheme(); + } + }, [activeTab]); + if (!config) { return ; } @@ -59,11 +82,92 @@ export default function SystemSettingsPage() { > 加载中...+ + 外观设置 + + ), + children: ( + + + + + ) + }, { key: 'app', label: ( diff --git a/web/src/router/LayoutShell.tsx b/web/src/router/LayoutShell.tsx index 01aabf7..92634c5 100644 --- a/web/src/router/LayoutShell.tsx +++ b/web/src/router/LayoutShell.tsx @@ -18,17 +18,17 @@ const LayoutShell = memo(function LayoutShell() { const navigate = useNavigate(); const [collapsed, setCollapsed] = useState(false); return ( -+ setCollapsed(c => !c)} activeKey={navKey} onChange={(key) => navigate(`/${key}`)} /> - + setCollapsed(c => !c)} /> - - ++ {navKey === 'adapters' && } {navKey === 'files' && } diff --git a/web/src/styles/settings-tabs.css b/web/src/styles/settings-tabs.css new file mode 100644 index 0000000..76d1398 --- /dev/null +++ b/web/src/styles/settings-tabs.css @@ -0,0 +1,40 @@ +.fx-settings-tabs .ant-tabs-nav-list { + padding: 8px 4px; +} + +.fx-settings-tabs .ant-tabs-tab { + margin: 4px 0 !important; + border-radius: 8px; + padding: 6px 10px !important; +} + +.fx-settings-tabs .ant-tabs-tab .ant-tabs-tab-btn { + color: var(--ant-color-text-secondary) !important; +} + +.fx-settings-tabs .ant-tabs-tab:hover { + background: var(--ant-color-fill-tertiary) !important; +} + +/* 选中态:按主题细分 */ +html[data-theme='dark'] .fx-settings-tabs .ant-tabs-tab-active { + background: var(--ant-color-primary-bg) !important; +} +html[data-theme='dark'] .fx-settings-tabs .ant-tabs-tab-active .ant-tabs-tab-btn, +html[data-theme='dark'] .fx-settings-tabs .ant-tabs-tab-active .ant-tabs-tab-btn .anticon { + color: var(--ant-color-text) !important; + font-weight: 600; +} + +html[data-theme='light'] .fx-settings-tabs .ant-tabs-tab-active { + background: var(--ant-color-primary) !important; +} +html[data-theme='light'] .fx-settings-tabs .ant-tabs-tab-active .ant-tabs-tab-btn, +html[data-theme='light'] .fx-settings-tabs .ant-tabs-tab-active .ant-tabs-tab-btn .anticon { + color: var(--ant-color-text-light-solid) !important; + font-weight: 600; +} + +.fx-settings-tabs .ant-tabs-ink-bar { + background: var(--ant-color-primary) !important; +} diff --git a/web/src/styles/sider-menu.css b/web/src/styles/sider-menu.css index 495624a..a34def9 100644 --- a/web/src/styles/sider-menu.css +++ b/web/src/styles/sider-menu.css @@ -5,13 +5,24 @@ margin-block: 2px; } -.foxel-sider-menu .ant-menu-item-selected { - font-weight: 600; +.foxel-sider-menu .ant-menu-item-selected { font-weight: 600; } + +/* 亮色主题:选中项使用主色,文字用浅色文本 */ +html[data-theme='light'] .foxel-sider-menu .ant-menu-item-selected { + background: var(--ant-color-primary) !important; + color: var(--ant-color-text-light-solid) !important; +} +html[data-theme='light'] .foxel-sider-menu .ant-menu-item-selected .ant-menu-item-icon { + color: var(--ant-color-text-light-solid) !important; } -.foxel-sider-menu .ant-menu-item-selected, -.foxel-sider-menu .ant-menu-item-selected .ant-menu-item-icon { - color: #fff !important; +/* 暗色主题:选中项使用主色背景(浅),文字使用常规文本色以保持对比度 */ +html[data-theme='dark'] .foxel-sider-menu .ant-menu-item-selected { + background: var(--ant-color-primary-bg) !important; + color: var(--ant-color-text) !important; +} +html[data-theme='dark'] .foxel-sider-menu .ant-menu-item-selected .ant-menu-item-icon { + color: var(--ant-color-text) !important; } .foxel-sider-menu .ant-menu-item-selected::after { @@ -21,3 +32,8 @@ .foxel-sider-menu .ant-menu-item .ant-menu-item-icon { transition: color .18s; } + +/* 悬停(未选中)背景 */ +.foxel-sider-menu .ant-menu-item:not(.ant-menu-item-selected):hover { + background: var(--ant-color-fill-tertiary) !important; +}