mirror of
				https://github.com/csunny/DB-GPT.git
				synced 2025-10-31 06:39:43 +00:00 
			
		
		
		
	Co-authored-by: Fangyin Cheng <staneyffer@gmail.com> Co-authored-by: 谨欣 <echo.cmy@antgroup.com> Co-authored-by: 严志勇 <yanzhiyong@tiansuixiansheng.com> Co-authored-by: yanzhiyong <932374019@qq.com>
		
			
				
	
	
		
			157 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			157 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { apiInterceptors, getFlowNodes } from '@/client/api';
 | |
| import { IFlowNode } from '@/types/flow';
 | |
| import { FLOW_NODES_KEY } from '@/utils';
 | |
| import { PlusOutlined } from '@ant-design/icons';
 | |
| import { Badge, Button, Collapse, CollapseProps, Input, Popover } from 'antd';
 | |
| import React, { useEffect, useMemo, useState } from 'react';
 | |
| import { useTranslation } from 'react-i18next';
 | |
| import StaticNodes from './static-nodes';
 | |
| 
 | |
| const { Search } = Input;
 | |
| 
 | |
| type GroupType = { category: string; categoryLabel: string; nodes: IFlowNode[] };
 | |
| 
 | |
| const AddNodes: React.FC = () => {
 | |
|   const { t } = useTranslation();
 | |
|   const [operators, setOperators] = useState<Array<IFlowNode>>([]);
 | |
|   const [resources, setResources] = useState<Array<IFlowNode>>([]);
 | |
|   const [operatorsGroup, setOperatorsGroup] = useState<GroupType[]>([]);
 | |
|   const [resourcesGroup, setResourcesGroup] = useState<GroupType[]>([]);
 | |
|   const [searchValue, setSearchValue] = useState<string>('');
 | |
| 
 | |
|   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));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function groupNodes(data: IFlowNode[]) {
 | |
|     const groups: GroupType[] = [];
 | |
|     const categoryMap: Record<string, { category: string; categoryLabel: string; nodes: IFlowNode[] }> = {};
 | |
|     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: <StaticNodes nodes={nodes} />,
 | |
|         extra: (
 | |
|           <Badge
 | |
|             showZero
 | |
|             count={nodes.length || 0}
 | |
|             style={{ backgroundColor: nodes.length > 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: <StaticNodes nodes={nodes} />,
 | |
|         extra: (
 | |
|           <Badge
 | |
|             showZero
 | |
|             count={nodes.length || 0}
 | |
|             style={{ backgroundColor: nodes.length > 0 ? '#52c41a' : '#7f9474' }}
 | |
|           />
 | |
|         ),
 | |
|       }));
 | |
|     }
 | |
|   }, [operatorsGroup, searchValue]);
 | |
| 
 | |
|   const resourceItems: CollapseProps['items'] = useMemo(() => {
 | |
|     if (!searchValue) {
 | |
|       return resourcesGroup.map(({ category, categoryLabel, nodes }) => ({
 | |
|         key: category,
 | |
|         label: categoryLabel,
 | |
|         children: <StaticNodes nodes={nodes} />,
 | |
|         extra: (
 | |
|           <Badge
 | |
|             showZero
 | |
|             count={nodes.length || 0}
 | |
|             style={{ backgroundColor: nodes.length > 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: <StaticNodes nodes={nodes} />,
 | |
|         extra: (
 | |
|           <Badge
 | |
|             showZero
 | |
|             count={nodes.length || 0}
 | |
|             style={{ backgroundColor: nodes.length > 0 ? '#52c41a' : '#7f9474' }}
 | |
|           />
 | |
|         ),
 | |
|       }));
 | |
|     }
 | |
|   }, [resourcesGroup, searchValue]);
 | |
| 
 | |
|   function searchNode(val: string) {
 | |
|     setSearchValue(val);
 | |
|   }
 | |
| 
 | |
|   return (
 | |
|     <Popover
 | |
|       placement='bottom'
 | |
|       trigger={['click']}
 | |
|       content={
 | |
|         <div className='w-[320px] overflow-hidden overflow-y-auto scrollbar-default'>
 | |
|           <p className='my-2 font-bold'>{t('add_node')}</p>
 | |
|           <Search placeholder='Search node' onSearch={searchNode} />
 | |
| 
 | |
|           <h2 className='my-2 ml-2 font-semibold'>{t('operators')}</h2>
 | |
|           <Collapse
 | |
|             className='max-h-[300px] overflow-hidden overflow-y-auto scrollbar-default'
 | |
|             size='small'
 | |
|             defaultActiveKey={['']}
 | |
|             items={operatorItems}
 | |
|           />
 | |
| 
 | |
|           <h2 className='my-2 ml-2 font-semibold'>{t('resource')}</h2>
 | |
|           <Collapse
 | |
|             className='max-h-[300px] overflow-hidden overflow-y-auto scrollbar-default'
 | |
|             size='small'
 | |
|             defaultActiveKey={['']}
 | |
|             items={resourceItems}
 | |
|           />
 | |
|         </div>
 | |
|       }
 | |
|     >
 | |
|       <Button
 | |
|         type='primary'
 | |
|         className='flex items-center justify-center rounded-full left-4 top-4'
 | |
|         style={{ zIndex: 1050 }}
 | |
|         icon={<PlusOutlined />}
 | |
|       />
 | |
|     </Popover>
 | |
|   );
 | |
| };
 | |
| 
 | |
| export default AddNodes;
 |