balibabu commited on
Commit
64b1da0
·
1 Parent(s): 673a675

feat: add RelevantForm #918 (#1344)

Browse files

### What problem does this PR solve?

feat: add RelevantForm #918

### Type of change

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

web/src/locales/en.ts CHANGED
@@ -558,6 +558,8 @@ The above is the content you need to summarize.`,
558
  addField: 'Add field',
559
  loop: 'Loop',
560
  createFlow: 'Create a workflow',
 
 
561
  },
562
  footer: {
563
  profile: 'All rights reserved @ React',
 
558
  addField: 'Add field',
559
  loop: 'Loop',
560
  createFlow: 'Create a workflow',
561
+ yes: 'Yes',
562
+ no: 'No',
563
  },
564
  footer: {
565
  profile: 'All rights reserved @ React',
web/src/pages/flow/canvas/index.tsx CHANGED
@@ -23,11 +23,13 @@ import ChatDrawer from '../chat/drawer';
23
  import styles from './index.less';
24
  import { BeginNode } from './node/begin-node';
25
  import { CategorizeNode } from './node/categorize-node';
 
26
 
27
  const nodeTypes = {
28
  ragNode: RagNode,
29
  categorizeNode: CategorizeNode,
30
  beginNode: BeginNode,
 
31
  };
32
 
33
  const edgeTypes = {
 
23
  import styles from './index.less';
24
  import { BeginNode } from './node/begin-node';
25
  import { CategorizeNode } from './node/categorize-node';
26
+ import { RelevantNode } from './node/relevant-node';
27
 
28
  const nodeTypes = {
29
  ragNode: RagNode,
30
  categorizeNode: CategorizeNode,
31
  beginNode: BeginNode,
32
+ relevantNode: RelevantNode,
33
  };
34
 
35
  const edgeTypes = {
web/src/pages/flow/canvas/node/categorize-handle.tsx CHANGED
@@ -14,7 +14,7 @@ interface IProps {
14
  top: number;
15
  right: number;
16
  text: string;
17
- idx: number;
18
  }
19
 
20
  const CategorizeHandle = ({ top, right, text, idx }: IProps) => {
@@ -30,6 +30,7 @@ const CategorizeHandle = ({ top, right, text, idx }: IProps) => {
30
  top: `${top}%`,
31
  right: `${right}%`,
32
  background: 'red',
 
33
  }}
34
  >
35
  <span className={styles.categorizeAnchorPointText}>{text}</span>
 
14
  top: number;
15
  right: number;
16
  text: string;
17
+ idx?: number;
18
  }
19
 
20
  const CategorizeHandle = ({ top, right, text, idx }: IProps) => {
 
30
  top: `${top}%`,
31
  right: `${right}%`,
32
  background: 'red',
33
+ color: 'black',
34
  }}
35
  >
36
  <span className={styles.categorizeAnchorPointText}>{text}</span>
web/src/pages/flow/canvas/node/index.tsx CHANGED
@@ -1,16 +1,10 @@
1
  import { Flex } from 'antd';
2
  import classNames from 'classnames';
3
- import get from 'lodash/get';
4
  import pick from 'lodash/pick';
5
  import { Handle, NodeProps, Position } from 'reactflow';
6
- import {
7
- CategorizeAnchorPointPositions,
8
- Operator,
9
- operatorMap,
10
- } from '../../constant';
11
  import { NodeData } from '../../interface';
12
  import OperatorIcon from '../../operator-icon';
13
- import CategorizeHandle from './categorize-handle';
14
  import NodeDropdown from './dropdown';
15
  import styles from './index.less';
16
 
@@ -20,8 +14,6 @@ export function RagNode({
20
  isConnectable = true,
21
  selected,
22
  }: NodeProps<NodeData>) {
23
- const isCategorize = data.label === Operator.Categorize;
24
- const categoryData = get(data, 'form.category_description') ?? {};
25
  const style = operatorMap[data.label as Operator];
26
 
27
  return (
@@ -47,16 +39,6 @@ export function RagNode({
47
  id="b"
48
  ></Handle>
49
  <Handle type="source" position={Position.Bottom} id="a" isConnectable />
50
- {isCategorize &&
51
- Object.keys(categoryData).map((x, idx) => (
52
- <CategorizeHandle
53
- top={CategorizeAnchorPointPositions[idx].top}
54
- right={CategorizeAnchorPointPositions[idx].right}
55
- key={idx}
56
- text={x}
57
- idx={idx}
58
- ></CategorizeHandle>
59
- ))}
60
  <Flex vertical align="center" justify={'center'} gap={6}>
61
  <OperatorIcon
62
  name={data.label as Operator}
 
1
  import { Flex } from 'antd';
2
  import classNames from 'classnames';
 
3
  import pick from 'lodash/pick';
4
  import { Handle, NodeProps, Position } from 'reactflow';
5
+ import { Operator, operatorMap } from '../../constant';
 
 
 
 
6
  import { NodeData } from '../../interface';
7
  import OperatorIcon from '../../operator-icon';
 
8
  import NodeDropdown from './dropdown';
9
  import styles from './index.less';
10
 
 
14
  isConnectable = true,
15
  selected,
16
  }: NodeProps<NodeData>) {
 
 
17
  const style = operatorMap[data.label as Operator];
18
 
19
  return (
 
39
  id="b"
40
  ></Handle>
41
  <Handle type="source" position={Position.Bottom} id="a" isConnectable />
 
 
 
 
 
 
 
 
 
 
42
  <Flex vertical align="center" justify={'center'} gap={6}>
43
  <OperatorIcon
44
  name={data.label as Operator}
web/src/pages/flow/canvas/node/relevant-node.tsx ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Flex } from 'antd';
2
+ import classNames from 'classnames';
3
+ import pick from 'lodash/pick';
4
+ import { Handle, NodeProps, Position } from 'reactflow';
5
+ import { Operator, operatorMap } from '../../constant';
6
+ import { NodeData } from '../../interface';
7
+ import OperatorIcon from '../../operator-icon';
8
+ import NodeDropdown from './dropdown';
9
+
10
+ import CategorizeHandle from './categorize-handle';
11
+ import styles from './index.less';
12
+
13
+ export function RelevantNode({ id, data, selected }: NodeProps<NodeData>) {
14
+ const style = operatorMap[data.label as Operator];
15
+
16
+ return (
17
+ <section
18
+ className={classNames(styles.ragNode, {
19
+ [styles.selectedNode]: selected,
20
+ })}
21
+ style={pick(style, ['backgroundColor', 'width', 'height', 'color'])}
22
+ >
23
+ <Handle
24
+ type="target"
25
+ position={Position.Left}
26
+ isConnectable
27
+ className={styles.handle}
28
+ id={'a'}
29
+ ></Handle>
30
+ <Handle
31
+ type="target"
32
+ position={Position.Top}
33
+ isConnectable
34
+ className={styles.handle}
35
+ id={'b'}
36
+ ></Handle>
37
+ <Handle
38
+ type="target"
39
+ position={Position.Bottom}
40
+ isConnectable
41
+ className={styles.handle}
42
+ id={'c'}
43
+ ></Handle>
44
+ <CategorizeHandle top={20} right={6} text={'yes'}></CategorizeHandle>
45
+ <CategorizeHandle top={80} right={6} text={'no'}></CategorizeHandle>
46
+ <Flex vertical align="center" justify="center">
47
+ <OperatorIcon
48
+ name={data.label as Operator}
49
+ fontSize={style.iconFontSize}
50
+ ></OperatorIcon>
51
+ <span
52
+ className={styles.type}
53
+ style={{ fontSize: style.fontSize ?? 14 }}
54
+ >
55
+ {data.label}
56
+ </span>
57
+ <NodeDropdown id={id}></NodeDropdown>
58
+ </Flex>
59
+ <section className={styles.bottomBox}>
60
+ <div className={styles.nodeName}>{data.name}</div>
61
+ </section>
62
+ </section>
63
+ );
64
+ }
web/src/pages/flow/categorize-form/dynamic-categorize.tsx CHANGED
@@ -2,8 +2,12 @@ import { useTranslate } from '@/hooks/commonHooks';
2
  import { CloseOutlined } from '@ant-design/icons';
3
  import { Button, Card, Form, Input, Select, Typography } from 'antd';
4
  import { useUpdateNodeInternals } from 'reactflow';
 
 
 
 
 
5
  import { ICategorizeItem } from '../interface';
6
- import { useBuildCategorizeToOptions, useHandleToSelectChange } from './hooks';
7
 
8
  interface IProps {
9
  nodeId?: string;
@@ -12,8 +16,11 @@ interface IProps {
12
  const DynamicCategorize = ({ nodeId }: IProps) => {
13
  const updateNodeInternals = useUpdateNodeInternals();
14
  const form = Form.useFormInstance();
15
- const buildCategorizeToOptions = useBuildCategorizeToOptions();
16
- const { handleSelectChange } = useHandleToSelectChange(nodeId);
 
 
 
17
  const { t } = useTranslate('flow');
18
 
19
  return (
 
2
  import { CloseOutlined } from '@ant-design/icons';
3
  import { Button, Card, Form, Input, Select, Typography } from 'antd';
4
  import { useUpdateNodeInternals } from 'reactflow';
5
+ import { Operator } from '../constant';
6
+ import {
7
+ useBuildFormSelectOptions,
8
+ useHandleFormSelectChange,
9
+ } from '../form-hooks';
10
  import { ICategorizeItem } from '../interface';
 
11
 
12
  interface IProps {
13
  nodeId?: string;
 
16
  const DynamicCategorize = ({ nodeId }: IProps) => {
17
  const updateNodeInternals = useUpdateNodeInternals();
18
  const form = Form.useFormInstance();
19
+ const buildCategorizeToOptions = useBuildFormSelectOptions(
20
+ Operator.Categorize,
21
+ nodeId,
22
+ );
23
+ const { handleSelectChange } = useHandleFormSelectChange(nodeId);
24
  const { t } = useTranslate('flow');
25
 
26
  return (
web/src/pages/flow/categorize-form/hooks.ts CHANGED
@@ -2,7 +2,6 @@ import get from 'lodash/get';
2
  import omit from 'lodash/omit';
3
  import { useCallback, useEffect } from 'react';
4
  import { Edge, Node } from 'reactflow';
5
- import { Operator } from '../constant';
6
  import {
7
  ICategorizeItem,
8
  ICategorizeItemResult,
@@ -11,28 +10,6 @@ import {
11
  } from '../interface';
12
  import useGraphStore from '../store';
13
 
14
- // exclude some nodes downstream of the classification node
15
- const excludedNodes = [Operator.Categorize, Operator.Answer, Operator.Begin];
16
-
17
- export const useBuildCategorizeToOptions = () => {
18
- const nodes = useGraphStore((state) => state.nodes);
19
-
20
- const buildCategorizeToOptions = useCallback(
21
- (toList: string[]) => {
22
- return nodes
23
- .filter(
24
- (x) =>
25
- excludedNodes.every((y) => y !== x.data.label) &&
26
- !toList.some((y) => y === x.id), // filter out selected values ​​in other to fields from the current drop-down box options
27
- )
28
- .map((x) => ({ label: x.data.name, value: x.id }));
29
- },
30
- [nodes],
31
- );
32
-
33
- return buildCategorizeToOptions;
34
- };
35
-
36
  /**
37
  * convert the following object into a list
38
  *
@@ -119,32 +96,3 @@ export const useHandleFormValuesChange = ({
119
 
120
  return { handleValuesChange };
121
  };
122
-
123
- export const useHandleToSelectChange = (nodeId?: string) => {
124
- const { addEdge, deleteEdgeBySourceAndSourceHandle } = useGraphStore(
125
- (state) => state,
126
- );
127
- const handleSelectChange = useCallback(
128
- (name?: string) => (value?: string) => {
129
- if (nodeId && name) {
130
- if (value) {
131
- addEdge({
132
- source: nodeId,
133
- target: value,
134
- sourceHandle: name,
135
- targetHandle: null,
136
- });
137
- } else {
138
- // clear selected value
139
- deleteEdgeBySourceAndSourceHandle({
140
- source: nodeId,
141
- sourceHandle: name,
142
- });
143
- }
144
- }
145
- },
146
- [addEdge, nodeId, deleteEdgeBySourceAndSourceHandle],
147
- );
148
-
149
- return { handleSelectChange };
150
- };
 
2
  import omit from 'lodash/omit';
3
  import { useCallback, useEffect } from 'react';
4
  import { Edge, Node } from 'reactflow';
 
5
  import {
6
  ICategorizeItem,
7
  ICategorizeItemResult,
 
10
  } from '../interface';
11
  import useGraphStore from '../store';
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  /**
14
  * convert the following object into a list
15
  *
 
96
 
97
  return { handleValuesChange };
98
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/pages/flow/constant.tsx CHANGED
@@ -67,7 +67,12 @@ export const operatorMap = {
67
  },
68
  [Operator.Relevant]: {
69
  description: 'BranchesOutlined description',
70
- backgroundColor: 'white',
 
 
 
 
 
71
  },
72
  [Operator.RewriteQuestion]: {
73
  description: 'RewriteQuestion description',
@@ -136,6 +141,7 @@ export const initialFormValuesMap = {
136
  [Operator.Generate]: initialGenerateValues,
137
  [Operator.Answer]: {},
138
  [Operator.Categorize]: {},
 
139
  };
140
 
141
  export const CategorizeAnchorPointPositions = [
@@ -173,6 +179,6 @@ export const NodeMap = {
173
  [Operator.Generate]: 'ragNode',
174
  [Operator.Answer]: 'ragNode',
175
  [Operator.Message]: 'ragNode',
176
- [Operator.Relevant]: 'ragNode',
177
  [Operator.RewriteQuestion]: 'ragNode',
178
  };
 
67
  },
68
  [Operator.Relevant]: {
69
  description: 'BranchesOutlined description',
70
+ backgroundColor: '#9fd94d',
71
+ color: 'white',
72
+ width: 70,
73
+ height: 70,
74
+ fontSize: 12,
75
+ iconFontSize: 16,
76
  },
77
  [Operator.RewriteQuestion]: {
78
  description: 'RewriteQuestion description',
 
141
  [Operator.Generate]: initialGenerateValues,
142
  [Operator.Answer]: {},
143
  [Operator.Categorize]: {},
144
+ [Operator.Relevant]: {},
145
  };
146
 
147
  export const CategorizeAnchorPointPositions = [
 
179
  [Operator.Generate]: 'ragNode',
180
  [Operator.Answer]: 'ragNode',
181
  [Operator.Message]: 'ragNode',
182
+ [Operator.Relevant]: 'relevantNode',
183
  [Operator.RewriteQuestion]: 'ragNode',
184
  };
web/src/pages/flow/form-hooks.ts ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback } from 'react';
2
+ import { Operator } from './constant';
3
+ import useGraphStore from './store';
4
+
5
+ const ExcludedNodesMap = {
6
+ // exclude some nodes downstream of the classification node
7
+ [Operator.Categorize]: [Operator.Categorize, Operator.Answer, Operator.Begin],
8
+ [Operator.Relevant]: [Operator.Begin],
9
+ };
10
+
11
+ export const useBuildFormSelectOptions = (
12
+ operatorName: Operator,
13
+ selfId?: string, // exclude the current node
14
+ ) => {
15
+ const nodes = useGraphStore((state) => state.nodes);
16
+
17
+ const buildCategorizeToOptions = useCallback(
18
+ (toList: string[]) => {
19
+ const excludedNodes: Operator[] = ExcludedNodesMap[operatorName] ?? [];
20
+ return nodes
21
+ .filter(
22
+ (x) =>
23
+ excludedNodes.every((y) => y !== x.data.label) &&
24
+ x.id !== selfId &&
25
+ !toList.some((y) => y === x.id), // filter out selected values ​​in other to fields from the current drop-down box options
26
+ )
27
+ .map((x) => ({ label: x.data.name, value: x.id }));
28
+ },
29
+ [nodes, operatorName, selfId],
30
+ );
31
+
32
+ return buildCategorizeToOptions;
33
+ };
34
+
35
+ export const useHandleFormSelectChange = (nodeId?: string) => {
36
+ const { addEdge, deleteEdgeBySourceAndSourceHandle } = useGraphStore(
37
+ (state) => state,
38
+ );
39
+ const handleSelectChange = useCallback(
40
+ (name?: string) => (value?: string) => {
41
+ if (nodeId && name) {
42
+ if (value) {
43
+ addEdge({
44
+ source: nodeId,
45
+ target: value,
46
+ sourceHandle: name,
47
+ targetHandle: null,
48
+ });
49
+ } else {
50
+ // clear selected value
51
+ deleteEdgeBySourceAndSourceHandle({
52
+ source: nodeId,
53
+ sourceHandle: name,
54
+ });
55
+ }
56
+ }
57
+ },
58
+ [addEdge, nodeId, deleteEdgeBySourceAndSourceHandle],
59
+ );
60
+
61
+ return { handleSelectChange };
62
+ };
web/src/pages/flow/interface.ts CHANGED
@@ -53,6 +53,11 @@ export interface ICategorizeForm extends IGenerateForm {
53
  category_description: ICategorizeItemResult;
54
  }
55
 
 
 
 
 
 
56
  export type NodeData = {
57
  label: string; // operator type
58
  name: string; // operator name
 
53
  category_description: ICategorizeItemResult;
54
  }
55
 
56
+ export interface IRelevantForm extends IGenerateForm {
57
+ yes: string;
58
+ no: string;
59
+ }
60
+
61
  export type NodeData = {
62
  label: string; // operator type
63
  name: string; // operator name
web/src/pages/flow/relevant-form/hooks.ts ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback, useEffect } from 'react';
2
+ import { Edge } from 'reactflow';
3
+ import { IOperatorForm } from '../interface';
4
+ import useGraphStore from '../store';
5
+
6
+ export const useBuildRelevantOptions = () => {
7
+ const nodes = useGraphStore((state) => state.nodes);
8
+
9
+ const buildRelevantOptions = useCallback(
10
+ (toList: string[]) => {
11
+ return nodes
12
+ .filter(
13
+ (x) => !toList.some((y) => y === x.id), // filter out selected values ​​in other to fields from the current drop-down box options
14
+ )
15
+ .map((x) => ({ label: x.data.name, value: x.id }));
16
+ },
17
+ [nodes],
18
+ );
19
+
20
+ return buildRelevantOptions;
21
+ };
22
+
23
+ const getTargetOfEdge = (edges: Edge[], sourceHandle: string) =>
24
+ edges.find((x) => x.sourceHandle === sourceHandle)?.target;
25
+
26
+ /**
27
+ * monitor changes in the connection and synchronize the target to the yes and no fields of the form
28
+ * similar to the categorize-form's useHandleFormValuesChange method
29
+ * @param param0
30
+ */
31
+ export const useWatchConnectionChanges = ({ nodeId, form }: IOperatorForm) => {
32
+ const edges = useGraphStore((state) => state.edges);
33
+
34
+ const watchConnectionChanges = useCallback(() => {
35
+ const edgeList = edges.filter((x) => x.source === nodeId);
36
+ const yes = getTargetOfEdge(edgeList, 'yes');
37
+ const no = getTargetOfEdge(edgeList, 'no');
38
+ form?.setFieldsValue({ yes, no });
39
+ }, [edges, nodeId, form]);
40
+
41
+ useEffect(() => {
42
+ watchConnectionChanges();
43
+ }, [watchConnectionChanges]);
44
+ };
web/src/pages/flow/relevant-form/index.tsx CHANGED
@@ -1,12 +1,24 @@
1
  import LLMSelect from '@/components/llm-select';
2
  import { useTranslate } from '@/hooks/commonHooks';
3
- import { Form } from 'antd';
 
 
 
 
 
4
  import { useSetLlmSetting } from '../hooks';
5
  import { IOperatorForm } from '../interface';
 
6
 
7
- const RelevantForm = ({ onValuesChange, form }: IOperatorForm) => {
8
- const { t } = useTranslate('chat');
9
  useSetLlmSetting(form);
 
 
 
 
 
 
10
 
11
  return (
12
  <Form
@@ -26,6 +38,20 @@ const RelevantForm = ({ onValuesChange, form }: IOperatorForm) => {
26
  >
27
  <LLMSelect></LLMSelect>
28
  </Form.Item>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  </Form>
30
  );
31
  };
 
1
  import LLMSelect from '@/components/llm-select';
2
  import { useTranslate } from '@/hooks/commonHooks';
3
+ import { Form, Select } from 'antd';
4
+ import { Operator } from '../constant';
5
+ import {
6
+ useBuildFormSelectOptions,
7
+ useHandleFormSelectChange,
8
+ } from '../form-hooks';
9
  import { useSetLlmSetting } from '../hooks';
10
  import { IOperatorForm } from '../interface';
11
+ import { useWatchConnectionChanges } from './hooks';
12
 
13
+ const RelevantForm = ({ onValuesChange, form, node }: IOperatorForm) => {
14
+ const { t } = useTranslate('flow');
15
  useSetLlmSetting(form);
16
+ const buildRelevantOptions = useBuildFormSelectOptions(
17
+ Operator.Relevant,
18
+ node?.id,
19
+ );
20
+ useWatchConnectionChanges({ nodeId: node?.id, form });
21
+ const { handleSelectChange } = useHandleFormSelectChange(node?.id);
22
 
23
  return (
24
  <Form
 
38
  >
39
  <LLMSelect></LLMSelect>
40
  </Form.Item>
41
+ <Form.Item label={t('yes')} name={'yes'}>
42
+ <Select
43
+ allowClear
44
+ options={buildRelevantOptions([form?.getFieldValue('no')])}
45
+ onChange={handleSelectChange('yes')}
46
+ />
47
+ </Form.Item>
48
+ <Form.Item label={t('no')} name={'no'}>
49
+ <Select
50
+ allowClear
51
+ options={buildRelevantOptions([form?.getFieldValue('yes')])}
52
+ onChange={handleSelectChange('no')}
53
+ />
54
+ </Form.Item>
55
  </Form>
56
  );
57
  };
web/src/pages/flow/store.ts CHANGED
@@ -107,9 +107,15 @@ const useGraphStore = create<RFState>()(
107
  return get().edges.find((x) => x.id === id);
108
  },
109
  deletePreviousEdgeOfClassificationNode: (connection: Connection) => {
110
- // Delete the edge on the classification node anchor when the anchor is connected to other nodes
111
  const { edges, getOperatorTypeFromId } = get();
112
- if (getOperatorTypeFromId(connection.source) === Operator.Categorize) {
 
 
 
 
 
 
113
  const previousEdge = edges.find(
114
  (x) =>
115
  x.source === connection.source &&
 
107
  return get().edges.find((x) => x.id === id);
108
  },
109
  deletePreviousEdgeOfClassificationNode: (connection: Connection) => {
110
+ // Delete the edge on the classification node or relevant node anchor when the anchor is connected to other nodes
111
  const { edges, getOperatorTypeFromId } = get();
112
+ // the node containing the anchor
113
+ const anchoredNodes = [Operator.Categorize, Operator.Relevant];
114
+ if (
115
+ anchoredNodes.some(
116
+ (x) => x === getOperatorTypeFromId(connection.source),
117
+ )
118
+ ) {
119
  const previousEdge = edges.find(
120
  (x) =>
121
  x.source === connection.source &&