From 1fffc79c32d7b0652132bb95b6e0a1713a54a38f Mon Sep 17 00:00:00 2001 From: lyzno1 <92089059+lyzno1@users.noreply.github.com> Date: Thu, 4 Sep 2025 17:13:49 +0800 Subject: [PATCH] fix: prevent empty workflow draft sync during page navigation (#25140) --- .../workflow-app/hooks/use-nodes-sync-draft.ts | 10 +++------- .../workflow-app/hooks/use-workflow-init.ts | 1 + web/app/components/workflow-app/index.tsx | 17 +++++++++++++++++ .../store/workflow/workflow-draft-slice.ts | 4 ++++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts index f7debd1030..8f552f4db1 100644 --- a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts @@ -33,19 +33,15 @@ export const useNodesSyncDraft = () => { conversationVariables, environmentVariables, syncWorkflowDraftHash, + isWorkflowDataLoaded, } = workflowStore.getState() if (appId) { const nodes = getNodes() - // Prevent syncing empty workflow if React Flow isn't properly initialized - // Check multiple indicators of uninitialized state: - // 1. Empty nodes with default viewport (x=0, y=0, zoom=1) - // 2. Empty edges array along with empty nodes - if (nodes.length === 0 && edges.length === 0 && x === 0 && y === 0 && zoom === 1) { - // This appears to be an uninitialized React Flow state, skip sync to prevent data loss + // Prevent sync if workflow data hasn't been loaded yet + if (!isWorkflowDataLoaded) return null - } const features = featuresStore!.getState().features const producedNodes = produce(nodes, (draft) => { diff --git a/web/app/components/workflow-app/hooks/use-workflow-init.ts b/web/app/components/workflow-app/hooks/use-workflow-init.ts index af4e20268a..5de0f6045a 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-init.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-init.ts @@ -49,6 +49,7 @@ export const useWorkflowInit = () => { }, {} as Record), environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [], conversationVariables: res.conversation_variables || [], + isWorkflowDataLoaded: true, }) setSyncWorkflowDraftHash(res.hash) setIsLoading(false) diff --git a/web/app/components/workflow-app/index.tsx b/web/app/components/workflow-app/index.tsx index 8895253a9f..cc3a4217dc 100644 --- a/web/app/components/workflow-app/index.tsx +++ b/web/app/components/workflow-app/index.tsx @@ -1,6 +1,7 @@ 'use client' import { + useEffect, useMemo, } from 'react' import useSWR from 'swr' @@ -10,6 +11,7 @@ import { import { useWorkflowInit, } from './hooks' +import { useWorkflowStore } from '@/app/components/workflow/store' import { initialEdges, initialNodes, @@ -32,9 +34,24 @@ const WorkflowAppWithAdditionalContext = () => { data, isLoading, } = useWorkflowInit() + const workflowStore = useWorkflowStore() const { isLoadingCurrentWorkspace, currentWorkspace } = useAppContext() const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig) + // Cleanup on unmount + useEffect(() => { + return () => { + // Reset the loaded flag when component unmounts + workflowStore.setState({ isWorkflowDataLoaded: false }) + + // Cancel any pending debounced sync operations + const { debouncedSyncWorkflowDraft } = workflowStore.getState() + // The debounced function from lodash has a cancel method + if (debouncedSyncWorkflowDraft && 'cancel' in debouncedSyncWorkflowDraft) + (debouncedSyncWorkflowDraft as any).cancel() + } + }, [workflowStore]) + const nodesData = useMemo(() => { if (data) return initialNodes(data.graph.nodes, data.graph.edges) 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 ec28debee2..501671fe32 100644 --- a/web/app/components/workflow/store/workflow/workflow-draft-slice.ts +++ b/web/app/components/workflow/store/workflow/workflow-draft-slice.ts @@ -21,6 +21,8 @@ export type WorkflowDraftSliceShape = { setSyncWorkflowDraftHash: (hash: string) => void isSyncingWorkflowDraft: boolean setIsSyncingWorkflowDraft: (isSyncingWorkflowDraft: boolean) => void + isWorkflowDataLoaded: boolean + setIsWorkflowDataLoaded: (loaded: boolean) => void } export const createWorkflowDraftSlice: StateCreator = set => ({ @@ -33,4 +35,6 @@ export const createWorkflowDraftSlice: StateCreator = s setSyncWorkflowDraftHash: syncWorkflowDraftHash => set(() => ({ syncWorkflowDraftHash })), isSyncingWorkflowDraft: false, setIsSyncingWorkflowDraft: isSyncingWorkflowDraft => set(() => ({ isSyncingWorkflowDraft })), + isWorkflowDataLoaded: false, + setIsWorkflowDataLoaded: loaded => set(() => ({ isWorkflowDataLoaded: loaded })), })