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

feat: add delete menu to graph node #918 (#1133)

Browse files

### What problem does this PR solve?
feat: add delete menu to graph node #918

### Type of change

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

web/src/components/operate-dropdown/index.tsx CHANGED
@@ -1,6 +1,5 @@
1
- import { ReactComponent as MoreIcon } from '@/assets/svg/more.svg';
2
  import { useShowDeleteConfirm } from '@/hooks/commonHooks';
3
- import { DeleteOutlined } from '@ant-design/icons';
4
  import { Dropdown, MenuProps, Space } from 'antd';
5
  import { useTranslation } from 'react-i18next';
6
 
@@ -8,12 +7,14 @@ import React from 'react';
8
  import styles from './index.less';
9
 
10
  interface IProps {
11
- deleteItem: () => Promise<any>;
 
12
  }
13
 
14
  const OperateDropdown = ({
15
  deleteItem,
16
  children,
 
17
  }: React.PropsWithChildren<IProps>) => {
18
  const { t } = useTranslation();
19
  const showDeleteConfirm = useShowDeleteConfirm();
@@ -51,7 +52,10 @@ const OperateDropdown = ({
51
  >
52
  {children || (
53
  <span className={styles.delete}>
54
- <MoreIcon />
 
 
 
55
  </span>
56
  )}
57
  </Dropdown>
 
 
1
  import { useShowDeleteConfirm } from '@/hooks/commonHooks';
2
+ import { DeleteOutlined, MoreOutlined } from '@ant-design/icons';
3
  import { Dropdown, MenuProps, Space } from 'antd';
4
  import { useTranslation } from 'react-i18next';
5
 
 
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();
 
52
  >
53
  {children || (
54
  <span className={styles.delete}>
55
+ <MoreOutlined
56
+ rotate={90}
57
+ style={{ fontSize: iconFontSize, color: 'gray', cursor: 'pointer' }}
58
+ />
59
  </span>
60
  )}
61
  </Dropdown>
web/src/pages/flow/canvas/index.tsx CHANGED
@@ -18,12 +18,12 @@ import {
18
  useSelectCanvasData,
19
  useShowDrawer,
20
  } from '../hooks';
21
- import { TextUpdaterNode } from './node';
22
 
23
  import ChatDrawer from '../chat/drawer';
24
  import styles from './index.less';
25
 
26
- const nodeTypes = { textUpdater: TextUpdaterNode };
27
 
28
  const edgeTypes = {
29
  buttonEdge: ButtonEdge,
 
18
  useSelectCanvasData,
19
  useShowDrawer,
20
  } from '../hooks';
21
+ import { RagNode } from './node';
22
 
23
  import ChatDrawer from '../chat/drawer';
24
  import styles from './index.less';
25
 
26
+ const nodeTypes = { ragNode: RagNode };
27
 
28
  const edgeTypes = {
29
  buttonEdge: ButtonEdge,
web/src/pages/flow/canvas/node/index.less CHANGED
@@ -1,5 +1,6 @@
1
- .textUpdaterNode {
2
  // height: 50px;
 
3
  box-shadow:
4
  -6px 0 12px 0 rgba(179, 177, 177, 0.08),
5
  -3px 0 6px -4px rgba(0, 0, 0, 0.12),
@@ -13,6 +14,9 @@
13
  color: #777;
14
  font-size: 12px;
15
  }
 
 
 
16
  }
17
  .selectedNode {
18
  border: 1px solid rgb(59, 118, 244);
 
1
+ .ragNode {
2
  // height: 50px;
3
+ position: relative;
4
  box-shadow:
5
  -6px 0 12px 0 rgba(179, 177, 177, 0.08),
6
  -3px 0 6px -4px rgba(0, 0, 0, 0.12),
 
14
  color: #777;
15
  font-size: 12px;
16
  }
17
+ .description {
18
+ font-size: 10px;
19
+ }
20
  }
21
  .selectedNode {
22
  border: 1px solid rgb(59, 118, 244);
web/src/pages/flow/canvas/node/index.tsx CHANGED
@@ -1,19 +1,28 @@
1
  import classNames from 'classnames';
2
  import { Handle, NodeProps, Position } from 'reactflow';
3
 
4
- import { Space } from 'antd';
5
- import { Operator } from '../../constant';
 
 
6
  import OperatorIcon from '../../operator-icon';
 
7
  import styles from './index.less';
8
 
9
- export function TextUpdaterNode({
 
10
  data,
11
  isConnectable = true,
12
  selected,
13
  }: NodeProps<{ label: string }>) {
 
 
 
 
 
14
  return (
15
  <section
16
- className={classNames(styles.textUpdaterNode, {
17
  [styles.selectedNode]: selected,
18
  })}
19
  >
@@ -37,14 +46,21 @@ export function TextUpdaterNode({
37
  {/* <PlusCircleOutlined style={{ fontSize: 10 }} /> */}
38
  </Handle>
39
  <Handle type="source" position={Position.Bottom} id="a" isConnectable />
40
- <div>
41
- <Space size={4}>
42
  <OperatorIcon
43
  name={data.label as Operator}
44
  fontSize={12}
45
  ></OperatorIcon>
46
- {data.label}
47
  </Space>
 
 
 
 
 
 
 
48
  </div>
49
  </section>
50
  );
 
1
  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, {
26
  [styles.selectedNode]: selected,
27
  })}
28
  >
 
46
  {/* <PlusCircleOutlined style={{ fontSize: 10 }} /> */}
47
  </Handle>
48
  <Handle type="source" position={Position.Bottom} id="a" isConnectable />
49
+ <Flex gap={10} justify={'space-between'}>
50
+ <Space size={6}>
51
  <OperatorIcon
52
  name={data.label as Operator}
53
  fontSize={12}
54
  ></OperatorIcon>
55
+ <span>{data.label}</span>
56
  </Space>
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
  );
web/src/pages/flow/constant.tsx CHANGED
@@ -19,18 +19,27 @@ export const operatorIconMap = {
19
  [Operator.Begin]: SlidersOutlined,
20
  };
21
 
22
- export const componentList = [
 
 
 
 
 
 
 
 
 
23
  {
24
  name: Operator.Retrieval,
25
- description: '',
26
  },
27
  {
28
  name: Operator.Generate,
29
- description: '',
30
  },
31
  {
32
  name: Operator.Answer,
33
- description: '',
34
  },
35
  ];
36
 
 
19
  [Operator.Begin]: SlidersOutlined,
20
  };
21
 
22
+ export const operatorMap = {
23
+ [Operator.Retrieval]: {
24
+ description: 'Retrieval description drjlftglrthjftl',
25
+ },
26
+ [Operator.Generate]: { description: 'Generate description' },
27
+ [Operator.Answer]: { description: 'Answer description' },
28
+ [Operator.Begin]: { description: 'Begin description' },
29
+ };
30
+
31
+ export const componentMenuList = [
32
  {
33
  name: Operator.Retrieval,
34
+ description: operatorMap[Operator.Retrieval].description,
35
  },
36
  {
37
  name: Operator.Generate,
38
+ description: operatorMap[Operator.Generate].description,
39
  },
40
  {
41
  name: Operator.Answer,
42
+ description: operatorMap[Operator.Answer].description,
43
  },
44
  ];
45
 
web/src/pages/flow/flow-sider/index.tsx CHANGED
@@ -1,13 +1,15 @@
1
- import { Card, Flex, Layout, Space } from 'antd';
2
  import classNames from 'classnames';
3
 
4
- import { componentList } from '../constant';
5
  import { useHandleDrag } from '../hooks';
6
  import OperatorIcon from '../operator-icon';
7
  import styles from './index.less';
8
 
9
  const { Sider } = Layout;
10
 
 
 
11
  interface IProps {
12
  setCollapsed: (width: boolean) => void;
13
  collapsed: boolean;
@@ -25,7 +27,7 @@ const FlowSide = ({ setCollapsed, collapsed }: IProps) => {
25
  onCollapse={(value) => setCollapsed(value)}
26
  >
27
  <Flex vertical gap={10} className={styles.siderContent}>
28
- {componentList.map((x) => {
29
  return (
30
  <Card
31
  key={x.name}
@@ -37,13 +39,14 @@ const FlowSide = ({ setCollapsed, collapsed }: IProps) => {
37
  <Flex justify="space-between" align="center">
38
  <Space size={15}>
39
  <OperatorIcon name={x.name}></OperatorIcon>
40
- {/* <Avatar
41
- icon={<OperatorIcon name={x.name}></OperatorIcon>}
42
- shape={'square'}
43
- /> */}
44
  <section>
45
  <b>{x.name}</b>
46
- <div>{x.description}</div>
 
 
 
 
 
47
  </section>
48
  </Space>
49
  </Flex>
 
1
+ import { Card, Flex, Layout, Space, Typography } from 'antd';
2
  import classNames from 'classnames';
3
 
4
+ import { componentMenuList } from '../constant';
5
  import { useHandleDrag } from '../hooks';
6
  import OperatorIcon from '../operator-icon';
7
  import styles from './index.less';
8
 
9
  const { Sider } = Layout;
10
 
11
+ const { Text } = Typography;
12
+
13
  interface IProps {
14
  setCollapsed: (width: boolean) => void;
15
  collapsed: boolean;
 
27
  onCollapse={(value) => setCollapsed(value)}
28
  >
29
  <Flex vertical gap={10} className={styles.siderContent}>
30
+ {componentMenuList.map((x) => {
31
  return (
32
  <Card
33
  key={x.name}
 
39
  <Flex justify="space-between" align="center">
40
  <Space size={15}>
41
  <OperatorIcon name={x.name}></OperatorIcon>
 
 
 
 
42
  <section>
43
  <b>{x.name}</b>
44
+ <Text
45
+ ellipsis={{ tooltip: x.description }}
46
+ style={{ width: 130 }}
47
+ >
48
+ {x.description}
49
+ </Text>
50
  </section>
51
  </Space>
52
  </Flex>
web/src/pages/flow/hooks.ts CHANGED
@@ -18,6 +18,7 @@ import { Node, Position, ReactFlowInstance } from 'reactflow';
18
  import { useDebounceEffect } from 'ahooks';
19
  import { humanId } from 'human-id';
20
  import { useParams } from 'umi';
 
21
  import useGraphStore, { RFState } from './store';
22
  import { buildDslComponentsByGraph } from './utils';
23
 
@@ -79,7 +80,7 @@ export const useHandleDrop = () => {
79
  });
80
  const newNode = {
81
  id: `${type}:${humanId()}`,
82
- type: 'textUpdater',
83
  position: position || {
84
  x: 0,
85
  y: 0,
@@ -110,7 +111,9 @@ export const useShowDrawer = () => {
110
  const handleShow = useCallback(
111
  (node: Node) => {
112
  setClickedNode(node);
113
- showDrawer();
 
 
114
  },
115
  [showDrawer],
116
  );
 
18
  import { useDebounceEffect } from 'ahooks';
19
  import { humanId } from 'human-id';
20
  import { useParams } from 'umi';
21
+ import { Operator } from './constant';
22
  import useGraphStore, { RFState } from './store';
23
  import { buildDslComponentsByGraph } from './utils';
24
 
 
80
  });
81
  const newNode = {
82
  id: `${type}:${humanId()}`,
83
+ type: 'ragNode',
84
  position: position || {
85
  x: 0,
86
  y: 0,
 
111
  const handleShow = useCallback(
112
  (node: Node) => {
113
  setClickedNode(node);
114
+ if (node.data.label !== Operator.Answer) {
115
+ showDrawer();
116
+ }
117
  },
118
  [showDrawer],
119
  );
web/src/pages/flow/mock.tsx CHANGED
@@ -5,7 +5,7 @@ export const initialNodes = [
5
  sourcePosition: Position.Left,
6
  targetPosition: Position.Right,
7
  id: 'node-1',
8
- type: 'textUpdater',
9
  position: { x: 0, y: 0 },
10
  // position: { x: 400, y: 100 },
11
  data: { label: 123 },
@@ -38,7 +38,7 @@ export const dsl = {
38
  nodes: [
39
  {
40
  id: 'begin',
41
- type: 'textUpdater',
42
  position: {
43
  x: 50,
44
  y: 200,
@@ -51,7 +51,7 @@ export const dsl = {
51
  },
52
  // {
53
  // id: 'Answer:China',
54
- // type: 'textUpdater',
55
  // position: {
56
  // x: 150,
57
  // y: 200,
@@ -64,7 +64,7 @@ export const dsl = {
64
  // },
65
  // {
66
  // id: 'Retrieval:China',
67
- // type: 'textUpdater',
68
  // position: {
69
  // x: 250,
70
  // y: 200,
@@ -77,7 +77,7 @@ export const dsl = {
77
  // },
78
  // {
79
  // id: 'Generate:China',
80
- // type: 'textUpdater',
81
  // position: {
82
  // x: 100,
83
  // y: 100,
 
5
  sourcePosition: Position.Left,
6
  targetPosition: Position.Right,
7
  id: 'node-1',
8
+ type: 'ragNode',
9
  position: { x: 0, y: 0 },
10
  // position: { x: 400, y: 100 },
11
  data: { label: 123 },
 
38
  nodes: [
39
  {
40
  id: 'begin',
41
+ type: 'ragNode',
42
  position: {
43
  x: 50,
44
  y: 200,
 
51
  },
52
  // {
53
  // id: 'Answer:China',
54
+ // type: 'ragNode',
55
  // position: {
56
  // x: 150,
57
  // y: 200,
 
64
  // },
65
  // {
66
  // id: 'Retrieval:China',
67
+ // type: 'ragNode',
68
  // position: {
69
  // x: 250,
70
  // y: 200,
 
77
  // },
78
  // {
79
  // id: 'Generate:China',
80
+ // type: 'ragNode',
81
  // position: {
82
  // x: 100,
83
  // y: 100,
web/src/pages/flow/store.ts CHANGED
@@ -34,75 +34,88 @@ export type RFState = {
34
  addNode: (nodes: Node) => void;
35
  deleteEdge: () => void;
36
  deleteEdgeById: (id: string) => void;
 
37
  findNodeByName: (operatorName: Operator) => Node | undefined;
38
  };
39
 
40
  // this is our useStore hook that we can use in our components to get parts of the store and call actions
41
  const useGraphStore = create<RFState>()(
42
- devtools((set, get) => ({
43
- nodes: [] as Node[],
44
- edges: [] as Edge[],
45
- selectedNodeIds: [],
46
- selectedEdgeIds: [],
47
- onNodesChange: (changes: NodeChange[]) => {
48
- set({
49
- nodes: applyNodeChanges(changes, get().nodes),
50
- });
51
- },
52
- onEdgesChange: (changes: EdgeChange[]) => {
53
- set({
54
- edges: applyEdgeChanges(changes, get().edges),
55
- });
56
- },
57
- onConnect: (connection: Connection) => {
58
- set({
59
- edges: addEdge(connection, get().edges),
60
- });
61
- },
62
- onSelectionChange: ({ nodes, edges }: OnSelectionChangeParams) => {
63
- set({
64
- selectedEdgeIds: edges.map((x) => x.id),
65
- selectedNodeIds: nodes.map((x) => x.id),
66
- });
67
- },
68
- setNodes: (nodes: Node[]) => {
69
- set({ nodes });
70
- },
71
- setEdges: (edges: Edge[]) => {
72
- set({ edges });
73
- },
74
- addNode: (node: Node) => {
75
- set({ nodes: get().nodes.concat(node) });
76
- },
77
- deleteEdge: () => {
78
- const { edges, selectedEdgeIds } = get();
79
- set({
80
- edges: edges.filter((edge) =>
81
- selectedEdgeIds.every((x) => x !== edge.id),
82
- ),
83
- });
84
- },
85
- deleteEdgeById: (id: string) => {
86
- const { edges } = get();
87
- set({
88
- edges: edges.filter((edge) => edge.id !== id),
89
- });
90
- },
91
- findNodeByName: (name: Operator) => {
92
- return get().nodes.find((x) => x.data.label === name);
93
- },
94
- updateNodeForm: (nodeId: string, values: any) => {
95
- set({
96
- nodes: get().nodes.map((node) => {
97
- if (node.id === nodeId) {
98
- node.data = { ...node.data, form: values };
99
- }
 
 
 
 
 
 
 
 
 
 
100
 
101
- return node;
102
- }),
103
- });
104
- },
105
- })),
 
 
106
  );
107
 
108
  export default useGraphStore;
 
34
  addNode: (nodes: Node) => void;
35
  deleteEdge: () => void;
36
  deleteEdgeById: (id: string) => void;
37
+ deleteNodeById: (id: string) => void;
38
  findNodeByName: (operatorName: Operator) => Node | undefined;
39
  };
40
 
41
  // this is our useStore hook that we can use in our components to get parts of the store and call actions
42
  const useGraphStore = create<RFState>()(
43
+ devtools(
44
+ (set, get) => ({
45
+ nodes: [] as Node[],
46
+ edges: [] as Edge[],
47
+ selectedNodeIds: [] as string[],
48
+ selectedEdgeIds: [] as string[],
49
+ onNodesChange: (changes: NodeChange[]) => {
50
+ set({
51
+ nodes: applyNodeChanges(changes, get().nodes),
52
+ });
53
+ },
54
+ onEdgesChange: (changes: EdgeChange[]) => {
55
+ set({
56
+ edges: applyEdgeChanges(changes, get().edges),
57
+ });
58
+ },
59
+ onConnect: (connection: Connection) => {
60
+ set({
61
+ edges: addEdge(connection, get().edges),
62
+ });
63
+ },
64
+ onSelectionChange: ({ nodes, edges }: OnSelectionChangeParams) => {
65
+ set({
66
+ selectedEdgeIds: edges.map((x) => x.id),
67
+ selectedNodeIds: nodes.map((x) => x.id),
68
+ });
69
+ },
70
+ setNodes: (nodes: Node[]) => {
71
+ set({ nodes });
72
+ },
73
+ setEdges: (edges: Edge[]) => {
74
+ set({ edges });
75
+ },
76
+ addNode: (node: Node) => {
77
+ set({ nodes: get().nodes.concat(node) });
78
+ },
79
+ deleteEdge: () => {
80
+ const { edges, selectedEdgeIds } = get();
81
+ set({
82
+ edges: edges.filter((edge) =>
83
+ selectedEdgeIds.every((x) => x !== edge.id),
84
+ ),
85
+ });
86
+ },
87
+ deleteEdgeById: (id: string) => {
88
+ const { edges } = get();
89
+ set({
90
+ edges: edges.filter((edge) => edge.id !== id),
91
+ });
92
+ },
93
+ deleteNodeById: (id: string) => {
94
+ const { nodes, edges } = get();
95
+ set({
96
+ nodes: nodes.filter((node) => node.id !== id),
97
+ edges: edges
98
+ .filter((edge) => edge.source !== id)
99
+ .filter((edge) => edge.target !== id),
100
+ });
101
+ },
102
+ findNodeByName: (name: Operator) => {
103
+ return get().nodes.find((x) => x.data.label === name);
104
+ },
105
+ updateNodeForm: (nodeId: string, values: any) => {
106
+ set({
107
+ nodes: get().nodes.map((node) => {
108
+ if (node.id === nodeId) {
109
+ node.data = { ...node.data, form: values };
110
+ }
111
 
112
+ return node;
113
+ }),
114
+ });
115
+ },
116
+ }),
117
+ { name: 'graph' },
118
+ ),
119
  );
120
 
121
  export default useGraphStore;
web/src/pages/flow/utils.ts CHANGED
@@ -41,7 +41,7 @@ export const buildNodesAndEdgesFromDSLComponents = (data: DSLComponents) => {
41
  const upstream = [...value.upstream];
42
  nodes.push({
43
  id: key,
44
- type: 'textUpdater',
45
  position: { x: 0, y: 0 },
46
  data: {
47
  label: value.obj.component_name,
 
41
  const upstream = [...value.upstream];
42
  nodes.push({
43
  id: key,
44
+ type: 'ragNode',
45
  position: { x: 0, y: 0 },
46
  data: {
47
  label: value.obj.component_name,