diff --git a/web/src/pages/flow/canvas/node/categorize-handle.tsx b/web/src/pages/flow/canvas/node/categorize-handle.tsx
new file mode 100644
index 000000000..2d4329734
--- /dev/null
+++ b/web/src/pages/flow/canvas/node/categorize-handle.tsx
@@ -0,0 +1,39 @@
+import { Handle, Position } from 'reactflow';
+// import { v4 as uuid } from 'uuid';
+
+import styles from './index.less';
+
+const DEFAULT_HANDLE_STYLE = {
+ width: 6,
+ height: 6,
+ bottom: -5,
+ fontSize: 8,
+};
+
+interface IProps {
+ top: number;
+ right: number;
+ text: string;
+ idx: number;
+}
+
+const CategorizeHandle = ({ top, right, text, idx }: IProps) => {
+ return (
+
+ {text}
+
+ );
+};
+
+export default CategorizeHandle;
diff --git a/web/src/pages/flow/canvas/node/index.less b/web/src/pages/flow/canvas/node/index.less
index bd42fd3ad..ffba5c07f 100644
--- a/web/src/pages/flow/canvas/node/index.less
+++ b/web/src/pages/flow/canvas/node/index.less
@@ -37,6 +37,12 @@
-3px 0 6px -4px rgba(0, 0, 0, 0.12),
-6px 0 16px 6px rgba(0, 0, 0, 0.05);
}
+ .categorizeAnchorPointText {
+ position: absolute;
+ top: -4px;
+ left: 8px;
+ white-space: nowrap;
+ }
}
.selectedNode {
border: 1px solid rgb(59, 118, 244);
diff --git a/web/src/pages/flow/canvas/node/index.tsx b/web/src/pages/flow/canvas/node/index.tsx
index 41d121166..5d0fc1bda 100644
--- a/web/src/pages/flow/canvas/node/index.tsx
+++ b/web/src/pages/flow/canvas/node/index.tsx
@@ -4,12 +4,14 @@ import { Handle, NodeProps, Position } from 'reactflow';
import OperateDropdown from '@/components/operate-dropdown';
import { CopyOutlined } from '@ant-design/icons';
import { Flex, MenuProps, Space } from 'antd';
+import get from 'lodash/get';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-import { Operator, operatorMap } from '../../constant';
+import { CategorizeAnchorPointPositions, Operator } from '../../constant';
import { NodeData } from '../../interface';
import OperatorIcon from '../../operator-icon';
import useGraphStore from '../../store';
+import CategorizeHandle from './categorize-handle';
import styles from './index.less';
export function RagNode({
@@ -30,7 +32,8 @@ export function RagNode({
duplicateNodeById(id);
}, [id, duplicateNodeById]);
- const description = operatorMap[data.label as Operator].description;
+ const isCategorize = data.label === Operator.Categorize;
+ const categoryData = get(data, 'form.category_description') ?? {};
const items: MenuProps['items'] = [
{
@@ -57,9 +60,7 @@ export function RagNode({
position={Position.Left}
isConnectable={isConnectable}
className={styles.handle}
- >
- {/* */}
-
+ >
- {/* */}
-
+ >
+ {isCategorize &&
+ Object.keys(categoryData).map((x, idx) => (
+
+ ))}
- {/* {data.label} */}
- {/* {id}
*/}
- {/*
-
- {description}
-
-
*/}
+
diff --git a/web/src/pages/flow/categorize-form/dynamic-categorize.tsx b/web/src/pages/flow/categorize-form/dynamic-categorize.tsx
index 3bba4f41f..d02a0bce8 100644
--- a/web/src/pages/flow/categorize-form/dynamic-categorize.tsx
+++ b/web/src/pages/flow/categorize-form/dynamic-categorize.tsx
@@ -1,58 +1,78 @@
import { CloseOutlined } from '@ant-design/icons';
import { Button, Card, Form, Input, Select, Typography } from 'antd';
-import { useBuildCategorizeToOptions } from './hooks';
+import { useBuildCategorizeToOptions, useHandleToSelectChange } from './hooks';
-const DynamicCategorize = () => {
+interface IProps {
+ nodeId?: string;
+}
+
+const DynamicCategorize = ({ nodeId }: IProps) => {
const form = Form.useFormInstance();
const options = useBuildCategorizeToOptions();
+ const { handleSelectChange } = useHandleToSelectChange(
+ options.map((x) => x.value),
+ nodeId,
+ );
return (
<>
- {(fields, { add, remove }) => (
-
+ );
+ }}
diff --git a/web/src/pages/flow/categorize-form/hooks.ts b/web/src/pages/flow/categorize-form/hooks.ts
index 615ea8ce0..cac5a5236 100644
--- a/web/src/pages/flow/categorize-form/hooks.ts
+++ b/web/src/pages/flow/categorize-form/hooks.ts
@@ -1,6 +1,6 @@
import get from 'lodash/get';
import omit from 'lodash/omit';
-import { useCallback, useEffect } from 'react';
+import { useCallback, useEffect, useRef } from 'react';
import { Operator } from '../constant';
import {
ICategorizeItem,
@@ -72,6 +72,7 @@ export const useHandleFormValuesChange = ({
}: IOperatorForm) => {
const handleValuesChange = useCallback(
(changedValues: any, values: any) => {
+ console.info(changedValues, values);
onValuesChange?.(changedValues, {
...omit(values, 'items'),
category_description: buildCategorizeObjectFromList(values.items),
@@ -90,3 +91,38 @@ export const useHandleFormValuesChange = ({
return { handleValuesChange };
};
+
+export const useHandleToSelectChange = (
+ opstionIds: string[],
+ nodeId?: string,
+) => {
+ // const [previousTarget, setPreviousTarget] = useState('');
+ const previousTarget = useRef('');
+ const { addEdge, deleteEdgeBySourceAndTarget } = useGraphStore(
+ (state) => state,
+ );
+ const handleSelectChange = useCallback(
+ (value?: string) => {
+ if (nodeId) {
+ if (previousTarget.current) {
+ // delete previous edge
+ deleteEdgeBySourceAndTarget(nodeId, previousTarget.current);
+ }
+ if (value) {
+ addEdge({
+ source: nodeId,
+ target: value,
+ sourceHandle: 'b',
+ targetHandle: 'd',
+ });
+ } else {
+ // if the value is empty, delete the edges between the current node and all nodes in the drop-down box.
+ }
+ previousTarget.current = value;
+ }
+ },
+ [addEdge, nodeId, deleteEdgeBySourceAndTarget],
+ );
+
+ return { handleSelectChange };
+};
diff --git a/web/src/pages/flow/categorize-form/index.tsx b/web/src/pages/flow/categorize-form/index.tsx
index 1526e5a00..5f1192a69 100644
--- a/web/src/pages/flow/categorize-form/index.tsx
+++ b/web/src/pages/flow/categorize-form/index.tsx
@@ -32,7 +32,7 @@ const CategorizeForm = ({ form, onValuesChange, node }: IOperatorForm) => {
>
-
+
);
};
diff --git a/web/src/pages/flow/constant.tsx b/web/src/pages/flow/constant.tsx
index 866eb4ec8..fc10b1345 100644
--- a/web/src/pages/flow/constant.tsx
+++ b/web/src/pages/flow/constant.tsx
@@ -82,3 +82,18 @@ export const initialFormValuesMap = {
[Operator.Answer]: {},
[Operator.Categorize]: {},
};
+
+export const CategorizeAnchorPointPositions = [
+ { top: 1, right: 34 },
+ { top: 8, right: 18 },
+ { top: 15, right: 10 },
+ { top: 24, right: 4 },
+ { top: 31, right: 1 },
+ { top: 38, right: -2 },
+ { top: 62, right: -2 }, //bottom
+ { top: 71, right: 1 },
+ { top: 79, right: 6 },
+ { top: 86, right: 12 },
+ { top: 91, right: 20 },
+ { top: 98, right: 34 },
+];
diff --git a/web/src/pages/flow/store.ts b/web/src/pages/flow/store.ts
index dac10f2cc..459d60a0d 100644
--- a/web/src/pages/flow/store.ts
+++ b/web/src/pages/flow/store.ts
@@ -34,10 +34,12 @@ export type RFState = {
onSelectionChange: OnSelectionChangeFunc;
addNode: (nodes: Node) => void;
getNode: (id: string) => Node | undefined;
+ addEdge: (connection: Connection) => void;
duplicateNode: (id: string) => void;
deleteEdge: () => void;
deleteEdgeById: (id: string) => void;
deleteNodeById: (id: string) => void;
+ deleteEdgeBySourceAndTarget: (source: string, target: string) => void;
findNodeByName: (operatorName: Operator) => Node | undefined;
findNodeById: (id: string) => Node | undefined;
};
@@ -83,6 +85,14 @@ const useGraphStore = create()(
getNode: (id: string) => {
return get().nodes.find((x) => x.id === id);
},
+ addEdge: (connection: Connection) => {
+ set({
+ edges: addEdge(connection, get().edges),
+ });
+ },
+ // addOnlyOneEdgeBetweenTwoNodes: (connection: Connection) => {
+
+ // },
duplicateNode: (id: string) => {
const { getNode, addNode } = get();
const node = getNode(id);
@@ -114,6 +124,14 @@ const useGraphStore = create()(
edges: edges.filter((edge) => edge.id !== id),
});
},
+ deleteEdgeBySourceAndTarget: (source: string, target: string) => {
+ const { edges } = get();
+ set({
+ edges: edges.filter(
+ (edge) => edge.target !== target && edge.source !== source,
+ ),
+ });
+ },
deleteNodeById: (id: string) => {
const { nodes, edges } = get();
set({