feat: refactor sidebar and settings to include agents and models; update routing and i18n messages

This commit is contained in:
duanshuwen
2026-04-20 22:56:33 +08:00
parent 4f41eb380e
commit 35319e6a1d
6 changed files with 126 additions and 33 deletions

View File

@@ -1,5 +1,5 @@
import { useLocation, useNavigate } from 'react-router-dom';
import { Book, Bot, Clock, Code, Cpu, House, Network, Puzzle, Settings } from 'lucide-react';
import { Book, Clock, Code, House, Network, Puzzle, Settings } from 'lucide-react';
import { useI18n } from '../../i18n';
import { NAV_ITEMS, normalizeWorkspacePath } from '../../router/routes';
@@ -9,8 +9,6 @@ const MENU_MARKS: Record<string, typeof House> = {
'/home': House,
'/knowledge': Book,
'/channels': Network,
'/agents': Bot,
'/models': Cpu,
'/skills': Puzzle,
'/cron': Clock,
// '/scripts': Code,

View File

@@ -416,6 +416,8 @@ export const messages: I18nMessages = {
systemSettings: 'System Settings',
account: 'Account',
general: 'General',
agents: 'Agents',
models: 'Models',
},
account: {
title: 'Account Settings',
@@ -892,6 +894,8 @@ export const messages: I18nMessages = {
systemSettings: '系统设置',
account: '账号',
general: '通用',
agents: 'Agents',
models: 'Models',
},
account: {
title: '账号设置',
@@ -1368,6 +1372,8 @@ export const messages: I18nMessages = {
systemSettings: 'システム設定',
account: 'アカウント',
general: '一般',
agents: 'Agents',
models: 'Models',
},
account: {
title: 'アカウント設定',

View File

@@ -1,7 +1,13 @@
import { useI18n } from '../../../i18n';
import { Settings, User } from 'lucide-react';
import { Bot, Cpu, Settings, User } from 'lucide-react';
export type SettingView = 'account' | 'general';
const SETTING_VIEWS = ['general', 'account', 'models', 'agents'] as const;
export type SettingView = (typeof SETTING_VIEWS)[number];
export function isSettingView(value: string | null): value is SettingView {
return value !== null && SETTING_VIEWS.includes(value as SettingView);
}
type SettingMenuProps = {
currentView: SettingView;
@@ -10,8 +16,12 @@ type SettingMenuProps = {
const MENU_ITEMS: Array<{
id: SettingView;
labelKey: 'settings.menu.account' | 'settings.menu.general';
Icon: typeof User | typeof Settings;
labelKey:
| 'settings.menu.account'
| 'settings.menu.general'
| 'settings.menu.models'
| 'settings.menu.agents';
Icon: typeof User | typeof Settings | typeof Cpu | typeof Bot;
}> = [
{
id: 'general',
@@ -23,6 +33,16 @@ const MENU_ITEMS: Array<{
labelKey: 'settings.menu.account',
Icon: User,
},
{
id: 'models',
labelKey: 'settings.menu.models',
Icon: Cpu,
},
{
id: 'agents',
labelKey: 'settings.menu.agents',
Icon: Bot,
},
];
export default function SettingMenu({ currentView, onChange }: SettingMenuProps) {

View File

@@ -1,18 +1,41 @@
import { useState } from 'react';
import { Suspense, lazy, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useI18n } from '../../i18n';
import { updateLanguage, updateThemeMode, useSettingsStore } from '../../stores';
import type { LanguageCode, ThemeMode } from '../../types/runtime';
import AccountSettingsPanel from './components/AccountSettingsPanel';
import GeneralSettingsPanel from './components/GeneralSettingsPanel';
import SettingMenu, { type SettingView } from './components/SettingMenu';
import SettingMenu, { isSettingView, type SettingView } from './components/SettingMenu';
import { useGatewaySettingState } from './useGatewaySettingState';
import { useSettingUpdateState } from './useSettingUpdateState';
const AgentsPage = lazy(() => import('../Agents'));
const ModelsPage = lazy(() => import('../Models'));
export default function SettingPage() {
const [currentView, setCurrentView] = useState<SettingView>('general');
const location = useLocation();
const navigate = useNavigate();
const { t } = useI18n();
const themeMode = useSettingsStore((state) => state.themeMode);
const language = useSettingsStore((state) => state.language);
const updateState = useSettingUpdateState();
const gatewayState = useGatewaySettingState();
const rawView = new URLSearchParams(location.search).get('view');
const currentView: SettingView = isSettingView(rawView) ? rawView : 'general';
useEffect(() => {
if (isSettingView(rawView)) return;
const searchParams = new URLSearchParams(location.search);
searchParams.set('view', 'general');
navigate(
{
pathname: location.pathname,
search: `?${searchParams.toString()}`,
},
{ replace: true },
);
}, [location.pathname, location.search, navigate, rawView]);
const handleThemeChange = async (nextTheme: ThemeMode) => {
await updateThemeMode(nextTheme);
@@ -22,14 +45,51 @@ export default function SettingPage() {
await updateLanguage(nextLanguage);
};
return (
<div className="bg-white dark:bg-[#1b1b1d] box-border w-full h-full min-w-0 min-h-0 flex rounded-2xl overflow-hidden">
<SettingMenu currentView={currentView} onChange={setCurrentView} />
const handleViewChange = (nextView: SettingView) => {
if (nextView === currentView) return;
const searchParams = new URLSearchParams(location.search);
searchParams.set('view', nextView);
navigate({
pathname: location.pathname,
search: `?${searchParams.toString()}`,
});
};
const loadingFallback = (
<div className="flex h-full min-h-0 w-full items-center justify-center bg-white text-sm text-[#99A0AE] dark:bg-[#1b1b1d] dark:text-gray-500">
{t('common.loading')}
</div>
);
const renderContent = () => {
switch (currentView) {
case 'account':
return (
<div className="flex-1 min-w-0 min-h-0 overflow-y-auto">
{currentView === 'account' ? (
<AccountSettingsPanel />
) : (
</div>
);
case 'models':
return (
<div className="flex-1 min-w-0 min-h-0 overflow-hidden">
<Suspense fallback={loadingFallback}>
<ModelsPage />
</Suspense>
</div>
);
case 'agents':
return (
<div className="flex-1 min-w-0 min-h-0 overflow-hidden">
<Suspense fallback={loadingFallback}>
<AgentsPage />
</Suspense>
</div>
);
case 'general':
default:
return (
<div className="flex-1 min-w-0 min-h-0 overflow-y-auto">
<GeneralSettingsPanel
themeMode={themeMode}
language={language}
@@ -38,8 +98,16 @@ export default function SettingPage() {
gatewayState={gatewayState}
updateState={updateState}
/>
)}
</div>
</div>
);
}
};
return (
<div className="bg-white dark:bg-[#1b1b1d] box-border w-full h-full min-w-0 min-h-0 flex rounded-2xl overflow-hidden">
<SettingMenu currentView={currentView} onChange={handleViewChange} />
{renderContent()}
</div>
);
}

View File

@@ -8,8 +8,6 @@ import { onAuthLogout } from './auth-session';
const HomePage = lazy(() => import('../pages/Home'));
const ChannelsPage = lazy(() => import('../pages/Channels'));
const AgentsPage = lazy(() => import('../pages/Agents'));
const ModelsPage = lazy(() => import('../pages/Models'));
const SkillsPage = lazy(() => import('../pages/Skills'));
const CronPage = lazy(() => import('../pages/Cron'));
const ScriptsPage = lazy(() => import('../pages/Scripts'));
@@ -32,6 +30,10 @@ function renderLazyPage(element: ReactNode) {
);
}
function renderSettingViewRedirect(view: 'agents' | 'models') {
return <Navigate to={`/setting?view=${view}`} replace />;
}
function AuthLogoutRedirector() {
const location = useLocation();
const navigate = useNavigate();
@@ -65,8 +67,8 @@ export function AppRouter() {
<Route element={<MainLayout />}>
<Route path="/home" element={renderLazyPage(<HomePage />)} />
<Route path="/channels" element={renderLazyPage(<ChannelsPage />)} />
<Route path="/agents" element={renderLazyPage(<AgentsPage />)} />
<Route path="/models" element={renderLazyPage(<ModelsPage />)} />
<Route path="/agents" element={renderSettingViewRedirect('agents')} />
<Route path="/models" element={renderSettingViewRedirect('models')} />
<Route path="/skills" element={renderLazyPage(<SkillsPage />)} />
<Route path="/cron" element={renderLazyPage(<CronPage />)} />
<Route path="/scripts" element={renderLazyPage(<ScriptsPage />)} />

View File

@@ -32,8 +32,6 @@ export const NAV_ITEMS: NavItem[] = [
{ path: '/home', labelKey: 'sidebar.home' },
{ path: '/knowledge', labelKey: 'sidebar.knowledge' },
{ path: '/channels', labelKey: 'sidebar.channels' },
{ path: '/agents', labelKey: 'sidebar.agents' },
{ path: '/models', labelKey: 'sidebar.models' },
{ path: '/skills', labelKey: 'sidebar.skills' },
{ path: '/cron', labelKey: 'sidebar.cron' },
// { path: '/scripts', labelKey: 'sidebar.scripts' },
@@ -44,13 +42,14 @@ export function normalizeWorkspacePath(pathname: string): WorkspacePath {
switch (pathname) {
case '/knowledge':
case '/channels':
case '/agents':
case '/models':
case '/skills':
case '/cron':
case '/scripts':
case '/setting':
return pathname;
case '/agents':
case '/models':
return '/setting';
case '/home':
default:
return DEFAULT_PATH;