aura-web/src/components/Sidebar.tsx

163 lines
4.7 KiB
TypeScript

import { NavLink, useNavigate } from 'react-router-dom';
import { Dropdown, Avatar, Tooltip } from 'antd';
import {
SearchOutlined,
MessageOutlined,
RobotOutlined,
CompassOutlined,
BookOutlined,
ApartmentOutlined,
BarChartOutlined,
TeamOutlined,
SunOutlined,
MoonOutlined,
LogoutOutlined
} from '@ant-design/icons';
import { useAuth } from '../store/auth';
import { useTheme } from '../main';
import kaiwuIcon from '../assets/brand/kaiwu-icon-gradient-transparent.png';
interface Props {
onOpenPalette?: () => void;
onNavigate?: () => void;
}
const NAV_GROUPS: Array<{
label: string;
items: Array<{ to: string; icon: React.ReactNode; label: string; end?: boolean }>;
}> = [
{
label: '工作台',
items: [
{ to: '/chat', icon: <MessageOutlined />, label: '聊天' },
{ to: '/agents', icon: <RobotOutlined />, label: '我的智能体' },
{ to: '/marketplace', icon: <CompassOutlined />, label: '智能体广场' }
]
},
{
label: '资源',
items: [
{ to: '/prompts', icon: <BookOutlined />, label: 'Prompt 模板库' },
{ to: '/workflows', icon: <ApartmentOutlined />, label: '工作流' }
]
},
{
label: '管理',
items: [
{ to: '/stats', icon: <BarChartOutlined />, label: '调用统计' },
{ to: '/teams', icon: <TeamOutlined />, label: '团队' }
]
},
{
label: '商城',
items: [
{ to: '/points-mall', icon: <CompassOutlined />, label: 'Token商城' }
]
}
];
export default function Sidebar({ onOpenPalette, onNavigate }: Props) {
const { user, logout } = useAuth();
const navigate = useNavigate();
const { mode, toggle } = useTheme();
const isMac =
typeof navigator !== 'undefined' && /mac|iphone|ipad|ipod/i.test(navigator.platform || '');
const cmdKey = isMac ? '⌘' : 'Ctrl';
return (
<aside className="sidebar">
<div className="brand">
<img src={kaiwuIcon} alt="鲸域AI" className="brand-logo" />
<span>AI</span>
<div className="sidebar-brand-spacer" />
<Tooltip title={mode === 'dark' ? '切换到明亮模式' : '切换到深色模式'}>
<button className="theme-toggle" onClick={toggle} aria-label="切换主题">
{mode === 'dark' ? <SunOutlined /> : <MoonOutlined />}
</button>
</Tooltip>
</div>
<div
onClick={() => {
onOpenPalette?.();
onNavigate?.();
}}
className="nav-item sidebar-search-action"
>
<span className="sidebar-search-label">
<SearchOutlined className="nav-icon" />
<span></span>
</span>
<span className="kbd">{cmdKey} K</span>
</div>
<div className="sidebar-scroll">
{NAV_GROUPS.map((group) => (
<div key={group.label}>
<div className="nav-section-label">{group.label}</div>
{group.items.map((it) => (
<NavLink
key={it.to}
to={it.to}
end={it.end}
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
onClick={onNavigate}
>
<span className="nav-icon">{it.icon}</span>
<span>{it.label}</span>
</NavLink>
))}
</div>
))}
</div>
{user && (
<Dropdown
menu={{
items: [
{
key: 'name',
label: <span className="sidebar-user-role">{user.phone}</span>,
disabled: true
},
{ type: 'divider' },
{
key: 'role',
label: `身份:${user.role === 'admin' ? '管理员' : '普通用户'}`,
disabled: true
},
{ type: 'divider' },
{
key: 'logout',
icon: <LogoutOutlined />,
label: '退出登录',
onClick: async () => {
await logout();
navigate('/login');
onNavigate?.();
}
}
]
}}
placement="topLeft"
>
<div className="sidebar-user">
<Avatar size={32} className="sidebar-user-avatar">
{(user.name?.charAt(0) || '?').toUpperCase()}
</Avatar>
<div className="sidebar-user-main">
<div className="sidebar-user-name">
{user.name}
</div>
<div className="sidebar-user-role">
{user.role === 'admin' ? '管理员' : '成员'}
</div>
</div>
</div>
</Dropdown>
)}
</aside>
);
}