balibabu commited on
Commit
457b4e6
·
1 Parent(s): bfb0635

feat: restrict classification operators cannot be connected to Answer and other classification #918 (#1294)

Browse files

### What problem does this PR solve?

feat: restrict classification operators cannot be connected to Answer
and other classification #918

### Type of change

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

web/src/pages/flow/canvas/index.tsx CHANGED
@@ -20,9 +20,16 @@ import {
20
  import { RagNode } from './node';
21
 
22
  import ChatDrawer from '../chat/drawer';
 
23
  import styles from './index.less';
 
 
24
 
25
- const nodeTypes = { ragNode: RagNode };
 
 
 
 
26
 
27
  const edgeTypes = {
28
  buttonEdge: ButtonEdge,
@@ -76,6 +83,7 @@ function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) {
76
  onKeyUp={handleKeyUp}
77
  onSelectionChange={onSelectionChange}
78
  nodeOrigin={[0.5, 0]}
 
79
  onChange={(...params) => {
80
  console.info('params:', ...params);
81
  }}
 
20
  import { RagNode } from './node';
21
 
22
  import ChatDrawer from '../chat/drawer';
23
+ import { isValidConnection } from '../utils';
24
  import styles from './index.less';
25
+ import { BeginNode } from './node/begin-node';
26
+ import { CategorizeNode } from './node/categorize-node';
27
 
28
+ const nodeTypes = {
29
+ ragNode: RagNode,
30
+ categorizeNode: CategorizeNode,
31
+ beginNode: BeginNode,
32
+ };
33
 
34
  const edgeTypes = {
35
  buttonEdge: ButtonEdge,
 
83
  onKeyUp={handleKeyUp}
84
  onSelectionChange={onSelectionChange}
85
  nodeOrigin={[0.5, 0]}
86
+ isValidConnection={isValidConnection}
87
  onChange={(...params) => {
88
  console.info('params:', ...params);
89
  }}
web/src/pages/flow/canvas/node/begin-node.tsx ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Flex, Space } from 'antd';
2
+ import classNames from 'classnames';
3
+ import { Handle, NodeProps, Position } from 'reactflow';
4
+ import { Operator } from '../../constant';
5
+ import { NodeData } from '../../interface';
6
+ import OperatorIcon from '../../operator-icon';
7
+ import NodeDropdown from './dropdown';
8
+
9
+ import styles from './index.less';
10
+
11
+ // TODO: do not allow other nodes to connect to this node
12
+ export function BeginNode({ id, data, selected }: NodeProps<NodeData>) {
13
+ return (
14
+ <section
15
+ className={classNames(styles.ragNode, {
16
+ [styles.selectedNode]: selected,
17
+ })}
18
+ >
19
+ <Handle
20
+ type="source"
21
+ position={Position.Right}
22
+ isConnectable
23
+ className={styles.handle}
24
+ ></Handle>
25
+ <Flex vertical align="center" justify="center">
26
+ <Space size={6}>
27
+ <OperatorIcon
28
+ name={data.label as Operator}
29
+ fontSize={16}
30
+ ></OperatorIcon>
31
+ <NodeDropdown id={id}></NodeDropdown>
32
+ </Space>
33
+ </Flex>
34
+ <section className={styles.bottomBox}>
35
+ <div className={styles.nodeName}>{id}</div>
36
+ </section>
37
+ </section>
38
+ );
39
+ }
web/src/pages/flow/canvas/node/categorize-node.tsx ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Flex, Space } from 'antd';
2
+ import classNames from 'classnames';
3
+ import get from 'lodash/get';
4
+ import { Handle, NodeProps, Position } from 'reactflow';
5
+ import { CategorizeAnchorPointPositions, Operator } from '../../constant';
6
+ import { NodeData } from '../../interface';
7
+ import OperatorIcon from '../../operator-icon';
8
+ import CategorizeHandle from './categorize-handle';
9
+ import NodeDropdown from './dropdown';
10
+
11
+ import styles from './index.less';
12
+
13
+ export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) {
14
+ const categoryData = get(data, 'form.category_description') ?? {};
15
+
16
+ return (
17
+ <section
18
+ className={classNames(styles.ragNode, {
19
+ [styles.selectedNode]: selected,
20
+ })}
21
+ >
22
+ <Handle
23
+ type="target"
24
+ position={Position.Left}
25
+ isConnectable
26
+ className={styles.handle}
27
+ ></Handle>
28
+ {Object.keys(categoryData).map((x, idx) => (
29
+ <CategorizeHandle
30
+ top={CategorizeAnchorPointPositions[idx].top}
31
+ right={CategorizeAnchorPointPositions[idx].right}
32
+ key={idx}
33
+ text={x}
34
+ idx={idx}
35
+ ></CategorizeHandle>
36
+ ))}
37
+ <Flex vertical align="center" justify="center">
38
+ <Space size={6}>
39
+ <OperatorIcon
40
+ name={data.label as Operator}
41
+ fontSize={16}
42
+ ></OperatorIcon>
43
+ <NodeDropdown id={id}></NodeDropdown>
44
+ </Space>
45
+ </Flex>
46
+ <section className={styles.bottomBox}>
47
+ <div className={styles.nodeName}>{id}</div>
48
+ </section>
49
+ </section>
50
+ );
51
+ }
web/src/pages/flow/canvas/node/dropdown.tsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import OperateDropdown from '@/components/operate-dropdown';
2
+ import { CopyOutlined } from '@ant-design/icons';
3
+ import { Flex, MenuProps } from 'antd';
4
+ import { useCallback } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import useGraphStore from '../../store';
7
+
8
+ interface IProps {
9
+ id: string;
10
+ }
11
+
12
+ const NodeDropdown = ({ id }: IProps) => {
13
+ const { t } = useTranslation();
14
+ const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
15
+ const duplicateNodeById = useGraphStore((store) => store.duplicateNode);
16
+
17
+ const deleteNode = useCallback(() => {
18
+ deleteNodeById(id);
19
+ }, [id, deleteNodeById]);
20
+
21
+ const duplicateNode = useCallback(() => {
22
+ duplicateNodeById(id);
23
+ }, [id, duplicateNodeById]);
24
+
25
+ const items: MenuProps['items'] = [
26
+ {
27
+ key: '2',
28
+ onClick: duplicateNode,
29
+ label: (
30
+ <Flex justify={'space-between'}>
31
+ {t('common.copy')}
32
+ <CopyOutlined />
33
+ </Flex>
34
+ ),
35
+ },
36
+ ];
37
+
38
+ return (
39
+ <OperateDropdown
40
+ iconFontSize={14}
41
+ deleteItem={deleteNode}
42
+ items={items}
43
+ ></OperateDropdown>
44
+ );
45
+ };
46
+
47
+ export default NodeDropdown;
web/src/pages/flow/canvas/node/index.tsx CHANGED
@@ -1,17 +1,13 @@
1
  import classNames from 'classnames';
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 } from 'antd';
7
  import get from 'lodash/get';
8
- import { useCallback } from 'react';
9
- import { useTranslation } from 'react-i18next';
10
  import { CategorizeAnchorPointPositions, Operator } from '../../constant';
11
  import { NodeData } from '../../interface';
12
  import OperatorIcon from '../../operator-icon';
13
- import useGraphStore from '../../store';
14
  import CategorizeHandle from './categorize-handle';
 
15
  import styles from './index.less';
16
 
17
  export function RagNode({
@@ -20,34 +16,9 @@ export function RagNode({
20
  isConnectable = true,
21
  selected,
22
  }: NodeProps<NodeData>) {
23
- const { t } = useTranslation();
24
- const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
25
- const duplicateNodeById = useGraphStore((store) => store.duplicateNode);
26
-
27
- const deleteNode = useCallback(() => {
28
- deleteNodeById(id);
29
- }, [id, deleteNodeById]);
30
-
31
- const duplicateNode = useCallback(() => {
32
- duplicateNodeById(id);
33
- }, [id, duplicateNodeById]);
34
-
35
  const isCategorize = data.label === Operator.Categorize;
36
  const categoryData = get(data, 'form.category_description') ?? {};
37
 
38
- const items: MenuProps['items'] = [
39
- {
40
- key: '2',
41
- onClick: duplicateNode,
42
- label: (
43
- <Flex justify={'space-between'}>
44
- {t('common.copy')}
45
- <CopyOutlined />
46
- </Flex>
47
- ),
48
- },
49
- ];
50
-
51
  return (
52
  <section
53
  className={classNames(styles.ragNode, {
@@ -86,11 +57,7 @@ export function RagNode({
86
  name={data.label as Operator}
87
  fontSize={16}
88
  ></OperatorIcon>
89
- <OperateDropdown
90
- iconFontSize={14}
91
- deleteItem={deleteNode}
92
- items={items}
93
- ></OperateDropdown>
94
  </Space>
95
  </Flex>
96
 
 
1
  import classNames from 'classnames';
2
  import { Handle, NodeProps, Position } from 'reactflow';
3
 
4
+ import { Flex, Space } from 'antd';
 
 
5
  import get from 'lodash/get';
 
 
6
  import { CategorizeAnchorPointPositions, Operator } from '../../constant';
7
  import { NodeData } from '../../interface';
8
  import OperatorIcon from '../../operator-icon';
 
9
  import CategorizeHandle from './categorize-handle';
10
+ import NodeDropdown from './dropdown';
11
  import styles from './index.less';
12
 
13
  export function RagNode({
 
16
  isConnectable = true,
17
  selected,
18
  }: NodeProps<NodeData>) {
 
 
 
 
 
 
 
 
 
 
 
 
19
  const isCategorize = data.label === Operator.Categorize;
20
  const categoryData = get(data, 'form.category_description') ?? {};
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  return (
23
  <section
24
  className={classNames(styles.ragNode, {
 
57
  name={data.label as Operator}
58
  fontSize={16}
59
  ></OperatorIcon>
60
+ <NodeDropdown id={id}></NodeDropdown>
 
 
 
 
61
  </Space>
62
  </Flex>
63
 
web/src/pages/flow/constant.tsx CHANGED
@@ -97,3 +97,27 @@ export const CategorizeAnchorPointPositions = [
97
  { top: 91, right: 20 },
98
  { top: 98, right: 34 },
99
  ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  { top: 91, right: 20 },
98
  { top: 98, right: 34 },
99
  ];
100
+
101
+ // key is the source of the edge, value is the target of the edge
102
+ // no connection lines are allowed between key and value
103
+ export const RestrictedUpstreamMap = {
104
+ [Operator.Begin]: [
105
+ Operator.Begin,
106
+ Operator.Answer,
107
+ Operator.Categorize,
108
+ Operator.Generate,
109
+ Operator.Retrieval,
110
+ ],
111
+ [Operator.Categorize]: [Operator.Begin, Operator.Categorize, Operator.Answer],
112
+ [Operator.Answer]: [],
113
+ [Operator.Retrieval]: [],
114
+ [Operator.Generate]: [],
115
+ };
116
+
117
+ export const NodeMap = {
118
+ [Operator.Begin]: 'beginNode',
119
+ [Operator.Categorize]: 'categorizeNode',
120
+ [Operator.Retrieval]: 'ragNode',
121
+ [Operator.Generate]: 'ragNode',
122
+ [Operator.Answer]: 'ragNode',
123
+ };
web/src/pages/flow/hooks.ts CHANGED
@@ -25,7 +25,7 @@ import { useDebounceEffect } from 'ahooks';
25
  import { FormInstance } from 'antd';
26
  import { humanId } from 'human-id';
27
  import { useParams } from 'umi';
28
- import { Operator } from './constant';
29
  import useGraphStore, { RFState } from './store';
30
  import { buildDslComponentsByGraph } from './utils';
31
 
@@ -87,7 +87,7 @@ export const useHandleDrop = () => {
87
  });
88
  const newNode = {
89
  id: `${type}:${humanId()}`,
90
- type: 'ragNode',
91
  position: position || {
92
  x: 0,
93
  y: 0,
 
25
  import { FormInstance } from 'antd';
26
  import { humanId } from 'human-id';
27
  import { useParams } from 'umi';
28
+ import { NodeMap, Operator } from './constant';
29
  import useGraphStore, { RFState } from './store';
30
  import { buildDslComponentsByGraph } from './utils';
31
 
 
87
  });
88
  const newNode = {
89
  id: `${type}:${humanId()}`,
90
+ type: NodeMap[type as Operator] || 'ragNode',
91
  position: position || {
92
  x: 0,
93
  y: 0,
web/src/pages/flow/mock.tsx CHANGED
@@ -38,7 +38,7 @@ export const dsl = {
38
  nodes: [
39
  {
40
  id: 'begin',
41
- type: 'ragNode',
42
  position: {
43
  x: 50,
44
  y: 200,
 
38
  nodes: [
39
  {
40
  id: 'begin',
41
+ type: 'beginNode',
42
  position: {
43
  x: 50,
44
  y: 200,
web/src/pages/flow/utils.ts CHANGED
@@ -3,9 +3,13 @@ import { removeUselessFieldsFromValues } from '@/utils/form';
3
  import dagre from 'dagre';
4
  import { curry, isEmpty } from 'lodash';
5
  import pipe from 'lodash/fp/pipe';
6
- import { Edge, MarkerType, Node, Position } from 'reactflow';
7
  import { v4 as uuidv4 } from 'uuid';
8
- import { Operator, initialFormValuesMap } from './constant';
 
 
 
 
9
  import { NodeData } from './interface';
10
 
11
  const buildEdges = (
@@ -162,3 +166,14 @@ export const buildDslComponentsByGraph = (
162
 
163
  return components;
164
  };
 
 
 
 
 
 
 
 
 
 
 
 
3
  import dagre from 'dagre';
4
  import { curry, isEmpty } from 'lodash';
5
  import pipe from 'lodash/fp/pipe';
6
+ import { Connection, Edge, MarkerType, Node, Position } from 'reactflow';
7
  import { v4 as uuidv4 } from 'uuid';
8
+ import {
9
+ Operator,
10
+ RestrictedUpstreamMap,
11
+ initialFormValuesMap,
12
+ } from './constant';
13
  import { NodeData } from './interface';
14
 
15
  const buildEdges = (
 
166
 
167
  return components;
168
  };
169
+
170
+ export const getOperatorType = (id: string | null) => {
171
+ return id?.split(':')[0] as Operator | undefined;
172
+ };
173
+
174
+ // restricted lines cannot be connected successfully.
175
+ export const isValidConnection = (connection: Connection) => {
176
+ return RestrictedUpstreamMap[
177
+ getOperatorType(connection.source) as Operator
178
+ ]?.every((x) => x !== getOperatorType(connection.target));
179
+ };