diff --git a/web/app/components/rag-pipeline/hooks/use-rag-pipeline-search.tsx b/web/app/components/rag-pipeline/hooks/use-rag-pipeline-search.tsx index 70415b8200..4b0e1f1973 100644 --- a/web/app/components/rag-pipeline/hooks/use-rag-pipeline-search.tsx +++ b/web/app/components/rag-pipeline/hooks/use-rag-pipeline-search.tsx @@ -1,7 +1,7 @@ 'use client' import { useCallback, useEffect, useMemo } from 'react' -import { useNodes } from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { useNodesInteractions } from '@/app/components/workflow/hooks/use-nodes-interactions' import type { CommonNodeType } from '@/app/components/workflow/types' import { ragPipelineNodesAction } from '@/app/components/goto-anything/actions/rag-pipeline-nodes' diff --git a/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx b/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx index 28a2f43fe5..33aeed4edf 100644 --- a/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx +++ b/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx @@ -3,7 +3,7 @@ import { useCallback, useMemo, } from 'react' -import { useEdges, useNodes } from 'reactflow' +import { useEdges } from 'reactflow' import { RiApps2AddLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { @@ -22,7 +22,6 @@ import AppPublisher from '@/app/components/app/app-publisher' import { useFeatures } from '@/app/components/base/features/hooks' import type { CommonEdgeType, - CommonNodeType, Node, } from '@/app/components/workflow/types' import { @@ -42,6 +41,7 @@ import { useIsChatMode } from '@/app/components/workflow/hooks' import type { StartNodeType } from '@/app/components/workflow/nodes/start/types' import { useProviderContext } from '@/context/provider-context' import { Plan } from '@/app/components/billing/type' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' const FeaturesTrigger = () => { const { t } = useTranslation() @@ -58,7 +58,7 @@ const FeaturesTrigger = () => { const toolPublished = useStore(s => s.toolPublished) const lastPublishedHasUserInput = useStore(s => s.lastPublishedHasUserInput) - const nodes = useNodes() + const nodes = useNodes() const hasWorkflowNodes = nodes.length > 0 const startNode = nodes.find(node => node.data.type === BlockEnum.Start) const startVariables = (startNode as Node)?.data?.variables diff --git a/web/app/components/workflow/block-selector/main.tsx b/web/app/components/workflow/block-selector/main.tsx index 3e13384785..1fff8528e2 100644 --- a/web/app/components/workflow/block-selector/main.tsx +++ b/web/app/components/workflow/block-selector/main.tsx @@ -9,7 +9,7 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import { useNodes } from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import type { OffsetOptions, Placement, diff --git a/web/app/components/workflow/block-selector/start-blocks.tsx b/web/app/components/workflow/block-selector/start-blocks.tsx index 31b6abce6c..07404460be 100644 --- a/web/app/components/workflow/block-selector/start-blocks.tsx +++ b/web/app/components/workflow/block-selector/start-blocks.tsx @@ -4,7 +4,7 @@ import { useEffect, useMemo, } from 'react' -import { useNodes } from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { useTranslation } from 'react-i18next' import BlockIcon from '../block-icon' import type { BlockEnum, CommonNodeType } from '../types' diff --git a/web/app/components/workflow/header/checklist.tsx b/web/app/components/workflow/header/checklist.tsx index 794a8997a9..aa532b98d7 100644 --- a/web/app/components/workflow/header/checklist.tsx +++ b/web/app/components/workflow/header/checklist.tsx @@ -5,7 +5,6 @@ import { import { useTranslation } from 'react-i18next' import { useEdges, - useNodes, } from 'reactflow' import { RiCloseLine, @@ -19,7 +18,6 @@ import { import type { ChecklistItem } from '../hooks/use-checklist' import type { CommonEdgeType, - CommonNodeType, } from '../types' import cn from '@/utils/classnames' import { @@ -32,7 +30,10 @@ import { } from '@/app/components/base/icons/src/vender/line/general' import { Warning } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' import { IconR } from '@/app/components/base/icons/src/vender/line/arrows' -import type { BlockEnum } from '../types' +import type { + BlockEnum, +} from '../types' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' type WorkflowChecklistProps = { disabled: boolean @@ -42,8 +43,8 @@ const WorkflowChecklist = ({ }: WorkflowChecklistProps) => { const { t } = useTranslation() const [open, setOpen] = useState(false) - const nodes = useNodes() const edges = useEdges() + const nodes = useNodes() const needWarningNodes = useChecklist(nodes, edges) const { handleNodeSelect } = useNodesInteractions() diff --git a/web/app/components/workflow/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index be7aabbc68..8144120bfe 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -4,7 +4,7 @@ import { useRef, } from 'react' import { useTranslation } from 'react-i18next' -import { useEdges, useNodes, useStoreApi } from 'reactflow' +import { useEdges, useStoreApi } from 'reactflow' import type { CommonEdgeType, CommonNodeType, @@ -56,6 +56,7 @@ import { } from '@/service/use-tools' import { useStore as useAppStore } from '@/app/components/app/store' import { AppModeEnum } from '@/types/app' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' export type ChecklistItem = { id: string @@ -407,7 +408,7 @@ export const useChecklistBeforePublish = () => { export const useWorkflowRunValidation = () => { const { t } = useTranslation() - const nodes = useNodes() + const nodes = useNodes() const edges = useEdges() const needWarningNodes = useChecklist(nodes, edges) const { notify } = useToastContext() diff --git a/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx b/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx index 3e35ff0168..276e2f38e7 100644 --- a/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx +++ b/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx @@ -1,5 +1,5 @@ import { useMemo } from 'react' -import { useNodes } from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { useTranslation } from 'react-i18next' import { BlockEnum, type CommonNodeType } from '../types' import { getWorkflowEntryNode } from '../utils/workflow-entry' diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 4fc9c48caa..880f652026 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -18,6 +18,7 @@ import ReactFlow, { ReactFlowProvider, SelectionMode, useEdgesState, + useNodes, useNodesState, useOnViewportChange, useReactFlow, @@ -97,6 +98,7 @@ import { useAllMCPTools, useAllWorkflowTools, } from '@/service/use-tools' +import { isEqual } from 'lodash-es' const Confirm = dynamic(() => import('@/app/components/base/confirm'), { ssr: false, @@ -167,7 +169,24 @@ export const Workflow: FC = memo(({ setShowConfirm, setControlPromptEditorRerenderKey, setSyncWorkflowDraftHash, + setNodes: setNodesInStore, } = workflowStore.getState() + const currentNodes = useNodes() + const setNodesOnlyChangeWithData = useCallback((nodes: Node[]) => { + const nodesData = nodes.map(node => ({ + id: node.id, + data: node.data, + })) + const oldData = workflowStore.getState().nodes.map(node => ({ + id: node.id, + data: node.data, + })) + if (!isEqual(oldData, nodesData)) + setNodesInStore(nodes) + }, [setNodesInStore, workflowStore]) + useEffect(() => { + setNodesOnlyChangeWithData(currentNodes as Node[]) + }, [currentNodes, setNodesOnlyChangeWithData]) const { handleSyncWorkflowDraft, syncWorkflowDraftWhenPageClose, diff --git a/web/app/components/workflow/node-contextmenu.tsx b/web/app/components/workflow/node-contextmenu.tsx index 311bf1fddf..86708981fe 100644 --- a/web/app/components/workflow/node-contextmenu.tsx +++ b/web/app/components/workflow/node-contextmenu.tsx @@ -4,7 +4,7 @@ import { useRef, } from 'react' import { useClickAway } from 'ahooks' -import { useNodes } from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import PanelOperatorPopup from './nodes/_base/components/panel-operator/panel-operator-popup' import type { Node } from './types' import { useStore } from './store' @@ -16,7 +16,6 @@ const NodeContextmenu = () => { const { handleNodeContextmenuCancel, handlePaneContextmenuCancel } = usePanelInteractions() const nodeMenu = useStore(s => s.nodeMenu) const currentNode = nodes.find(node => node.id === nodeMenu?.nodeId) as Node - useEffect(() => { if (nodeMenu) handlePaneContextmenuCancel() diff --git a/web/app/components/workflow/nodes/assigner/hooks.ts b/web/app/components/workflow/nodes/assigner/hooks.ts index d0600e5e09..0f316c6aeb 100644 --- a/web/app/components/workflow/nodes/assigner/hooks.ts +++ b/web/app/components/workflow/nodes/assigner/hooks.ts @@ -1,7 +1,5 @@ import { useCallback } from 'react' -import { - useNodes, -} from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { uniqBy } from 'lodash-es' import { useIsChatMode, diff --git a/web/app/components/workflow/nodes/assigner/node.tsx b/web/app/components/workflow/nodes/assigner/node.tsx index 5e5950d715..cf1896d2ba 100644 --- a/web/app/components/workflow/nodes/assigner/node.tsx +++ b/web/app/components/workflow/nodes/assigner/node.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import React from 'react' -import { useNodes } from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { useTranslation } from 'react-i18next' import type { AssignerNodeType } from './types' import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' diff --git a/web/app/components/workflow/nodes/document-extractor/node.tsx b/web/app/components/workflow/nodes/document-extractor/node.tsx index a0437a4f54..9f1105b51a 100644 --- a/web/app/components/workflow/nodes/document-extractor/node.tsx +++ b/web/app/components/workflow/nodes/document-extractor/node.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import React from 'react' -import { useNodes } from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { useTranslation } from 'react-i18next' import type { DocExtractorNodeType } from './types' import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' diff --git a/web/app/components/workflow/nodes/if-else/components/condition-value.tsx b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx index 8fe5578819..4a2d378aef 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-value.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx @@ -3,7 +3,7 @@ import { useMemo, } from 'react' import { useTranslation } from 'react-i18next' -import { useNodes } from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { ComparisonOperator } from '../types' import { comparisonOperatorNotRequireValue, @@ -46,7 +46,7 @@ const ConditionValue = ({ if (Array.isArray(value)) // transfer method return value[0] - if(value === true || value === false) + if (value === true || value === false) return value ? 'True' : 'False' return value.replace(/{{#([^#]*)#}}/g, (a, b) => { diff --git a/web/app/components/workflow/nodes/list-operator/node.tsx b/web/app/components/workflow/nodes/list-operator/node.tsx index 3c59f36587..5a6c5fe83f 100644 --- a/web/app/components/workflow/nodes/list-operator/node.tsx +++ b/web/app/components/workflow/nodes/list-operator/node.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import React from 'react' -import { useNodes } from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { useTranslation } from 'react-i18next' import type { ListFilterNodeType } from './types' import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' diff --git a/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx b/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx index e96475b953..96dfb5f52f 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx @@ -3,7 +3,7 @@ import { useMemo, } from 'react' import { useTranslation } from 'react-i18next' -import { useNodes } from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { useStore } from '../../../store' import { BlockEnum } from '../../../types' import type { diff --git a/web/app/components/workflow/nodes/variable-assigner/hooks.ts b/web/app/components/workflow/nodes/variable-assigner/hooks.ts index 62bd11ce2e..3b95ca6c41 100644 --- a/web/app/components/workflow/nodes/variable-assigner/hooks.ts +++ b/web/app/components/workflow/nodes/variable-assigner/hooks.ts @@ -1,8 +1,9 @@ import { useCallback } from 'react' import { - useNodes, useStoreApi, } from 'reactflow' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' + import { uniqBy } from 'lodash-es' import { produce } from 'immer' import { diff --git a/web/app/components/workflow/store/workflow/use-nodes.ts b/web/app/components/workflow/store/workflow/use-nodes.ts new file mode 100644 index 0000000000..4bc273a690 --- /dev/null +++ b/web/app/components/workflow/store/workflow/use-nodes.ts @@ -0,0 +1,7 @@ +import { + useStore, +} from '@/app/components/workflow/store' + +const useWorkflowNodes = () => useStore(s => s.nodes) + +export default useWorkflowNodes diff --git a/web/app/components/workflow/store/workflow/workflow-draft-slice.ts b/web/app/components/workflow/store/workflow/workflow-draft-slice.ts index cae716dd52..f20a9453e9 100644 --- a/web/app/components/workflow/store/workflow/workflow-draft-slice.ts +++ b/web/app/components/workflow/store/workflow/workflow-draft-slice.ts @@ -23,6 +23,8 @@ export type WorkflowDraftSliceShape = { setIsSyncingWorkflowDraft: (isSyncingWorkflowDraft: boolean) => void isWorkflowDataLoaded: boolean setIsWorkflowDataLoaded: (loaded: boolean) => void + nodes: Node[] + setNodes: (nodes: Node[]) => void } export const createWorkflowDraftSlice: StateCreator = set => ({ @@ -37,4 +39,6 @@ export const createWorkflowDraftSlice: StateCreator = s setIsSyncingWorkflowDraft: isSyncingWorkflowDraft => set(() => ({ isSyncingWorkflowDraft })), isWorkflowDataLoaded: false, setIsWorkflowDataLoaded: loaded => set(() => ({ isWorkflowDataLoaded: loaded })), + nodes: [], + setNodes: nodes => set(() => ({ nodes })), })