import { ChatContext } from '@/app/chat-context'; import { apiInterceptors, getFlowNodes } from '@/client/api'; import { IFlowNode } from '@/types/flow'; import { FLOW_NODES_KEY } from '@/utils'; import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons'; import type { CollapseProps } from 'antd'; import { Badge, Collapse, Input, Layout, Space } from 'antd'; import React, { useContext, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import StaticNodes from './static-nodes'; const { Search } = Input; const { Sider } = Layout; type GroupType = { category: string; categoryLabel: string; nodes: IFlowNode[]; }; const zeroWidthTriggerDefaultStyle: React.CSSProperties = { display: 'flex', alignItems: 'center', justifyContent: 'center', width: 16, height: 48, position: 'absolute', top: '50%', transform: 'translateY(-50%)', border: '1px solid #d6d8da', borderRadius: 8, right: -8, }; const AddNodesSider: React.FC = () => { const { t } = useTranslation(); const { mode } = useContext(ChatContext); const [collapsed, setCollapsed] = useState(false); const [searchValue, setSearchValue] = useState(''); const [operators, setOperators] = useState>([]); const [resources, setResources] = useState>([]); const [operatorsGroup, setOperatorsGroup] = useState([]); const [resourcesGroup, setResourcesGroup] = useState([]); useEffect(() => { getNodes(); }, []); async function getNodes() { const [_, data] = await apiInterceptors(getFlowNodes()); if (data && data.length > 0) { localStorage.setItem(FLOW_NODES_KEY, JSON.stringify(data)); const operatorNodes = data.filter(node => node.flow_type === 'operator'); const resourceNodes = data.filter(node => node.flow_type === 'resource'); setOperators(operatorNodes); setResources(resourceNodes); setOperatorsGroup(groupNodes(operatorNodes)); setResourcesGroup(groupNodes(resourceNodes)); } } const triggerStyle: React.CSSProperties = useMemo(() => { if (collapsed) { return { ...zeroWidthTriggerDefaultStyle, right: -16, borderRadius: '0px 8px 8px 0', borderLeft: '1px solid #d5e5f6', }; } return { ...zeroWidthTriggerDefaultStyle, borderLeft: '1px solid #d6d8da', }; }, [collapsed]); function groupNodes(data: IFlowNode[]) { const groups: GroupType[] = []; const categoryMap: Record = {}; data.forEach(item => { const { category, category_label } = item; if (!categoryMap[category]) { categoryMap[category] = { category, categoryLabel: category_label, nodes: [], }; groups.push(categoryMap[category]); } categoryMap[category].nodes.push(item); }); return groups; } const operatorItems: CollapseProps['items'] = useMemo(() => { if (!searchValue) { return operatorsGroup.map(({ category, categoryLabel, nodes }) => ({ key: category, label: categoryLabel, children: , extra: ( 0 ? '#52c41a' : '#7f9474', }} /> ), })); } else { const searchedNodes = operators.filter(node => node.label.toLowerCase().includes(searchValue.toLowerCase())); return groupNodes(searchedNodes).map(({ category, categoryLabel, nodes }) => ({ key: category, label: categoryLabel, children: , extra: ( 0 ? '#52c41a' : '#7f9474', }} /> ), })); } }, [operatorsGroup, searchValue]); const resourceItems: CollapseProps['items'] = useMemo(() => { if (!searchValue) { return resourcesGroup.map(({ category, categoryLabel, nodes }) => ({ key: category, label: categoryLabel, children: , extra: ( 0 ? '#52c41a' : '#7f9474', }} /> ), })); } else { const searchedNodes = resources.filter(node => node.label.toLowerCase().includes(searchValue.toLowerCase())); return groupNodes(searchedNodes).map(({ category, categoryLabel, nodes }) => ({ key: category, label: categoryLabel, children: , extra: ( 0 ? '#52c41a' : '#7f9474', }} /> ), })); } }, [resourcesGroup, searchValue]); function searchNode(val: string) { setSearchValue(val); } return ( : } zeroWidthTriggerStyle={triggerStyle} onCollapse={collapsed => setCollapsed(collapsed)} >

{t('add_node')}

{t('operators')}

{t('resource')}

); }; export default AddNodesSider;