File size: 4,299 Bytes
a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | import { Link, useLocation } from 'react-router-dom';
import { Package, ChevronRight, Puzzle, Wrench, Server, Settings } from 'lucide-react';
import { usePlugins } from '@hooks/usePlugins';
import { cn } from '@lib/utils';
/**
* 平台导航项配置
* 一等入口:插件、Tool、MCP、System
*/
const platformNavItems = [
{
path: '/plugins',
label: '插件中心',
icon: Puzzle,
matchPattern: /^\/plugins/,
},
{
path: '/tools',
label: 'Tool 目录',
icon: Wrench,
matchPattern: /^\/tools/,
},
{
path: '/mcp',
label: 'MCP 服务',
icon: Server,
matchPattern: /^\/mcp/,
},
{
path: '/system',
label: '系统状态',
icon: Settings,
matchPattern: /^\/system/,
},
];
/**
* 控制台侧边栏
* 分为平台导航和插件运行区
*/
export function Sidebar() {
const location = useLocation();
const { data: plugins, isLoading } = usePlugins();
// 只显示 enabled 状态的插件
const enabledPlugins = plugins?.filter((p) => p.status === 'enabled') || [];
return (
<aside className="w-56 h-full border-r border-console-border bg-console-surface-raised overflow-y-auto">
<nav className="p-3" aria-label="控制台导航">
{/* 平台导航区 */}
<div className="mb-4">
<h2 className="px-3 mb-2 text-xs font-medium text-console-text-secondary uppercase tracking-wider">
平台
</h2>
<ul className="space-y-0.5" role="list">
{platformNavItems.map((item) => {
const isActive = item.matchPattern.test(location.pathname);
return (
<li key={item.path}>
<Link
to={item.path}
className={cn(
'flex items-center gap-2.5 px-3 py-1.5 rounded-console-sm text-sm transition-colors',
isActive
? 'bg-console-status-info-bg text-console-status-info font-medium'
: 'text-console-text-primary hover:bg-console-surface-base'
)}
aria-current={isActive ? 'page' : undefined}
>
<item.icon className="h-4 w-4 shrink-0" aria-hidden="true" />
<span className="truncate">{item.label}</span>
</Link>
</li>
);
})}
</ul>
</div>
{/* 插件运行区 */}
<div>
<h2 className="px-3 mb-2 text-xs font-medium text-console-text-secondary uppercase tracking-wider">
插件运行
</h2>
{isLoading ? (
<div className="px-3 py-2 text-xs text-console-text-muted">
加载中...
</div>
) : enabledPlugins.length === 0 ? (
<div className="px-3 py-2 text-xs text-console-text-muted">
暂无启用插件
</div>
) : (
<ul className="space-y-0.5" role="list">
{enabledPlugins.map((plugin) => {
const isActive = location.pathname === `/tool/${plugin.metadata.name}`;
return (
<li key={plugin.metadata.name}>
<Link
to={`/tool/${plugin.metadata.name}`}
className={cn(
'flex items-center gap-2.5 px-3 py-1.5 rounded-console-sm text-sm transition-colors',
isActive
? 'bg-console-status-info-bg text-console-status-info font-medium'
: 'text-console-text-primary hover:bg-console-surface-base'
)}
aria-current={isActive ? 'page' : undefined}
>
<Package className="h-4 w-4 shrink-0" aria-hidden="true" />
<span className="flex-1 truncate">
{plugin.metadata.name}
</span>
<ChevronRight className="h-3.5 w-3.5 text-console-text-muted shrink-0" aria-hidden="true" />
</Link>
</li>
);
})}
</ul>
)}
</div>
</nav>
</aside>
);
}
|