balibabu commited on
Commit
4a674c0
·
1 Parent(s): ddd1aa2

feat: duplicate node #918 (#1136)

Browse files

### What problem does this PR solve?
feat: duplicate node #918


### Type of change


- [x] New Feature (non-breaking change which adds functionality)

web/src/components/operate-dropdown/index.tsx CHANGED
@@ -3,18 +3,20 @@ import { DeleteOutlined, MoreOutlined } from '@ant-design/icons';
3
  import { Dropdown, MenuProps, Space } from 'antd';
4
  import { useTranslation } from 'react-i18next';
5
 
6
- import React from 'react';
7
  import styles from './index.less';
8
 
9
  interface IProps {
10
  deleteItem: () => Promise<any> | void;
11
  iconFontSize?: number;
 
12
  }
13
 
14
  const OperateDropdown = ({
15
  deleteItem,
16
  children,
17
  iconFontSize = 30,
 
18
  }: React.PropsWithChildren<IProps>) => {
19
  const { t } = useTranslation();
20
  const showDeleteConfirm = useShowDeleteConfirm();
@@ -31,17 +33,20 @@ const OperateDropdown = ({
31
  }
32
  };
33
 
34
- const items: MenuProps['items'] = [
35
- {
36
- key: '1',
37
- label: (
38
- <Space>
39
- {t('common.delete')}
40
- <DeleteOutlined />
41
- </Space>
42
- ),
43
- },
44
- ];
 
 
 
45
 
46
  return (
47
  <Dropdown
 
3
  import { Dropdown, MenuProps, Space } from 'antd';
4
  import { useTranslation } from 'react-i18next';
5
 
6
+ import React, { useMemo } from 'react';
7
  import styles from './index.less';
8
 
9
  interface IProps {
10
  deleteItem: () => Promise<any> | void;
11
  iconFontSize?: number;
12
+ items?: MenuProps['items'];
13
  }
14
 
15
  const OperateDropdown = ({
16
  deleteItem,
17
  children,
18
  iconFontSize = 30,
19
+ items: otherItems = [],
20
  }: React.PropsWithChildren<IProps>) => {
21
  const { t } = useTranslation();
22
  const showDeleteConfirm = useShowDeleteConfirm();
 
33
  }
34
  };
35
 
36
+ const items: MenuProps['items'] = useMemo(() => {
37
+ return [
38
+ {
39
+ key: '1',
40
+ label: (
41
+ <Space>
42
+ {t('common.delete')}
43
+ <DeleteOutlined />
44
+ </Space>
45
+ ),
46
+ },
47
+ ...otherItems,
48
+ ];
49
+ }, [t, otherItems]);
50
 
51
  return (
52
  <Dropdown
web/src/pages/flow/canvas/context-menu/index.tsx CHANGED
@@ -63,6 +63,8 @@ export function NodeContextMenu({
63
  );
64
  }
65
 
 
 
66
  export const useHandleNodeContextMenu = (sideWidth: number) => {
67
  const [menu, setMenu] = useState<INodeContextMenu>({} as INodeContextMenu);
68
  const ref = useRef<any>(null);
 
63
  );
64
  }
65
 
66
+ /* @deprecated
67
+ */
68
  export const useHandleNodeContextMenu = (sideWidth: number) => {
69
  const [menu, setMenu] = useState<INodeContextMenu>({} as INodeContextMenu);
70
  const ref = useRef<any>(null);
web/src/pages/flow/canvas/index.tsx CHANGED
@@ -8,7 +8,6 @@ import ReactFlow, {
8
  } from 'reactflow';
9
  import 'reactflow/dist/style.css';
10
 
11
- import { NodeContextMenu, useHandleNodeContextMenu } from './context-menu';
12
  import { ButtonEdge } from './edge';
13
 
14
  import FlowDrawer from '../flow-drawer';
@@ -30,12 +29,11 @@ const edgeTypes = {
30
  };
31
 
32
  interface IProps {
33
- sideWidth: number;
34
  chatDrawerVisible: boolean;
35
  hideChatDrawer(): void;
36
  }
37
 
38
- function FlowCanvas({ sideWidth, chatDrawerVisible, hideChatDrawer }: IProps) {
39
  const {
40
  nodes,
41
  edges,
@@ -45,8 +43,6 @@ function FlowCanvas({ sideWidth, chatDrawerVisible, hideChatDrawer }: IProps) {
45
  onSelectionChange,
46
  } = useSelectCanvasData();
47
 
48
- const { ref, menu, onNodeContextMenu, onPaneClick } =
49
- useHandleNodeContextMenu(sideWidth);
50
  const { drawerVisible, hideDrawer, showDrawer, clickedNode } =
51
  useShowDrawer();
52
 
@@ -64,18 +60,15 @@ function FlowCanvas({ sideWidth, chatDrawerVisible, hideChatDrawer }: IProps) {
64
  return (
65
  <div className={styles.canvasWrapper}>
66
  <ReactFlow
67
- ref={ref}
68
  connectionMode={ConnectionMode.Loose}
69
  nodes={nodes}
70
  onNodesChange={onNodesChange}
71
- onNodeContextMenu={onNodeContextMenu}
72
  edges={edges}
73
  onEdgesChange={onEdgesChange}
74
  fitView
75
  onConnect={onConnect}
76
  nodeTypes={nodeTypes}
77
  edgeTypes={edgeTypes}
78
- onPaneClick={onPaneClick}
79
  onDrop={onDrop}
80
  onDragOver={onDragOver}
81
  onNodeClick={onNodeClick}
@@ -95,9 +88,6 @@ function FlowCanvas({ sideWidth, chatDrawerVisible, hideChatDrawer }: IProps) {
95
  >
96
  <Background />
97
  <Controls />
98
- {Object.keys(menu).length > 0 && (
99
- <NodeContextMenu onClick={onPaneClick} {...(menu as any)} />
100
- )}
101
  </ReactFlow>
102
  <FlowDrawer
103
  node={clickedNode}
 
8
  } from 'reactflow';
9
  import 'reactflow/dist/style.css';
10
 
 
11
  import { ButtonEdge } from './edge';
12
 
13
  import FlowDrawer from '../flow-drawer';
 
29
  };
30
 
31
  interface IProps {
 
32
  chatDrawerVisible: boolean;
33
  hideChatDrawer(): void;
34
  }
35
 
36
+ function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) {
37
  const {
38
  nodes,
39
  edges,
 
43
  onSelectionChange,
44
  } = useSelectCanvasData();
45
 
 
 
46
  const { drawerVisible, hideDrawer, showDrawer, clickedNode } =
47
  useShowDrawer();
48
 
 
60
  return (
61
  <div className={styles.canvasWrapper}>
62
  <ReactFlow
 
63
  connectionMode={ConnectionMode.Loose}
64
  nodes={nodes}
65
  onNodesChange={onNodesChange}
 
66
  edges={edges}
67
  onEdgesChange={onEdgesChange}
68
  fitView
69
  onConnect={onConnect}
70
  nodeTypes={nodeTypes}
71
  edgeTypes={edgeTypes}
 
72
  onDrop={onDrop}
73
  onDragOver={onDragOver}
74
  onNodeClick={onNodeClick}
 
88
  >
89
  <Background />
90
  <Controls />
 
 
 
91
  </ReactFlow>
92
  <FlowDrawer
93
  node={clickedNode}
web/src/pages/flow/canvas/node/index.tsx CHANGED
@@ -2,24 +2,50 @@ import classNames from 'classnames';
2
  import { Handle, NodeProps, Position } from 'reactflow';
3
 
4
  import OperateDropdown from '@/components/operate-dropdown';
5
- import { Flex, Space } from 'antd';
 
6
  import { useCallback } from 'react';
 
7
  import { Operator, operatorMap } from '../../constant';
8
  import OperatorIcon from '../../operator-icon';
9
  import useGraphStore from '../../store';
10
  import styles from './index.less';
11
 
 
 
12
  export function RagNode({
13
  id,
14
  data,
15
  isConnectable = true,
16
  selected,
17
  }: NodeProps<{ label: string }>) {
 
18
  const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
 
 
19
  const deleteNode = useCallback(() => {
20
  deleteNodeById(id);
21
  }, [id, deleteNodeById]);
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  return (
24
  <section
25
  className={classNames(styles.ragNode, {
@@ -57,10 +83,17 @@ export function RagNode({
57
  <OperateDropdown
58
  iconFontSize={14}
59
  deleteItem={deleteNode}
 
60
  ></OperateDropdown>
61
  </Flex>
62
- <div className={styles.description}>
63
- {operatorMap[data.label as Operator].description}
 
 
 
 
 
 
64
  </div>
65
  </section>
66
  );
 
2
  import { Handle, NodeProps, Position } from 'reactflow';
3
 
4
  import OperateDropdown from '@/components/operate-dropdown';
5
+ import { CopyOutlined } from '@ant-design/icons';
6
+ import { Flex, MenuProps, Space, Typography } from 'antd';
7
  import { useCallback } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
  import { Operator, operatorMap } from '../../constant';
10
  import OperatorIcon from '../../operator-icon';
11
  import useGraphStore from '../../store';
12
  import styles from './index.less';
13
 
14
+ const { Text } = Typography;
15
+
16
  export function RagNode({
17
  id,
18
  data,
19
  isConnectable = true,
20
  selected,
21
  }: NodeProps<{ label: string }>) {
22
+ const { t } = useTranslation();
23
  const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
24
+ const duplicateNodeById = useGraphStore((store) => store.duplicateNode);
25
+
26
  const deleteNode = useCallback(() => {
27
  deleteNodeById(id);
28
  }, [id, deleteNodeById]);
29
 
30
+ const duplicateNode = useCallback(() => {
31
+ duplicateNodeById(id);
32
+ }, [id, duplicateNodeById]);
33
+
34
+ const description = operatorMap[data.label as Operator].description;
35
+
36
+ const items: MenuProps['items'] = [
37
+ {
38
+ key: '2',
39
+ onClick: duplicateNode,
40
+ label: (
41
+ <Flex justify={'space-between'}>
42
+ {t('common.copy')}
43
+ <CopyOutlined />
44
+ </Flex>
45
+ ),
46
+ },
47
+ ];
48
+
49
  return (
50
  <section
51
  className={classNames(styles.ragNode, {
 
83
  <OperateDropdown
84
  iconFontSize={14}
85
  deleteItem={deleteNode}
86
+ items={items}
87
  ></OperateDropdown>
88
  </Flex>
89
+ <div>
90
+ <Text
91
+ ellipsis={{ tooltip: description }}
92
+ style={{ width: 130 }}
93
+ className={styles.description}
94
+ >
95
+ {description}
96
+ </Text>
97
  </div>
98
  </section>
99
  );
web/src/pages/flow/index.tsx CHANGED
@@ -27,7 +27,6 @@ function RagFlow() {
27
  <FlowHeader showChatDrawer={showChatDrawer}></FlowHeader>
28
  <Content style={{ margin: 0 }}>
29
  <FlowCanvas
30
- sideWidth={collapsed ? 0 : 200}
31
  chatDrawerVisible={chatDrawerVisible}
32
  hideChatDrawer={hideChatDrawer}
33
  ></FlowCanvas>
 
27
  <FlowHeader showChatDrawer={showChatDrawer}></FlowHeader>
28
  <Content style={{ margin: 0 }}>
29
  <FlowCanvas
 
30
  chatDrawerVisible={chatDrawerVisible}
31
  hideChatDrawer={hideChatDrawer}
32
  ></FlowCanvas>
web/src/pages/flow/store.ts CHANGED
@@ -1,4 +1,5 @@
1
  import type {} from '@redux-devtools/extension';
 
2
  import {
3
  Connection,
4
  Edge,
@@ -32,6 +33,8 @@ export type RFState = {
32
  updateNodeForm: (nodeId: string, values: any) => void;
33
  onSelectionChange: OnSelectionChangeFunc;
34
  addNode: (nodes: Node) => void;
 
 
35
  deleteEdge: () => void;
36
  deleteEdgeById: (id: string) => void;
37
  deleteNodeById: (id: string) => void;
@@ -76,6 +79,26 @@ const useGraphStore = create<RFState>()(
76
  addNode: (node: Node) => {
77
  set({ nodes: get().nodes.concat(node) });
78
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  deleteEdge: () => {
80
  const { edges, selectedEdgeIds } = get();
81
  set({
 
1
  import type {} from '@redux-devtools/extension';
2
+ import { humanId } from 'human-id';
3
  import {
4
  Connection,
5
  Edge,
 
33
  updateNodeForm: (nodeId: string, values: any) => void;
34
  onSelectionChange: OnSelectionChangeFunc;
35
  addNode: (nodes: Node) => void;
36
+ getNode: (id: string) => Node | undefined;
37
+ duplicateNode: (id: string) => void;
38
  deleteEdge: () => void;
39
  deleteEdgeById: (id: string) => void;
40
  deleteNodeById: (id: string) => void;
 
79
  addNode: (node: Node) => {
80
  set({ nodes: get().nodes.concat(node) });
81
  },
82
+ getNode: (id: string) => {
83
+ return get().nodes.find((x) => x.id === id);
84
+ },
85
+ duplicateNode: (id: string) => {
86
+ const { getNode, addNode } = get();
87
+ const node = getNode(id);
88
+ const position = {
89
+ x: (node?.position?.x || 0) + 30,
90
+ y: (node?.position?.y || 0) + 20,
91
+ };
92
+
93
+ addNode({
94
+ ...(node || {}),
95
+ data: node?.data,
96
+ selected: false,
97
+ dragging: false,
98
+ id: `${node?.data?.label}:${humanId()}`,
99
+ position,
100
+ });
101
+ },
102
  deleteEdge: () => {
103
  const { edges, selectedEdgeIds } = get();
104
  set({