Feat: Render operator menu by category. #3221 (#5302)

### What problem does this PR solve?
Feat: Render operator menu by category. #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2025-02-24 16:51:44 +08:00 committed by GitHub
parent f9f75aa119
commit ca865df87f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 3088 additions and 64 deletions

View File

@ -1,3 +1,4 @@
import { Card, CardContent } from '@/components/ui/card';
import {
Collapsible,
CollapsibleContent,
@ -11,81 +12,97 @@ import {
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from '@/components/ui/sidebar';
import { ChevronDown } from 'lucide-react';
import { useMemo } from 'react';
import {
Calendar,
ChevronDown,
Home,
Inbox,
Search,
Settings,
} from 'lucide-react';
AgentOperatorList,
Operator,
componentMenuList,
operatorMap,
} from './constant';
import OperatorIcon from './operator-icon';
// Menu items.
const items = [
{
title: 'Home',
url: '#',
icon: Home,
},
{
title: 'Inbox',
url: '#',
icon: Inbox,
},
{
title: 'Calendar',
url: '#',
icon: Calendar,
},
{
title: 'Search',
url: '#',
icon: Search,
},
{
title: 'Settings',
url: '#',
icon: Settings,
},
];
function SideDown() {
return (
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
);
}
type OperatorItem = {
name: Operator;
};
function OperatorCard({ name }: OperatorItem) {
return (
<Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard">
<CardContent className="p-2 flex items-center gap-2">
<OperatorIcon
name={name}
color={operatorMap[name].color}
></OperatorIcon>
{name}
</CardContent>
</Card>
);
}
type OperatorCollapsibleProps = { operatorList: OperatorItem[]; title: string };
function OperatorCollapsible({
operatorList,
title,
}: OperatorCollapsibleProps) {
return (
<Collapsible defaultOpen className="group/collapsible">
<SidebarGroup>
<SidebarGroupLabel asChild className="mb-1">
<CollapsibleTrigger>
<span className="font-bold text-base">{title}</span>
<SideDown />
</CollapsibleTrigger>
</SidebarGroupLabel>
<CollapsibleContent className="px-2">
<SidebarGroupContent>
<SidebarMenu className="gap-2">
{operatorList.map((item) => (
<OperatorCard key={item.name} name={item.name}></OperatorCard>
))}
</SidebarMenu>
</SidebarGroupContent>
</CollapsibleContent>
</SidebarGroup>
</Collapsible>
);
}
export function AgentSidebar() {
const agentOperatorList = useMemo(() => {
return componentMenuList.filter((x) =>
AgentOperatorList.some((y) => y === x.name),
);
}, []);
const thirdOperatorList = useMemo(() => {
return componentMenuList.filter(
(x) => !AgentOperatorList.some((y) => y === x.name),
);
}, []);
return (
<Sidebar variant={'floating'} className="top-16">
<SidebarHeader>
<p className="font-bold text-2xl">All nodes</p>
</SidebarHeader>
<SidebarContent>
<Collapsible defaultOpen className="group/collapsible">
<SidebarGroup>
<SidebarGroupLabel asChild>
<CollapsibleTrigger>
Help
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
</CollapsibleTrigger>
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</CollapsibleContent>
</SidebarGroup>
</Collapsible>
<SidebarGroup>yyy</SidebarGroup>
<OperatorCollapsible
title="Agent operator"
operatorList={agentOperatorList}
></OperatorCollapsible>
<OperatorCollapsible
title="Third-party tools"
operatorList={thirdOperatorList}
></OperatorCollapsible>
</SidebarContent>
</Sidebar>
);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
import React from 'react';
import { Operator, operatorIconMap } from '../constant';
interface IProps {
name: Operator;
fontSize?: number;
width?: number;
color?: string;
}
const OperatorIcon = ({ name, fontSize, width, color }: IProps) => {
const Icon = operatorIconMap[name] || React.Fragment;
return (
<Icon
className={'text-2xl max-h-6 max-w-6 text-[rgb(59, 118, 244)]'}
style={{ fontSize, color }}
width={width}
></Icon>
);
};
export default OperatorIcon;