diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 6aeb4baa8..c8171190d 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -558,6 +558,8 @@ The above is the content you need to summarize.`, addField: 'Add field', loop: 'Loop', createFlow: 'Create a workflow', + yes: 'Yes', + no: 'No', }, footer: { profile: 'All rights reserved @ React', diff --git a/web/src/pages/flow/canvas/index.tsx b/web/src/pages/flow/canvas/index.tsx index e2ad47e9a..08ab72d2b 100644 --- a/web/src/pages/flow/canvas/index.tsx +++ b/web/src/pages/flow/canvas/index.tsx @@ -23,11 +23,13 @@ import ChatDrawer from '../chat/drawer'; import styles from './index.less'; import { BeginNode } from './node/begin-node'; import { CategorizeNode } from './node/categorize-node'; +import { RelevantNode } from './node/relevant-node'; const nodeTypes = { ragNode: RagNode, categorizeNode: CategorizeNode, beginNode: BeginNode, + relevantNode: RelevantNode, }; const edgeTypes = { diff --git a/web/src/pages/flow/canvas/node/categorize-handle.tsx b/web/src/pages/flow/canvas/node/categorize-handle.tsx index 24dd7faed..987236bca 100644 --- a/web/src/pages/flow/canvas/node/categorize-handle.tsx +++ b/web/src/pages/flow/canvas/node/categorize-handle.tsx @@ -14,7 +14,7 @@ interface IProps { top: number; right: number; text: string; - idx: number; + idx?: number; } const CategorizeHandle = ({ top, right, text, idx }: IProps) => { @@ -30,6 +30,7 @@ const CategorizeHandle = ({ top, right, text, idx }: IProps) => { top: `${top}%`, right: `${right}%`, background: 'red', + color: 'black', }} > {text} diff --git a/web/src/pages/flow/canvas/node/index.tsx b/web/src/pages/flow/canvas/node/index.tsx index 9ab5e48c2..94bf1d731 100644 --- a/web/src/pages/flow/canvas/node/index.tsx +++ b/web/src/pages/flow/canvas/node/index.tsx @@ -1,16 +1,10 @@ import { Flex } from 'antd'; import classNames from 'classnames'; -import get from 'lodash/get'; import pick from 'lodash/pick'; import { Handle, NodeProps, Position } from 'reactflow'; -import { - CategorizeAnchorPointPositions, - Operator, - operatorMap, -} from '../../constant'; +import { Operator, operatorMap } from '../../constant'; import { NodeData } from '../../interface'; import OperatorIcon from '../../operator-icon'; -import CategorizeHandle from './categorize-handle'; import NodeDropdown from './dropdown'; import styles from './index.less'; @@ -20,8 +14,6 @@ export function RagNode({ isConnectable = true, selected, }: NodeProps) { - const isCategorize = data.label === Operator.Categorize; - const categoryData = get(data, 'form.category_description') ?? {}; const style = operatorMap[data.label as Operator]; return ( @@ -47,16 +39,6 @@ export function RagNode({ id="b" > - {isCategorize && - Object.keys(categoryData).map((x, idx) => ( - - ))} ) { + const style = operatorMap[data.label as Operator]; + + return ( +
+ + + + + + + + + {data.label} + + + +
+
{data.name}
+
+
+ ); +} diff --git a/web/src/pages/flow/categorize-form/dynamic-categorize.tsx b/web/src/pages/flow/categorize-form/dynamic-categorize.tsx index 37ef93129..84eb94250 100644 --- a/web/src/pages/flow/categorize-form/dynamic-categorize.tsx +++ b/web/src/pages/flow/categorize-form/dynamic-categorize.tsx @@ -2,8 +2,12 @@ import { useTranslate } from '@/hooks/commonHooks'; import { CloseOutlined } from '@ant-design/icons'; import { Button, Card, Form, Input, Select, Typography } from 'antd'; import { useUpdateNodeInternals } from 'reactflow'; +import { Operator } from '../constant'; +import { + useBuildFormSelectOptions, + useHandleFormSelectChange, +} from '../form-hooks'; import { ICategorizeItem } from '../interface'; -import { useBuildCategorizeToOptions, useHandleToSelectChange } from './hooks'; interface IProps { nodeId?: string; @@ -12,8 +16,11 @@ interface IProps { const DynamicCategorize = ({ nodeId }: IProps) => { const updateNodeInternals = useUpdateNodeInternals(); const form = Form.useFormInstance(); - const buildCategorizeToOptions = useBuildCategorizeToOptions(); - const { handleSelectChange } = useHandleToSelectChange(nodeId); + const buildCategorizeToOptions = useBuildFormSelectOptions( + Operator.Categorize, + nodeId, + ); + const { handleSelectChange } = useHandleFormSelectChange(nodeId); const { t } = useTranslate('flow'); return ( diff --git a/web/src/pages/flow/categorize-form/hooks.ts b/web/src/pages/flow/categorize-form/hooks.ts index cd13fdc4b..b986fd7ba 100644 --- a/web/src/pages/flow/categorize-form/hooks.ts +++ b/web/src/pages/flow/categorize-form/hooks.ts @@ -2,7 +2,6 @@ import get from 'lodash/get'; import omit from 'lodash/omit'; import { useCallback, useEffect } from 'react'; import { Edge, Node } from 'reactflow'; -import { Operator } from '../constant'; import { ICategorizeItem, ICategorizeItemResult, @@ -11,28 +10,6 @@ import { } from '../interface'; import useGraphStore from '../store'; -// exclude some nodes downstream of the classification node -const excludedNodes = [Operator.Categorize, Operator.Answer, Operator.Begin]; - -export const useBuildCategorizeToOptions = () => { - const nodes = useGraphStore((state) => state.nodes); - - const buildCategorizeToOptions = useCallback( - (toList: string[]) => { - return nodes - .filter( - (x) => - excludedNodes.every((y) => y !== x.data.label) && - !toList.some((y) => y === x.id), // filter out selected values ​​in other to fields from the current drop-down box options - ) - .map((x) => ({ label: x.data.name, value: x.id })); - }, - [nodes], - ); - - return buildCategorizeToOptions; -}; - /** * convert the following object into a list * @@ -119,32 +96,3 @@ export const useHandleFormValuesChange = ({ return { handleValuesChange }; }; - -export const useHandleToSelectChange = (nodeId?: string) => { - const { addEdge, deleteEdgeBySourceAndSourceHandle } = useGraphStore( - (state) => state, - ); - const handleSelectChange = useCallback( - (name?: string) => (value?: string) => { - if (nodeId && name) { - if (value) { - addEdge({ - source: nodeId, - target: value, - sourceHandle: name, - targetHandle: null, - }); - } else { - // clear selected value - deleteEdgeBySourceAndSourceHandle({ - source: nodeId, - sourceHandle: name, - }); - } - } - }, - [addEdge, nodeId, deleteEdgeBySourceAndSourceHandle], - ); - - return { handleSelectChange }; -}; diff --git a/web/src/pages/flow/constant.tsx b/web/src/pages/flow/constant.tsx index b95f6aebb..53a32dcee 100644 --- a/web/src/pages/flow/constant.tsx +++ b/web/src/pages/flow/constant.tsx @@ -67,7 +67,12 @@ export const operatorMap = { }, [Operator.Relevant]: { description: 'BranchesOutlined description', - backgroundColor: 'white', + backgroundColor: '#9fd94d', + color: 'white', + width: 70, + height: 70, + fontSize: 12, + iconFontSize: 16, }, [Operator.RewriteQuestion]: { description: 'RewriteQuestion description', @@ -136,6 +141,7 @@ export const initialFormValuesMap = { [Operator.Generate]: initialGenerateValues, [Operator.Answer]: {}, [Operator.Categorize]: {}, + [Operator.Relevant]: {}, }; export const CategorizeAnchorPointPositions = [ @@ -173,6 +179,6 @@ export const NodeMap = { [Operator.Generate]: 'ragNode', [Operator.Answer]: 'ragNode', [Operator.Message]: 'ragNode', - [Operator.Relevant]: 'ragNode', + [Operator.Relevant]: 'relevantNode', [Operator.RewriteQuestion]: 'ragNode', }; diff --git a/web/src/pages/flow/form-hooks.ts b/web/src/pages/flow/form-hooks.ts new file mode 100644 index 000000000..c77a41ece --- /dev/null +++ b/web/src/pages/flow/form-hooks.ts @@ -0,0 +1,62 @@ +import { useCallback } from 'react'; +import { Operator } from './constant'; +import useGraphStore from './store'; + +const ExcludedNodesMap = { + // exclude some nodes downstream of the classification node + [Operator.Categorize]: [Operator.Categorize, Operator.Answer, Operator.Begin], + [Operator.Relevant]: [Operator.Begin], +}; + +export const useBuildFormSelectOptions = ( + operatorName: Operator, + selfId?: string, // exclude the current node +) => { + const nodes = useGraphStore((state) => state.nodes); + + const buildCategorizeToOptions = useCallback( + (toList: string[]) => { + const excludedNodes: Operator[] = ExcludedNodesMap[operatorName] ?? []; + return nodes + .filter( + (x) => + excludedNodes.every((y) => y !== x.data.label) && + x.id !== selfId && + !toList.some((y) => y === x.id), // filter out selected values ​​in other to fields from the current drop-down box options + ) + .map((x) => ({ label: x.data.name, value: x.id })); + }, + [nodes, operatorName, selfId], + ); + + return buildCategorizeToOptions; +}; + +export const useHandleFormSelectChange = (nodeId?: string) => { + const { addEdge, deleteEdgeBySourceAndSourceHandle } = useGraphStore( + (state) => state, + ); + const handleSelectChange = useCallback( + (name?: string) => (value?: string) => { + if (nodeId && name) { + if (value) { + addEdge({ + source: nodeId, + target: value, + sourceHandle: name, + targetHandle: null, + }); + } else { + // clear selected value + deleteEdgeBySourceAndSourceHandle({ + source: nodeId, + sourceHandle: name, + }); + } + } + }, + [addEdge, nodeId, deleteEdgeBySourceAndSourceHandle], + ); + + return { handleSelectChange }; +}; diff --git a/web/src/pages/flow/interface.ts b/web/src/pages/flow/interface.ts index 2dd27f838..64e187495 100644 --- a/web/src/pages/flow/interface.ts +++ b/web/src/pages/flow/interface.ts @@ -53,6 +53,11 @@ export interface ICategorizeForm extends IGenerateForm { category_description: ICategorizeItemResult; } +export interface IRelevantForm extends IGenerateForm { + yes: string; + no: string; +} + export type NodeData = { label: string; // operator type name: string; // operator name diff --git a/web/src/pages/flow/relevant-form/hooks.ts b/web/src/pages/flow/relevant-form/hooks.ts new file mode 100644 index 000000000..befadbbee --- /dev/null +++ b/web/src/pages/flow/relevant-form/hooks.ts @@ -0,0 +1,44 @@ +import { useCallback, useEffect } from 'react'; +import { Edge } from 'reactflow'; +import { IOperatorForm } from '../interface'; +import useGraphStore from '../store'; + +export const useBuildRelevantOptions = () => { + const nodes = useGraphStore((state) => state.nodes); + + const buildRelevantOptions = useCallback( + (toList: string[]) => { + return nodes + .filter( + (x) => !toList.some((y) => y === x.id), // filter out selected values ​​in other to fields from the current drop-down box options + ) + .map((x) => ({ label: x.data.name, value: x.id })); + }, + [nodes], + ); + + return buildRelevantOptions; +}; + +const getTargetOfEdge = (edges: Edge[], sourceHandle: string) => + edges.find((x) => x.sourceHandle === sourceHandle)?.target; + +/** + * monitor changes in the connection and synchronize the target to the yes and no fields of the form + * similar to the categorize-form's useHandleFormValuesChange method + * @param param0 + */ +export const useWatchConnectionChanges = ({ nodeId, form }: IOperatorForm) => { + const edges = useGraphStore((state) => state.edges); + + const watchConnectionChanges = useCallback(() => { + const edgeList = edges.filter((x) => x.source === nodeId); + const yes = getTargetOfEdge(edgeList, 'yes'); + const no = getTargetOfEdge(edgeList, 'no'); + form?.setFieldsValue({ yes, no }); + }, [edges, nodeId, form]); + + useEffect(() => { + watchConnectionChanges(); + }, [watchConnectionChanges]); +}; diff --git a/web/src/pages/flow/relevant-form/index.tsx b/web/src/pages/flow/relevant-form/index.tsx index 5bd99290e..c22991b68 100644 --- a/web/src/pages/flow/relevant-form/index.tsx +++ b/web/src/pages/flow/relevant-form/index.tsx @@ -1,12 +1,24 @@ import LLMSelect from '@/components/llm-select'; import { useTranslate } from '@/hooks/commonHooks'; -import { Form } from 'antd'; +import { Form, Select } from 'antd'; +import { Operator } from '../constant'; +import { + useBuildFormSelectOptions, + useHandleFormSelectChange, +} from '../form-hooks'; import { useSetLlmSetting } from '../hooks'; import { IOperatorForm } from '../interface'; +import { useWatchConnectionChanges } from './hooks'; -const RelevantForm = ({ onValuesChange, form }: IOperatorForm) => { - const { t } = useTranslate('chat'); +const RelevantForm = ({ onValuesChange, form, node }: IOperatorForm) => { + const { t } = useTranslate('flow'); useSetLlmSetting(form); + const buildRelevantOptions = useBuildFormSelectOptions( + Operator.Relevant, + node?.id, + ); + useWatchConnectionChanges({ nodeId: node?.id, form }); + const { handleSelectChange } = useHandleFormSelectChange(node?.id); return (
{ > + + +
); }; diff --git a/web/src/pages/flow/store.ts b/web/src/pages/flow/store.ts index 809221d8f..fa208a588 100644 --- a/web/src/pages/flow/store.ts +++ b/web/src/pages/flow/store.ts @@ -107,9 +107,15 @@ const useGraphStore = create()( return get().edges.find((x) => x.id === id); }, deletePreviousEdgeOfClassificationNode: (connection: Connection) => { - // Delete the edge on the classification node anchor when the anchor is connected to other nodes + // Delete the edge on the classification node or relevant node anchor when the anchor is connected to other nodes const { edges, getOperatorTypeFromId } = get(); - if (getOperatorTypeFromId(connection.source) === Operator.Categorize) { + // the node containing the anchor + const anchoredNodes = [Operator.Categorize, Operator.Relevant]; + if ( + anchoredNodes.some( + (x) => x === getOperatorTypeFromId(connection.source), + ) + ) { const previousEdge = edges.find( (x) => x.source === connection.source &&