diff --git a/web/package-lock.json b/web/package-lock.json index c027b24ec33..dfb57b1c4b6 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -35,6 +35,7 @@ "@tanstack/react-query-devtools": "^5.51.5", "@tanstack/react-table": "^8.20.5", "@uiw/react-markdown-preview": "^5.1.3", + "@xyflow/react": "^12.3.6", "ahooks": "^3.7.10", "antd": "^5.12.7", "axios": "^1.6.3", @@ -68,7 +69,7 @@ "react-string-replace": "^1.1.1", "react-syntax-highlighter": "^15.5.0", "react18-json-view": "^0.2.8", - "reactflow": "^11.11.2", + "@xyflow/react": "^11.11.2", "recharts": "^2.12.4", "rehype-katex": "^7.0.1", "rehype-raw": "^7.0.0", @@ -5469,12 +5470,12 @@ "node": ">=12.0.0" } }, - "node_modules/@reactflow/background": { + "node_modules/@@xyflow/react/background": { "version": "11.3.12", - "resolved": "https://registry.npmmirror.com/@reactflow/background/-/background-11.3.12.tgz", + "resolved": "https://registry.npmmirror.com/@@xyflow/react/background/-/background-11.3.12.tgz", "integrity": "sha512-jBuWVb43JQy5h4WOS7G0PU8voGTEJNA+qDmx8/jyBtrjbasTesLNfQvboTGjnQYYiJco6mw5vrtQItAJDNoIqw==", "dependencies": { - "@reactflow/core": "11.11.2", + "@@xyflow/react/core": "11.11.2", "classcat": "^5.0.3", "zustand": "^4.4.1" }, @@ -5483,12 +5484,12 @@ "react-dom": ">=17" } }, - "node_modules/@reactflow/controls": { + "node_modules/@@xyflow/react/controls": { "version": "11.2.12", - "resolved": "https://registry.npmmirror.com/@reactflow/controls/-/controls-11.2.12.tgz", + "resolved": "https://registry.npmmirror.com/@@xyflow/react/controls/-/controls-11.2.12.tgz", "integrity": "sha512-L9F3+avFRShoprdT+5oOijm5gVsz2rqWCXBzOAgD923L1XFGIspdiHLLf8IlPGsT+mfl0GxbptZhaEeEzl1e3g==", "dependencies": { - "@reactflow/core": "11.11.2", + "@@xyflow/react/core": "11.11.2", "classcat": "^5.0.3", "zustand": "^4.4.1" }, @@ -5497,9 +5498,9 @@ "react-dom": ">=17" } }, - "node_modules/@reactflow/core": { + "node_modules/@@xyflow/react/core": { "version": "11.11.2", - "resolved": "https://registry.npmmirror.com/@reactflow/core/-/core-11.11.2.tgz", + "resolved": "https://registry.npmmirror.com/@@xyflow/react/core/-/core-11.11.2.tgz", "integrity": "sha512-+GfgyskweL1PsgRSguUwfrT2eDotlFgaKfDLm7x0brdzzPJY2qbCzVetaxedaiJmIli3817iYbILvE9qLKwbRA==", "dependencies": { "@types/d3": "^7.4.0", @@ -5517,12 +5518,12 @@ "react-dom": ">=17" } }, - "node_modules/@reactflow/minimap": { + "node_modules/@@xyflow/react/minimap": { "version": "11.7.12", - "resolved": "https://registry.npmmirror.com/@reactflow/minimap/-/minimap-11.7.12.tgz", + "resolved": "https://registry.npmmirror.com/@@xyflow/react/minimap/-/minimap-11.7.12.tgz", "integrity": "sha512-SRDU77c2PCF54PV/MQfkz7VOW46q7V1LZNOQlXAp7dkNyAOI6R+tb9qBUtUJOvILB+TCN6pRfD9fQ+2T99bW3Q==", "dependencies": { - "@reactflow/core": "11.11.2", + "@@xyflow/react/core": "11.11.2", "@types/d3-selection": "^3.0.3", "@types/d3-zoom": "^3.0.1", "classcat": "^5.0.3", @@ -5535,12 +5536,12 @@ "react-dom": ">=17" } }, - "node_modules/@reactflow/node-resizer": { + "node_modules/@@xyflow/react/node-resizer": { "version": "2.2.12", - "resolved": "https://registry.npmmirror.com/@reactflow/node-resizer/-/node-resizer-2.2.12.tgz", + "resolved": "https://registry.npmmirror.com/@@xyflow/react/node-resizer/-/node-resizer-2.2.12.tgz", "integrity": "sha512-6LHJGuI1zHyRrZHw5gGlVLIWnvVxid9WIqw8FMFSg+oF2DuS3pAPwSoZwypy7W22/gDNl9eD1Dcl/OtFtDFQ+w==", "dependencies": { - "@reactflow/core": "11.11.2", + "@@xyflow/react/core": "11.11.2", "classcat": "^5.0.4", "d3-drag": "^3.0.0", "d3-selection": "^3.0.0", @@ -5551,12 +5552,12 @@ "react-dom": ">=17" } }, - "node_modules/@reactflow/node-toolbar": { + "node_modules/@@xyflow/react/node-toolbar": { "version": "1.3.12", - "resolved": "https://registry.npmmirror.com/@reactflow/node-toolbar/-/node-toolbar-1.3.12.tgz", + "resolved": "https://registry.npmmirror.com/@@xyflow/react/node-toolbar/-/node-toolbar-1.3.12.tgz", "integrity": "sha512-4kJRvNna/E3y2MZW9/80wTKwkhw4pLJiz3D5eQrD13XcmojSb1rArO9CiwyrI+rMvs5gn6NlCFB4iN1F+Q+lxQ==", "dependencies": { - "@reactflow/core": "11.11.2", + "@@xyflow/react/core": "11.11.2", "classcat": "^5.0.3", "zustand": "^4.4.1" }, @@ -8954,6 +8955,34 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "peer": true }, + "node_modules/@xyflow/react": { + "version": "12.3.6", + "resolved": "https://registry.npmmirror.com/@xyflow/react/-/react-12.3.6.tgz", + "integrity": "sha512-9GS+cz8hDZahpvTrVCmySAEgKUL8oN4b2q1DluHrKtkqhAMWfH2s7kblhbM4Y4Y4SUnH2lt4drXKZ/4/Lot/2Q==", + "dependencies": { + "@xyflow/system": "0.0.47", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.47", + "resolved": "https://registry.npmmirror.com/@xyflow/system/-/system-0.0.47.tgz", + "integrity": "sha512-aUXJPIvsCFxGX70ccRG8LPsR+A8ExYXfh/noYNpqn8udKerrLdSHxMG2VsvUrQ1PGex10fOpbJwFU4A+I/Xv8w==", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/3d-force-graph": { "version": "1.73.3", "resolved": "https://registry.npmmirror.com/3d-force-graph/-/3d-force-graph-1.73.3.tgz", @@ -25533,17 +25562,17 @@ "lodash": "^4.0.1" } }, - "node_modules/reactflow": { + "node_modules/@xyflow/react": { "version": "11.11.2", - "resolved": "https://registry.npmmirror.com/reactflow/-/reactflow-11.11.2.tgz", + "resolved": "https://registry.npmmirror.com/@xyflow/react/-/@xyflow/react-11.11.2.tgz", "integrity": "sha512-o1fT3stSdhzW+SedCGNSmEvZvULZygZIMLyW67NcWNZrgwx1wuJfzLg5fuQ0Nzf389wItumZX/zP3zdaPX7lEw==", "dependencies": { - "@reactflow/background": "11.3.12", - "@reactflow/controls": "11.2.12", - "@reactflow/core": "11.11.2", - "@reactflow/minimap": "11.7.12", - "@reactflow/node-resizer": "2.2.12", - "@reactflow/node-toolbar": "1.3.12" + "@@xyflow/react/background": "11.3.12", + "@@xyflow/react/controls": "11.2.12", + "@@xyflow/react/core": "11.11.2", + "@@xyflow/react/minimap": "11.7.12", + "@@xyflow/react/node-resizer": "2.2.12", + "@@xyflow/react/node-toolbar": "1.3.12" }, "peerDependencies": { "react": ">=17", diff --git a/web/package.json b/web/package.json index 6e82a12a497..36b8a28c7de 100644 --- a/web/package.json +++ b/web/package.json @@ -46,6 +46,7 @@ "@tanstack/react-query-devtools": "^5.51.5", "@tanstack/react-table": "^8.20.5", "@uiw/react-markdown-preview": "^5.1.3", + "@xyflow/react": "^11.11.2", "ahooks": "^3.7.10", "antd": "^5.12.7", "axios": "^1.6.3", @@ -79,7 +80,6 @@ "react-string-replace": "^1.1.1", "react-syntax-highlighter": "^15.5.0", "react18-json-view": "^0.2.8", - "reactflow": "^11.11.2", "recharts": "^2.12.4", "rehype-katex": "^7.0.1", "rehype-raw": "^7.0.0", diff --git a/web/src/interfaces/database/flow.ts b/web/src/interfaces/database/flow.ts index 2a81f92f157..9c3dfc203e8 100644 --- a/web/src/interfaces/database/flow.ts +++ b/web/src/interfaces/database/flow.ts @@ -1,4 +1,4 @@ -import { Edge, Node } from 'reactflow'; +import { Edge, Node } from '@xyflow/react'; import { IReference, Message } from './chat'; export type DSLComponents = Record; @@ -25,11 +25,6 @@ export interface IOperatorNode { params: Record; } -export interface IGraph { - nodes: Node[]; - edges: Edge[]; -} - export declare interface IFlow { avatar?: null | string; canvas_type: null; @@ -56,3 +51,119 @@ export interface IFlowTemplate { update_date: string; update_time: number; } + +export type ICategorizeItemResult = Record< + string, + Omit +>; + +export interface IGenerateForm { + max_tokens?: number; + temperature?: number; + top_p?: number; + presence_penalty?: number; + frequency_penalty?: number; + cite?: boolean; + prompt: number; + llm_id: string; + parameters: { key: string; component_id: string }; +} +export interface ICategorizeItem { + name: string; + description?: string; + examples?: string; + to?: string; + index: number; +} + +export interface ICategorizeForm extends IGenerateForm { + category_description: ICategorizeItemResult; +} + +export interface IRelevantForm extends IGenerateForm { + yes: string; + no: string; +} + +export interface ISwitchCondition { + items: ISwitchItem[]; + logical_operator: string; + to: string; +} + +export interface ISwitchItem { + cpn_id: string; + operator: string; + value: string; +} + +export interface ISwitchForm { + conditions: ISwitchCondition[]; + end_cpn_id: string; + no: string; +} + +export interface IBeginForm { + prologue?: string; +} + +export interface IRetrievalForm { + similarity_threshold?: number; + keywords_similarity_weight?: number; + top_n?: number; + top_k?: number; + rerank_id?: string; + empty_response?: string; + kb_ids: string[]; +} + +export type BaseNodeData = { + label: string; // operator type + name: string; // operator name + color?: string; + form?: TForm; +}; + +export type BaseNode = Node>; + +export type IBeginNode = BaseNode; +export type IRetrievalNode = BaseNode; +export type IGenerateNode = BaseNode; +export type ICategorizeNode = BaseNode; +export type ISwitchNode = BaseNode; +export type IRagNode = BaseNode; +export type IRelevantNode = BaseNode; +export type ILogicNode = BaseNode; +export type INoteNode = BaseNode; +export type IMessageNode = BaseNode; +export type IRewriteNode = BaseNode; +export type IInvokeNode = BaseNode; +export type ITemplateNode = BaseNode; +export type IEmailNode = BaseNode; +export type IIterationNode = BaseNode; +export type IIterationStartNode = BaseNode; +export type IKeywordNode = BaseNode; + +export type RAGFlowNodeType = + | IBeginNode + | IRetrievalNode + | IGenerateNode + | ICategorizeNode + | ISwitchNode + | IRagNode + | IRelevantNode + | ILogicNode + | INoteNode + | IMessageNode + | IRewriteNode + | IInvokeNode + | ITemplateNode + | IEmailNode + | IIterationNode + | IIterationStartNode + | IKeywordNode; + +export interface IGraph { + nodes: RAGFlowNodeType[]; + edges: Edge[]; +} diff --git a/web/src/pages/flow/canvas/context-menu/index.tsx b/web/src/pages/flow/canvas/context-menu/index.tsx index 47b809de938..6cb306af941 100644 --- a/web/src/pages/flow/canvas/context-menu/index.tsx +++ b/web/src/pages/flow/canvas/context-menu/index.tsx @@ -1,5 +1,5 @@ +import { NodeMouseHandler, useReactFlow } from '@xyflow/react'; import { useCallback, useRef, useState } from 'react'; -import { NodeMouseHandler, useReactFlow } from 'reactflow'; import styles from './index.less'; diff --git a/web/src/pages/flow/canvas/edge/index.tsx b/web/src/pages/flow/canvas/edge/index.tsx index 112ff1b308d..52f939b8d7a 100644 --- a/web/src/pages/flow/canvas/edge/index.tsx +++ b/web/src/pages/flow/canvas/edge/index.tsx @@ -3,7 +3,7 @@ import { EdgeLabelRenderer, EdgeProps, getBezierPath, -} from 'reactflow'; +} from '@xyflow/react'; import useGraphStore from '../../store'; import { useTheme } from '@/components/theme-provider'; diff --git a/web/src/pages/flow/canvas/index.tsx b/web/src/pages/flow/canvas/index.tsx index 6aa742c3c60..cbd107ad8b9 100644 --- a/web/src/pages/flow/canvas/index.tsx +++ b/web/src/pages/flow/canvas/index.tsx @@ -4,14 +4,16 @@ import { TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip'; -import { FolderInput, FolderOutput } from 'lucide-react'; -import ReactFlow, { +import { Background, ConnectionMode, ControlButton, Controls, -} from 'reactflow'; -import 'reactflow/dist/style.css'; + NodeTypes, + ReactFlow, +} from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import { FolderInput, FolderOutput } from 'lucide-react'; import ChatDrawer from '../chat/drawer'; import FormDrawer from '../flow-drawer'; import { @@ -20,6 +22,7 @@ import { useValidateConnection, useWatchNodeFormDataChange, } from '../hooks'; +import { useBeforeDelete } from '../hooks/use-before-delete'; import { useHandleExportOrImportJsonFile } from '../hooks/use-export-json'; import { useShowDrawer } from '../hooks/use-show-drawer'; import JsonUploadModal from '../json-upload-modal'; @@ -43,7 +46,7 @@ import { RewriteNode } from './node/rewrite-node'; import { SwitchNode } from './node/switch-node'; import { TemplateNode } from './node/template-node'; -const nodeTypes = { +const nodeTypes: NodeTypes = { ragNode: RagNode, categorizeNode: CategorizeNode, beginNode: BeginNode, @@ -113,6 +116,8 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) { hideDrawer, }); + const { handleBeforeDelete } = useBeforeDelete(); + useWatchNodeFormDataChange(); return ( @@ -165,6 +170,7 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) { zIndex: 1001, // https://github.com/xyflow/xyflow/discussions/3498 }} deleteKeyCode={['Delete', 'Backspace']} + onBeforeDelete={handleBeforeDelete} > diff --git a/web/src/pages/flow/canvas/node/begin-node.tsx b/web/src/pages/flow/canvas/node/begin-node.tsx index 35a9560e4c9..83e36652b36 100644 --- a/web/src/pages/flow/canvas/node/begin-node.tsx +++ b/web/src/pages/flow/canvas/node/begin-node.tsx @@ -1,22 +1,23 @@ import { useTheme } from '@/components/theme-provider'; +import { IBeginNode } from '@/interfaces/database/flow'; +import { Handle, NodeProps, Position } from '@xyflow/react'; import { Flex } from 'antd'; import classNames from 'classnames'; import get from 'lodash/get'; import { useTranslation } from 'react-i18next'; -import { Handle, NodeProps, Position } from 'reactflow'; import { BeginQueryType, BeginQueryTypeIconMap, Operator, operatorMap, } from '../../constant'; -import { BeginQuery, NodeData } from '../../interface'; +import { BeginQuery } from '../../interface'; import OperatorIcon from '../../operator-icon'; import { RightHandleStyle } from './handle-icon'; import styles from './index.less'; // TODO: do not allow other nodes to connect to this node -export function BeginNode({ selected, data }: NodeProps) { +export function BeginNode({ selected, data }: NodeProps) { const { t } = useTranslation(); const query: BeginQuery[] = get(data, 'form.query', []); const { theme } = useTheme(); diff --git a/web/src/pages/flow/canvas/node/categorize-handle.tsx b/web/src/pages/flow/canvas/node/categorize-handle.tsx index 8c028825ce9..ce1fc3624ff 100644 --- a/web/src/pages/flow/canvas/node/categorize-handle.tsx +++ b/web/src/pages/flow/canvas/node/categorize-handle.tsx @@ -1,4 +1,4 @@ -import { Handle, Position } from 'reactflow'; +import { Handle, Position } from '@xyflow/react'; import React from 'react'; import styles from './index.less'; diff --git a/web/src/pages/flow/canvas/node/categorize-node.tsx b/web/src/pages/flow/canvas/node/categorize-node.tsx index f130cda28ca..18c3cdff0e9 100644 --- a/web/src/pages/flow/canvas/node/categorize-node.tsx +++ b/web/src/pages/flow/canvas/node/categorize-node.tsx @@ -1,16 +1,20 @@ import LLMLabel from '@/components/llm-select/llm-label'; import { useTheme } from '@/components/theme-provider'; +import { ICategorizeNode } from '@/interfaces/database/flow'; +import { Handle, NodeProps, Position } from '@xyflow/react'; import { Flex } from 'antd'; import classNames from 'classnames'; import { get } from 'lodash'; -import { Handle, NodeProps, Position } from 'reactflow'; -import { NodeData } from '../../interface'; import { RightHandleStyle } from './handle-icon'; import { useBuildCategorizeHandlePositions } from './hooks'; import styles from './index.less'; import NodeHeader from './node-header'; -export function CategorizeNode({ id, data, selected }: NodeProps) { +export function CategorizeNode({ + id, + data, + selected, +}: NodeProps) { const { positions } = useBuildCategorizeHandlePositions({ data, id }); const { theme } = useTheme(); return ( diff --git a/web/src/pages/flow/canvas/node/email-node.tsx b/web/src/pages/flow/canvas/node/email-node.tsx index 121719ceaf9..ae4af848c65 100644 --- a/web/src/pages/flow/canvas/node/email-node.tsx +++ b/web/src/pages/flow/canvas/node/email-node.tsx @@ -1,8 +1,8 @@ +import { IEmailNode } from '@/interfaces/database/flow'; +import { Handle, NodeProps, Position } from '@xyflow/react'; import { Flex } from 'antd'; import classNames from 'classnames'; import { useState } from 'react'; -import { Handle, NodeProps, Position } from 'reactflow'; -import { NodeData } from '../../interface'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; @@ -12,7 +12,7 @@ export function EmailNode({ data, isConnectable = true, selected, -}: NodeProps) { +}: NodeProps) { const [showDetails, setShowDetails] = useState(false); return ( diff --git a/web/src/pages/flow/canvas/node/generate-node.tsx b/web/src/pages/flow/canvas/node/generate-node.tsx index d79d1dc6cc4..01b4829fdba 100644 --- a/web/src/pages/flow/canvas/node/generate-node.tsx +++ b/web/src/pages/flow/canvas/node/generate-node.tsx @@ -1,11 +1,12 @@ import LLMLabel from '@/components/llm-select/llm-label'; import { useTheme } from '@/components/theme-provider'; +import { IGenerateNode } from '@/interfaces/database/flow'; +import { Handle, NodeProps, Position } from '@xyflow/react'; import { Flex } from 'antd'; import classNames from 'classnames'; import { get } from 'lodash'; -import { Handle, NodeProps, Position } from 'reactflow'; import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query'; -import { IGenerateParameter, NodeData } from '../../interface'; +import { IGenerateParameter } from '../../interface'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; @@ -15,7 +16,7 @@ export function GenerateNode({ data, isConnectable = true, selected, -}: NodeProps) { +}: NodeProps) { const parameters: IGenerateParameter[] = get(data, 'form.parameters', []); const getLabel = useGetComponentLabelByValue(id); const { theme } = useTheme(); diff --git a/web/src/pages/flow/canvas/node/handle-icon.tsx b/web/src/pages/flow/canvas/node/handle-icon.tsx index c71f05c3bf8..36c7f3634ec 100644 --- a/web/src/pages/flow/canvas/node/handle-icon.tsx +++ b/web/src/pages/flow/canvas/node/handle-icon.tsx @@ -10,11 +10,11 @@ export const HandleIcon = () => { }; export const RightHandleStyle: CSSProperties = { - right: -5, + right: 0, }; export const LeftHandleStyle: CSSProperties = { - left: -7, + left: 0, }; export default HandleIcon; diff --git a/web/src/pages/flow/canvas/node/hooks.ts b/web/src/pages/flow/canvas/node/hooks.ts index ce11dc6faf6..fbea8f1668b 100644 --- a/web/src/pages/flow/canvas/node/hooks.ts +++ b/web/src/pages/flow/canvas/node/hooks.ts @@ -1,12 +1,13 @@ +import { useUpdateNodeInternals } from '@xyflow/react'; import get from 'lodash/get'; import { useEffect, useMemo } from 'react'; -import { useUpdateNodeInternals } from 'reactflow'; import { SwitchElseTo } from '../../constant'; + import { ICategorizeItemResult, ISwitchCondition, - NodeData, -} from '../../interface'; + RAGFlowNodeType, +} from '@/interfaces/database/flow'; import { generateSwitchHandleText } from '../../utils'; export const useBuildCategorizeHandlePositions = ({ @@ -14,7 +15,7 @@ export const useBuildCategorizeHandlePositions = ({ id, }: { id: string; - data: NodeData; + data: RAGFlowNodeType['data']; }) => { const updateNodeInternals = useUpdateNodeInternals(); @@ -54,7 +55,7 @@ export const useBuildSwitchHandlePositions = ({ id, }: { id: string; - data: NodeData; + data: RAGFlowNodeType['data']; }) => { const updateNodeInternals = useUpdateNodeInternals(); diff --git a/web/src/pages/flow/canvas/node/index.tsx b/web/src/pages/flow/canvas/node/index.tsx index f549811e586..32191f5ccc7 100644 --- a/web/src/pages/flow/canvas/node/index.tsx +++ b/web/src/pages/flow/canvas/node/index.tsx @@ -1,7 +1,7 @@ import { useTheme } from '@/components/theme-provider'; +import { IRagNode } from '@/interfaces/database/flow'; +import { Handle, NodeProps, Position } from '@xyflow/react'; import classNames from 'classnames'; -import { Handle, NodeProps, Position } from 'reactflow'; -import { NodeData } from '../../interface'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; @@ -11,7 +11,7 @@ export function RagNode({ data, isConnectable = true, selected, -}: NodeProps) { +}: NodeProps) { const { theme } = useTheme(); return (
) { +}: NodeProps) { const { t } = useTranslation(); const { theme } = useTheme(); const url = get(data, 'form.url'); diff --git a/web/src/pages/flow/canvas/node/iteration-node.tsx b/web/src/pages/flow/canvas/node/iteration-node.tsx index 7524c6744d9..c15b4fc6c6a 100644 --- a/web/src/pages/flow/canvas/node/iteration-node.tsx +++ b/web/src/pages/flow/canvas/node/iteration-node.tsx @@ -1,8 +1,11 @@ import { useTheme } from '@/components/theme-provider'; +import { + IIterationNode, + IIterationStartNode, +} from '@/interfaces/database/flow'; import { cn } from '@/lib/utils'; +import { Handle, NodeProps, NodeResizeControl, Position } from '@xyflow/react'; import { ListRestart } from 'lucide-react'; -import { Handle, NodeProps, NodeResizeControl, Position } from 'reactflow'; -import { NodeData } from '../../interface'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; @@ -19,7 +22,11 @@ function ResizeIcon() { fill="none" strokeLinecap="round" strokeLinejoin="round" - style={{ position: 'absolute', right: 5, bottom: 5 }} + style={{ + position: 'absolute', + right: 5, + bottom: 5, + }} > @@ -33,6 +40,7 @@ function ResizeIcon() { const controlStyle = { background: 'transparent', border: 'none', + cursor: 'nwse-resize', }; export function IterationNode({ @@ -40,7 +48,7 @@ export function IterationNode({ data, isConnectable = true, selected, -}: NodeProps) { +}: NodeProps) { const { theme } = useTheme(); return ( @@ -93,7 +101,7 @@ export function IterationNode({ export function IterationStartNode({ isConnectable = true, selected, -}: NodeProps) { +}: NodeProps) { const { theme } = useTheme(); return ( diff --git a/web/src/pages/flow/canvas/node/keyword-node.tsx b/web/src/pages/flow/canvas/node/keyword-node.tsx index 40ae0e2908d..f607d431780 100644 --- a/web/src/pages/flow/canvas/node/keyword-node.tsx +++ b/web/src/pages/flow/canvas/node/keyword-node.tsx @@ -1,9 +1,9 @@ import LLMLabel from '@/components/llm-select/llm-label'; import { useTheme } from '@/components/theme-provider'; +import { IKeywordNode } from '@/interfaces/database/flow'; +import { Handle, NodeProps, Position } from '@xyflow/react'; import classNames from 'classnames'; import { get } from 'lodash'; -import { Handle, NodeProps, Position } from 'reactflow'; -import { NodeData } from '../../interface'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; @@ -13,7 +13,7 @@ export function KeywordNode({ data, isConnectable = true, selected, -}: NodeProps) { +}: NodeProps) { const { theme } = useTheme(); return (
) { +}: NodeProps) { const { theme } = useTheme(); return (
) { +}: NodeProps) { const messages: string[] = get(data, 'form.messages', []); const { theme } = useTheme(); return ( diff --git a/web/src/pages/flow/canvas/node/note-node.tsx b/web/src/pages/flow/canvas/node/note-node.tsx index 5ff140f0b2a..1917a81509e 100644 --- a/web/src/pages/flow/canvas/node/note-node.tsx +++ b/web/src/pages/flow/canvas/node/note-node.tsx @@ -1,11 +1,11 @@ +import { NodeProps, NodeResizeControl } from '@xyflow/react'; import { Flex, Form, Input } from 'antd'; import classNames from 'classnames'; -import { NodeProps, NodeResizeControl } from 'reactflow'; -import { NodeData } from '../../interface'; import NodeDropdown from './dropdown'; import SvgIcon from '@/components/svg-icon'; import { useTheme } from '@/components/theme-provider'; +import { INoteNode } from '@/interfaces/database/flow'; import { memo, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -21,7 +21,7 @@ const controlStyle = { border: 'none', }; -function NoteNode({ data, id }: NodeProps) { +function NoteNode({ data, id }: NodeProps) { const { t } = useTranslation(); const [form] = Form.useForm(); const { theme } = useTheme(); diff --git a/web/src/pages/flow/canvas/node/relevant-node.tsx b/web/src/pages/flow/canvas/node/relevant-node.tsx index 99b6d8d0059..acc098d69b1 100644 --- a/web/src/pages/flow/canvas/node/relevant-node.tsx +++ b/web/src/pages/flow/canvas/node/relevant-node.tsx @@ -1,16 +1,16 @@ +import { Handle, NodeProps, Position } from '@xyflow/react'; import { Flex } from 'antd'; import classNames from 'classnames'; -import { Handle, NodeProps, Position } from 'reactflow'; -import { NodeData } from '../../interface'; import { RightHandleStyle } from './handle-icon'; import { useTheme } from '@/components/theme-provider'; +import { IRelevantNode } from '@/interfaces/database/flow'; import { get } from 'lodash'; import { useReplaceIdWithName } from '../../hooks'; import styles from './index.less'; import NodeHeader from './node-header'; -export function RelevantNode({ id, data, selected }: NodeProps) { +export function RelevantNode({ id, data, selected }: NodeProps) { const yes = get(data, 'form.yes'); const no = get(data, 'form.no'); const replaceIdWithName = useReplaceIdWithName(); diff --git a/web/src/pages/flow/canvas/node/retrieval-node.tsx b/web/src/pages/flow/canvas/node/retrieval-node.tsx index 325b39a4e6b..0fd2760ede5 100644 --- a/web/src/pages/flow/canvas/node/retrieval-node.tsx +++ b/web/src/pages/flow/canvas/node/retrieval-node.tsx @@ -1,12 +1,12 @@ import { useTheme } from '@/components/theme-provider'; import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks'; +import { IRetrievalNode } from '@/interfaces/database/flow'; import { UserOutlined } from '@ant-design/icons'; +import { Handle, NodeProps, Position } from '@xyflow/react'; import { Avatar, Flex } from 'antd'; import classNames from 'classnames'; import { get } from 'lodash'; import { useMemo } from 'react'; -import { Handle, NodeProps, Position } from 'reactflow'; -import { NodeData } from '../../interface'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; @@ -16,7 +16,7 @@ export function RetrievalNode({ data, isConnectable = true, selected, -}: NodeProps) { +}: NodeProps) { const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []); const { theme } = useTheme(); const { list: knowledgeList } = useFetchKnowledgeList(true); diff --git a/web/src/pages/flow/canvas/node/rewrite-node.tsx b/web/src/pages/flow/canvas/node/rewrite-node.tsx index fa1029e8a4d..093b2c80ea3 100644 --- a/web/src/pages/flow/canvas/node/rewrite-node.tsx +++ b/web/src/pages/flow/canvas/node/rewrite-node.tsx @@ -1,9 +1,9 @@ import LLMLabel from '@/components/llm-select/llm-label'; import { useTheme } from '@/components/theme-provider'; +import { IRewriteNode } from '@/interfaces/database/flow'; +import { Handle, NodeProps, Position } from '@xyflow/react'; import classNames from 'classnames'; import { get } from 'lodash'; -import { Handle, NodeProps, Position } from 'reactflow'; -import { NodeData } from '../../interface'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; @@ -13,7 +13,7 @@ export function RewriteNode({ data, isConnectable = true, selected, -}: NodeProps) { +}: NodeProps) { const { theme } = useTheme(); return (
) { +export function SwitchNode({ id, data, selected }: NodeProps) { const { positions } = useBuildSwitchHandlePositions({ data, id }); const { theme } = useTheme(); return ( diff --git a/web/src/pages/flow/canvas/node/template-node.tsx b/web/src/pages/flow/canvas/node/template-node.tsx index c16286df52c..971fbab3842 100644 --- a/web/src/pages/flow/canvas/node/template-node.tsx +++ b/web/src/pages/flow/canvas/node/template-node.tsx @@ -1,13 +1,14 @@ import { useTheme } from '@/components/theme-provider'; +import { Handle, NodeProps, Position } from '@xyflow/react'; import { Flex } from 'antd'; import classNames from 'classnames'; import { get } from 'lodash'; -import { Handle, NodeProps, Position } from 'reactflow'; import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query'; -import { IGenerateParameter, NodeData } from '../../interface'; +import { IGenerateParameter } from '../../interface'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import NodeHeader from './node-header'; +import { ITemplateNode } from '@/interfaces/database/flow'; import styles from './index.less'; export function TemplateNode({ @@ -15,7 +16,7 @@ export function TemplateNode({ data, isConnectable = true, selected, -}: NodeProps) { +}: NodeProps) { const parameters: IGenerateParameter[] = get(data, 'form.parameters', []); const getLabel = useGetComponentLabelByValue(id); const { theme } = useTheme(); diff --git a/web/src/pages/flow/flow-drawer/index.tsx b/web/src/pages/flow/flow-drawer/index.tsx index 3014d9dfc77..487e746be29 100644 --- a/web/src/pages/flow/flow-drawer/index.tsx +++ b/web/src/pages/flow/flow-drawer/index.tsx @@ -5,7 +5,6 @@ import { Drawer, Flex, Form, Input } from 'antd'; import { lowerFirst } from 'lodash'; import { Play } from 'lucide-react'; import { useEffect, useRef } from 'react'; -import { Node } from 'reactflow'; import { BeginId, Operator, operatorMap } from '../constant'; import AkShareForm from '../form/akshare-form'; import AnswerForm from '../form/answer-form'; @@ -44,12 +43,13 @@ import OperatorIcon from '../operator-icon'; import { getDrawerWidth, needsSingleStepDebugging } from '../utils'; import SingleDebugDrawer from './single-debug-drawer'; +import { RAGFlowNodeType } from '@/interfaces/database/flow'; import { RunTooltip } from '../flow-tooltip'; import IterationForm from '../form/iteration-from'; import styles from './index.less'; interface IProps { - node?: Node; + node?: RAGFlowNodeType; singleDebugDrawerVisible: IModalProps['visible']; hideSingleDebugDrawer: IModalProps['hideModal']; showSingleDebugDrawer: IModalProps['showModal']; @@ -104,7 +104,7 @@ const FormDrawer = ({ hideSingleDebugDrawer, showSingleDebugDrawer, }: IModalProps & IProps) => { - const operatorName: Operator = node?.data.label; + const operatorName: Operator = node?.data.label as Operator; const OperatorForm = FormMap[operatorName] ?? EmptyContent; const [form] = Form.useForm(); const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({ diff --git a/web/src/pages/flow/form/categorize-form/dynamic-categorize.tsx b/web/src/pages/flow/form/categorize-form/dynamic-categorize.tsx index f4319c4a0fb..e87f9441152 100644 --- a/web/src/pages/flow/form/categorize-form/dynamic-categorize.tsx +++ b/web/src/pages/flow/form/categorize-form/dynamic-categorize.tsx @@ -1,5 +1,6 @@ import { useTranslate } from '@/hooks/common-hooks'; import { CloseOutlined, PlusOutlined } from '@ant-design/icons'; +import { useUpdateNodeInternals } from '@xyflow/react'; import { Button, Card, @@ -19,7 +20,6 @@ import { useEffect, useState, } from 'react'; -import { useUpdateNodeInternals } from 'reactflow'; import { Operator } from '../../constant'; import { useBuildFormSelectOptions } from '../../form-hooks'; diff --git a/web/src/pages/flow/form/components/dynamic-input-variable.tsx b/web/src/pages/flow/form/components/dynamic-input-variable.tsx index 9ad47b7744d..b79af90d01e 100644 --- a/web/src/pages/flow/form/components/dynamic-input-variable.tsx +++ b/web/src/pages/flow/form/components/dynamic-input-variable.tsx @@ -2,14 +2,13 @@ import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { Button, Collapse, Flex, Form, Input, Select } from 'antd'; import { PropsWithChildren, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { Node } from 'reactflow'; import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; -import { NodeData } from '../../interface'; +import { RAGFlowNodeType } from '../../interface'; import styles from './index.less'; interface IProps { - node?: Node; + node?: RAGFlowNodeType; } enum VariableType { diff --git a/web/src/pages/flow/form/generate-form/dynamic-parameters.tsx b/web/src/pages/flow/form/generate-form/dynamic-parameters.tsx index 8326376f929..5ab8e6308fc 100644 --- a/web/src/pages/flow/form/generate-form/dynamic-parameters.tsx +++ b/web/src/pages/flow/form/generate-form/dynamic-parameters.tsx @@ -2,14 +2,13 @@ import { EditableCell, EditableRow } from '@/components/editable-cell'; import { useTranslate } from '@/hooks/common-hooks'; import { DeleteOutlined } from '@ant-design/icons'; import { Button, Flex, Select, Table, TableProps } from 'antd'; -import { Node } from 'reactflow'; import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; -import { IGenerateParameter, NodeData } from '../../interface'; +import { IGenerateParameter, RAGFlowNodeType } from '../../interface'; import { useHandleOperateParameters } from './hooks'; import styles from './index.less'; interface IProps { - node?: Node; + node?: RAGFlowNodeType; } const components = { diff --git a/web/src/pages/flow/form/invoke-form/dynamic-variables.tsx b/web/src/pages/flow/form/invoke-form/dynamic-variables.tsx index ad3e07ac527..3538b8b728e 100644 --- a/web/src/pages/flow/form/invoke-form/dynamic-variables.tsx +++ b/web/src/pages/flow/form/invoke-form/dynamic-variables.tsx @@ -4,14 +4,13 @@ import { DeleteOutlined } from '@ant-design/icons'; import { Button, Collapse, Flex, Input, Select, Table, TableProps } from 'antd'; import { trim } from 'lodash'; import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; -import { IInvokeVariable, NodeData } from '../../interface'; +import { IInvokeVariable, RAGFlowNodeType } from '../../interface'; import { useHandleOperateParameters } from './hooks'; -import { Node } from 'reactflow'; import styles from './index.less'; interface IProps { - node?: Node; + node?: RAGFlowNodeType; } const components = { diff --git a/web/src/pages/flow/form/relevant-form/hooks.ts b/web/src/pages/flow/form/relevant-form/hooks.ts index 6ccbfff08e7..6f31550fb42 100644 --- a/web/src/pages/flow/form/relevant-form/hooks.ts +++ b/web/src/pages/flow/form/relevant-form/hooks.ts @@ -1,6 +1,6 @@ +import { Edge } from '@xyflow/react'; import pick from 'lodash/pick'; import { useCallback, useEffect } from 'react'; -import { Edge } from 'reactflow'; import { IOperatorForm } from '../../interface'; import useGraphStore from '../../store'; diff --git a/web/src/pages/flow/hooks.tsx b/web/src/pages/flow/hooks.tsx index aff2ef38d01..177b24cfb40 100644 --- a/web/src/pages/flow/hooks.tsx +++ b/web/src/pages/flow/hooks.tsx @@ -1,3 +1,10 @@ +import { + Connection, + Edge, + Node, + Position, + ReactFlowInstance, +} from '@xyflow/react'; import React, { ChangeEvent, useCallback, @@ -5,7 +12,6 @@ import React, { useMemo, useState, } from 'react'; -import { Connection, Edge, Node, Position, ReactFlowInstance } from 'reactflow'; // import { shallow } from 'zustand/shallow'; import { variableEnabledFieldMap } from '@/constants/chat'; import { @@ -14,6 +20,12 @@ import { } from '@/constants/knowledge'; import { useFetchModelId } from '@/hooks/logic-hooks'; import { Variable } from '@/interfaces/database/chat'; +import { + ICategorizeForm, + IRelevantForm, + ISwitchForm, + RAGFlowNodeType, +} from '@/interfaces/database/flow'; import { FormInstance, message } from 'antd'; import { humanId } from 'human-id'; import { get, isEmpty, lowerFirst, pick } from 'lodash'; @@ -60,7 +72,6 @@ import { initialWikipediaValues, initialYahooFinanceValues, } from './constant'; -import { ICategorizeForm, IRelevantForm, ISwitchForm } from './interface'; import useGraphStore, { RFState } from './store'; import { generateNodeNamesWithIncreasingIndex, @@ -149,7 +160,7 @@ export const useInitializeOperatorParams = () => { export const useHandleDrag = () => { const handleDragStart = useCallback( (operatorId: string) => (ev: React.DragEvent) => { - ev.dataTransfer.setData('application/reactflow', operatorId); + ev.dataTransfer.setData('application/@xyflow/react', operatorId); ev.dataTransfer.effectAllowed = 'move'; }, [], @@ -184,7 +195,7 @@ export const useHandleDrop = () => { (event: React.DragEvent) => { event.preventDefault(); - const type = event.dataTransfer.getData('application/reactflow'); + const type = event.dataTransfer.getData('application/@xyflow/react'); // check if the dropped element is valid if (typeof type === 'undefined' || !type) { @@ -193,7 +204,7 @@ export const useHandleDrop = () => { // reactFlowInstance.project was renamed to reactFlowInstance.screenToFlowPosition // and you don't need to subtract the reactFlowBounds.left/top anymore - // details: https://reactflow.dev/whats-new/2023-11-10 + // details: https://@xyflow/react.dev/whats-new/2023-11-10 const position = reactFlowInstance?.screenToFlowPosition({ x: event.clientX, y: event.clientY, @@ -216,10 +227,8 @@ export const useHandleDrop = () => { }; if (type === Operator.Iteration) { - newNode.style = { - width: 500, - height: 250, - }; + newNode.width = 500; + newNode.height = 250; const iterationStartNode: Node = { id: `${Operator.IterationStart}:${humanId()}`, type: 'iterationStartNode', @@ -320,7 +329,7 @@ export const useValidateConnection = () => { ); const isSameNodeChild = useCallback( - (connection: Connection) => { + (connection: Connection | Edge) => { const sourceParentId = getParentIdById(connection.source); const targetParentId = getParentIdById(connection.target); if (sourceParentId || targetParentId) { @@ -333,7 +342,7 @@ export const useValidateConnection = () => { // restricted lines cannot be connected successfully. const isValidConnection = useCallback( - (connection: Connection) => { + (connection: Connection | Edge) => { // node cannot connect to itself const isSelfConnected = connection.target === connection.source; @@ -567,7 +576,7 @@ export const useCopyPaste = () => { (event: ClipboardEvent) => { const nodes = JSON.parse( event.clipboardData?.getData('agent:nodes') || '[]', - ) as Node[] | undefined; + ) as RAGFlowNodeType[] | undefined; if (Array.isArray(nodes) && nodes.length) { event.preventDefault(); diff --git a/web/src/pages/flow/hooks/use-before-delete.tsx b/web/src/pages/flow/hooks/use-before-delete.tsx new file mode 100644 index 00000000000..14512ae9609 --- /dev/null +++ b/web/src/pages/flow/hooks/use-before-delete.tsx @@ -0,0 +1,57 @@ +import { RAGFlowNodeType } from '@/interfaces/database/flow'; +import { OnBeforeDelete } from '@xyflow/react'; +import { Operator } from '../constant'; +import useGraphStore from '../store'; + +const UndeletableNodes = [Operator.Begin, Operator.IterationStart]; + +export function useBeforeDelete() { + const getOperatorTypeFromId = useGraphStore( + (state) => state.getOperatorTypeFromId, + ); + const handleBeforeDelete: OnBeforeDelete = async ({ + nodes, // Nodes to be deleted + edges, // Edges to be deleted + }) => { + const toBeDeletedNodes = nodes.filter((node) => { + const operatorType = node.data?.label as Operator; + if (operatorType === Operator.Begin) { + return false; + } + + if ( + operatorType === Operator.IterationStart && + !nodes.some((x) => x.id === node.parentId) + ) { + return false; + } + + return true; + }); + + const toBeDeletedEdges = edges.filter((edge) => { + const sourceType = getOperatorTypeFromId(edge.source) as Operator; + const downStreamNodes = nodes.filter((x) => x.id === edge.target); + + // This edge does not need to be deleted, the range of edges that do not need to be deleted is smaller, so consider the case where it does not need to be deleted + if ( + UndeletableNodes.includes(sourceType) && // Upstream node is Begin or IterationStart + downStreamNodes.length === 0 // Downstream node does not exist in the nodes to be deleted + ) { + if (!nodes.some((x) => x.id === edge.source)) { + return true; // Can be deleted + } + return false; // Cannot be deleted + } + + return true; + }); + + return { + nodes: toBeDeletedNodes, + edges: toBeDeletedEdges, + }; + }; + + return { handleBeforeDelete }; +} diff --git a/web/src/pages/flow/hooks/use-build-dsl.ts b/web/src/pages/flow/hooks/use-build-dsl.ts index a6e5c015ba0..17a0681ed0d 100644 --- a/web/src/pages/flow/hooks/use-build-dsl.ts +++ b/web/src/pages/flow/hooks/use-build-dsl.ts @@ -1,6 +1,6 @@ import { useFetchFlow } from '@/hooks/flow-hooks'; +import { RAGFlowNodeType } from '@/interfaces/database/flow'; import { useCallback } from 'react'; -import { Node } from 'reactflow'; import useGraphStore from '../store'; import { buildDslComponentsByGraph } from '../utils'; @@ -9,7 +9,7 @@ export const useBuildDslData = () => { const { nodes, edges } = useGraphStore((state) => state); const buildDslData = useCallback( - (currentNodes?: Node[]) => { + (currentNodes?: RAGFlowNodeType[]) => { const dslComponents = buildDslComponentsByGraph( currentNodes ?? nodes, edges, diff --git a/web/src/pages/flow/hooks/use-get-begin-query.tsx b/web/src/pages/flow/hooks/use-get-begin-query.tsx index 0a86affaf44..1ed5536162e 100644 --- a/web/src/pages/flow/hooks/use-get-begin-query.tsx +++ b/web/src/pages/flow/hooks/use-get-begin-query.tsx @@ -1,9 +1,8 @@ import { DefaultOptionType } from 'antd/es/select'; import get from 'lodash/get'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Node } from 'reactflow'; import { BeginId, Operator } from '../constant'; -import { BeginQuery, NodeData } from '../interface'; +import { BeginQuery, RAGFlowNodeType } from '../interface'; import useGraphStore from '../store'; export const useGetBeginNodeDataQuery = () => { @@ -48,7 +47,7 @@ export const useBuildComponentIdSelectOptions = ( // Limit the nodes inside iteration to only reference peer nodes with the same parentId and other external nodes other than their parent nodes const filterChildNodesToSameParentOrExternal = useCallback( - (node: Node) => { + (node: RAGFlowNodeType) => { // Node inside iteration if (parentId) { return ( diff --git a/web/src/pages/flow/hooks/use-save-graph.ts b/web/src/pages/flow/hooks/use-save-graph.ts index e042aca6251..eb102d1a2c3 100644 --- a/web/src/pages/flow/hooks/use-save-graph.ts +++ b/web/src/pages/flow/hooks/use-save-graph.ts @@ -1,8 +1,8 @@ import { useFetchFlow, useResetFlow, useSetFlow } from '@/hooks/flow-hooks'; +import { RAGFlowNodeType } from '@/interfaces/database/flow'; import { useDebounceEffect } from 'ahooks'; import dayjs from 'dayjs'; import { useCallback, useEffect, useState } from 'react'; -import { Node } from 'reactflow'; import { useParams } from 'umi'; import useGraphStore from '../store'; import { useBuildDslData } from './use-build-dsl'; @@ -14,7 +14,7 @@ export const useSaveGraph = () => { const { buildDslData } = useBuildDslData(); const saveGraph = useCallback( - async (currentNodes?: Node[]) => { + async (currentNodes?: RAGFlowNodeType[]) => { return setFlow({ id, title: data.title, @@ -32,7 +32,7 @@ export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => { const { resetFlow } = useResetFlow(); const handleRun = useCallback( - async (nextNodes?: Node[]) => { + async (nextNodes?: RAGFlowNodeType[]) => { const saveRet = await saveGraph(nextNodes); if (saveRet?.code === 0) { // Call the reset api before opening the run drawer each time diff --git a/web/src/pages/flow/hooks/use-show-drawer.tsx b/web/src/pages/flow/hooks/use-show-drawer.tsx index 8146db4bcf8..efc4cf32adc 100644 --- a/web/src/pages/flow/hooks/use-show-drawer.tsx +++ b/web/src/pages/flow/hooks/use-show-drawer.tsx @@ -1,7 +1,7 @@ import { useSetModalState } from '@/hooks/common-hooks'; +import { Node, NodeMouseHandler } from '@xyflow/react'; import get from 'lodash/get'; import { useCallback, useEffect } from 'react'; -import { Node, NodeMouseHandler } from 'reactflow'; import { Operator } from '../constant'; import { BeginQuery } from '../interface'; import useGraphStore from '../store'; diff --git a/web/src/pages/flow/index.tsx b/web/src/pages/flow/index.tsx index 50b6256832f..1d758e3478a 100644 --- a/web/src/pages/flow/index.tsx +++ b/web/src/pages/flow/index.tsx @@ -1,7 +1,7 @@ import { useSetModalState } from '@/hooks/common-hooks'; +import { ReactFlowProvider } from '@xyflow/react'; import { Layout } from 'antd'; import { useState } from 'react'; -import { ReactFlowProvider } from 'reactflow'; import FlowCanvas from './canvas'; import Sider from './flow-sider'; import FlowHeader from './header'; diff --git a/web/src/pages/flow/interface.ts b/web/src/pages/flow/interface.ts index 3975500f73c..ff70f1e690d 100644 --- a/web/src/pages/flow/interface.ts +++ b/web/src/pages/flow/interface.ts @@ -1,51 +1,13 @@ +import { RAGFlowNodeType } from '@/interfaces/database/flow'; import { FormInstance } from 'antd'; -import { Node } from 'reactflow'; - -export interface DSLComponentList { - id: string; - name: string; -} export interface IOperatorForm { onValuesChange?(changedValues: any, values: any): void; form?: FormInstance; - node?: Node; + node?: RAGFlowNodeType; nodeId?: string; } -export interface IBeginForm { - prologue?: string; -} - -export interface IRetrievalForm { - similarity_threshold?: number; - keywords_similarity_weight?: number; - top_n?: number; - top_k?: number; - rerank_id?: string; - empty_response?: string; - kb_ids: string[]; -} - -export interface IGenerateForm { - max_tokens?: number; - temperature?: number; - top_p?: number; - presence_penalty?: number; - frequency_penalty?: number; - cite?: boolean; - prompt: number; - llm_id: string; - parameters: { key: string; component_id: string }; -} -export interface ICategorizeItem { - name: string; - description?: string; - examples?: string; - to?: string; - index: number; -} - export interface IGenerateParameter { id?: string; key: string; @@ -56,49 +18,6 @@ export interface IInvokeVariable extends IGenerateParameter { value?: string; } -export type ICategorizeItemResult = Record< - string, - Omit ->; -export interface ICategorizeForm extends IGenerateForm { - category_description: ICategorizeItemResult; -} - -export interface IRelevantForm extends IGenerateForm { - yes: string; - no: string; -} - -export interface ISwitchCondition { - items: ISwitchItem[]; - logical_operator: string; - to: string; -} - -export interface ISwitchItem { - cpn_id: string; - operator: string; - value: string; -} - -export interface ISwitchForm { - conditions: ISwitchCondition[]; - end_cpn_id: string; - no: string; -} - -export type NodeData = { - label: string; // operator type - name: string; // operator name - color?: string; - form: - | IBeginForm - | IRetrievalForm - | IGenerateForm - | ICategorizeForm - | ISwitchForm; -}; - export type IPosition = { top: number; right: number; idx: number }; export interface BeginQuery { diff --git a/web/src/pages/flow/mock.tsx b/web/src/pages/flow/mock.tsx index 49f6ac92294..79c61d73da0 100644 --- a/web/src/pages/flow/mock.tsx +++ b/web/src/pages/flow/mock.tsx @@ -1,4 +1,4 @@ -import { Position } from 'reactflow'; +import { Position } from '@xyflow/react'; export const initialNodes = [ { diff --git a/web/src/pages/flow/store.ts b/web/src/pages/flow/store.ts index 3f645a40673..c6e8c69b6eb 100644 --- a/web/src/pages/flow/store.ts +++ b/web/src/pages/flow/store.ts @@ -1,14 +1,9 @@ +import { RAGFlowNodeType } from '@/interfaces/database/flow'; import type {} from '@redux-devtools/extension'; -import { omit } from 'lodash'; -import differenceWith from 'lodash/differenceWith'; -import intersectionWith from 'lodash/intersectionWith'; -import lodashSet from 'lodash/set'; import { Connection, Edge, EdgeChange, - Node, - NodeChange, OnConnect, OnEdgesChange, OnNodesChange, @@ -17,12 +12,15 @@ import { addEdge, applyEdgeChanges, applyNodeChanges, -} from 'reactflow'; +} from '@xyflow/react'; +import { omit } from 'lodash'; +import differenceWith from 'lodash/differenceWith'; +import intersectionWith from 'lodash/intersectionWith'; +import lodashSet from 'lodash/set'; import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; import { Operator, SwitchElseTo } from './constant'; -import { NodeData } from './interface'; import { duplicateNodeForm, generateDuplicateNode, @@ -32,25 +30,25 @@ import { } from './utils'; export type RFState = { - nodes: Node[]; + nodes: RAGFlowNodeType[]; edges: Edge[]; selectedNodeIds: string[]; selectedEdgeIds: string[]; clickedNodeId: string; // currently selected node - onNodesChange: OnNodesChange; + onNodesChange: OnNodesChange; onEdgesChange: OnEdgesChange; onConnect: OnConnect; - setNodes: (nodes: Node[]) => void; + setNodes: (nodes: RAGFlowNodeType[]) => void; setEdges: (edges: Edge[]) => void; setEdgesByNodeId: (nodeId: string, edges: Edge[]) => void; updateNodeForm: ( nodeId: string, values: any, path?: (string | number)[], - ) => Node[]; + ) => RAGFlowNodeType[]; onSelectionChange: OnSelectionChangeFunc; - addNode: (nodes: Node) => void; - getNode: (id?: string | null) => Node | undefined; + addNode: (nodes: RAGFlowNodeType) => void; + getNode: (id?: string | null) => RAGFlowNodeType | undefined; addEdge: (connection: Connection) => void; getEdge: (id: string) => Edge | undefined; updateFormDataOnConnect: (connection: Connection) => void; @@ -67,7 +65,7 @@ export type RFState = { deleteNodeById: (id: string) => void; deleteIterationNodeById: (id: string) => void; deleteEdgeBySourceAndSourceHandle: (connection: Partial) => void; - findNodeByName: (operatorName: Operator) => Node | undefined; + findNodeByName: (operatorName: Operator) => RAGFlowNodeType | undefined; updateMutableNodeFormItem: (id: string, field: string, value: any) => void; getOperatorTypeFromId: (id?: string | null) => string | undefined; getParentIdById: (id?: string | null) => string | undefined; @@ -80,12 +78,12 @@ export type RFState = { const useGraphStore = create()( devtools( immer((set, get) => ({ - nodes: [] as Node[], + nodes: [] as RAGFlowNodeType[], edges: [] as Edge[], selectedNodeIds: [] as string[], selectedEdgeIds: [] as string[], clickedNodeId: '', - onNodesChange: (changes: NodeChange[]) => { + onNodesChange: (changes) => { set({ nodes: applyNodeChanges(changes, get().nodes), }); @@ -112,7 +110,7 @@ const useGraphStore = create()( selectedNodeIds: nodes.map((x) => x.id), }); }, - setNodes: (nodes: Node[]) => { + setNodes: (nodes: RAGFlowNodeType[]) => { set({ nodes }); }, setEdges: (edges: Edge[]) => { @@ -164,7 +162,7 @@ const useGraphStore = create()( ]); } }, - addNode: (node: Node) => { + addNode: (node: RAGFlowNodeType) => { set({ nodes: get().nodes.concat(node) }); }, getNode: (id?: string | null) => { @@ -262,7 +260,7 @@ const useGraphStore = create()( const { getNode, generateNodeName, nodes } = get(); const node = getNode(id); - const iterationNode: Node = { + const iterationNode: RAGFlowNodeType = { ...(node || {}), data: { ...(node?.data || { label: Operator.Iteration, form: {} }), diff --git a/web/src/pages/flow/utils.ts b/web/src/pages/flow/utils.ts index 6585d24089b..5ce73247b9c 100644 --- a/web/src/pages/flow/utils.ts +++ b/web/src/pages/flow/utils.ts @@ -1,11 +1,15 @@ -import { DSLComponents } from '@/interfaces/database/flow'; +import { + DSLComponents, + ICategorizeItemResult, + RAGFlowNodeType, +} from '@/interfaces/database/flow'; import { removeUselessFieldsFromValues } from '@/utils/form'; +import { Edge, Node, Position, XYPosition } from '@xyflow/react'; import { FormInstance, FormListFieldData } from 'antd'; import { humanId } from 'human-id'; import { curry, get, intersectionWith, isEqual, sample } from 'lodash'; import pipe from 'lodash/fp/pipe'; import isObject from 'lodash/isObject'; -import { Edge, Node, Position, XYPosition } from 'reactflow'; import { v4 as uuidv4 } from 'uuid'; import { CategorizeAnchorPointPositions, @@ -13,7 +17,7 @@ import { NodeMap, Operator, } from './constant'; -import { ICategorizeItemResult, IPosition, NodeData } from './interface'; +import { IPosition } from './interface'; const buildEdges = ( operatorIds: string[], @@ -122,7 +126,7 @@ const buildOperatorParams = (operatorName: string) => // construct a dsl based on the node information of the graph export const buildDslComponentsByGraph = ( - nodes: Node[], + nodes: RAGFlowNodeType[], edges: Edge[], oldDslComponents: DSLComponents, ): DSLComponents => { @@ -260,7 +264,7 @@ const splitName = (name: string) => { export const generateNodeNamesWithIncreasingIndex = ( name: string, - nodes: Node[], + nodes: RAGFlowNodeType[], ) => { const templateNameList = nodes .filter((x) => { @@ -298,7 +302,7 @@ export const generateNodeNamesWithIncreasingIndex = ( return `${name}_${index}`; }; -export const duplicateNodeForm = (nodeData?: NodeData) => { +export const duplicateNodeForm = (nodeData?: RAGFlowNodeType['data']) => { const form: Record = { ...(nodeData?.form ?? {}) }; // Delete the downstream node corresponding to the to field of the Categorize operator @@ -321,7 +325,7 @@ export const duplicateNodeForm = (nodeData?: NodeData) => { } return { - ...(nodeData ?? {}), + ...(nodeData ?? { label: '' }), form, }; }; @@ -336,7 +340,7 @@ export const needsSingleStepDebugging = (label: string) => { // Get the coordinates of the node relative to the Iteration node export function getRelativePositionToIterationNode( - nodes: Node[], + nodes: RAGFlowNodeType[], position?: XYPosition, // relative position ) { if (!position) {