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 +2 -0
- web/src/pages/flow/canvas/index.tsx +2 -0
- web/src/pages/flow/canvas/node/categorize-handle.tsx +2 -1
- web/src/pages/flow/canvas/node/index.tsx +1 -19
- web/src/pages/flow/canvas/node/relevant-node.tsx +64 -0
- web/src/pages/flow/categorize-form/dynamic-categorize.tsx +10 -3
- web/src/pages/flow/categorize-form/hooks.ts +0 -52
- web/src/pages/flow/constant.tsx +8 -2
- web/src/pages/flow/form-hooks.ts +62 -0
- web/src/pages/flow/interface.ts +5 -0
- web/src/pages/flow/relevant-form/hooks.ts +44 -0
- web/src/pages/flow/relevant-form/index.tsx +29 -3
- web/src/pages/flow/store.ts +8 -2
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
|
| 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 =
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
| 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: '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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]: '
|
| 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('
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 &&
|