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}
+
+
+
+
+
+ );
+}
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 &&