From a1ab87107b2a9d12a335b111a8ef6b5d32860ba6 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Fri, 10 May 2024 14:48:20 +0800 Subject: [PATCH] chore: workflow sync with hash (#4250) --- .../workflow/hooks/use-nodes-sync-draft.ts | 43 ++++++++++++++----- .../hooks/use-workflow-interactions.ts | 26 +++++++++-- .../workflow/hooks/use-workflow-run.ts | 4 +- .../components/workflow/hooks/use-workflow.ts | 4 +- web/app/components/workflow/index.tsx | 8 +++- web/app/components/workflow/store.ts | 4 ++ web/service/workflow.ts | 6 +-- web/types/workflow.ts | 1 + 8 files changed, 74 insertions(+), 22 deletions(-) diff --git a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts index d392da21fc..05a4dccfde 100644 --- a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts @@ -7,6 +7,7 @@ import { useWorkflowStore, } from '../store' import { BlockEnum } from '../types' +import { useWorkflowUpdate } from '../hooks' import { useNodesReadOnly } from './use-workflow' import { syncWorkflowDraft } from '@/service/workflow' import { useFeaturesStore } from '@/app/components/base/features/hooks' @@ -17,19 +18,23 @@ export const useNodesSyncDraft = () => { const workflowStore = useWorkflowStore() const featuresStore = useFeaturesStore() const { getNodesReadOnly } = useNodesReadOnly() + const { handleRefreshWorkflowDraft } = useWorkflowUpdate() const debouncedSyncWorkflowDraft = useStore(s => s.debouncedSyncWorkflowDraft) const params = useParams() - const getPostParams = useCallback((appIdParams?: string) => { + const getPostParams = useCallback(() => { const { getNodes, edges, transform, } = store.getState() const [x, y, zoom] = transform - const appId = workflowStore.getState().appId + const { + appId, + syncWorkflowDraftHash, + } = workflowStore.getState() - if (appId || appIdParams) { + if (appId) { const nodes = getNodes() const hasStartNode = nodes.find(node => node.data.type === BlockEnum.Start) @@ -54,7 +59,7 @@ export const useNodesSyncDraft = () => { }) }) return { - url: `/apps/${appId || appIdParams}/workflows/draft`, + url: `/apps/${appId}/workflows/draft`, params: { graph: { nodes: producedNodes, @@ -75,6 +80,7 @@ export const useNodesSyncDraft = () => { sensitive_word_avoidance: features.moderation, file_upload: features.file, }, + hash: syncWorkflowDraftHash, }, } } @@ -93,23 +99,38 @@ export const useNodesSyncDraft = () => { } }, [getPostParams, params.appId, getNodesReadOnly]) - const doSyncWorkflowDraft = useCallback(async (appId?: string) => { + const doSyncWorkflowDraft = useCallback(async (notRefreshWhenSyncError?: boolean) => { if (getNodesReadOnly()) return - const postParams = getPostParams(appId) + const postParams = getPostParams() if (postParams) { - const res = await syncWorkflowDraft(postParams) - workflowStore.getState().setDraftUpdatedAt(res.updated_at) + const { + setSyncWorkflowDraftHash, + setDraftUpdatedAt, + } = workflowStore.getState() + try { + const res = await syncWorkflowDraft(postParams) + setSyncWorkflowDraftHash(res.hash) + setDraftUpdatedAt(res.updated_at) + } + catch (error: any) { + if (error && error.json && !error.bodyUsed) { + error.json().then((err: any) => { + if (err.code === 'draft_workflow_not_sync' && !notRefreshWhenSyncError) + handleRefreshWorkflowDraft() + }) + } + } } - }, [workflowStore, getPostParams, getNodesReadOnly]) + }, [workflowStore, getPostParams, getNodesReadOnly, handleRefreshWorkflowDraft]) - const handleSyncWorkflowDraft = useCallback((sync?: boolean, appId?: string) => { + const handleSyncWorkflowDraft = useCallback((sync?: boolean, notRefreshWhenSyncError?: boolean) => { if (getNodesReadOnly()) return if (sync) - doSyncWorkflowDraft(appId) + doSyncWorkflowDraft(notRefreshWhenSyncError) else debouncedSyncWorkflowDraft(doSyncWorkflowDraft) }, [debouncedSyncWorkflowDraft, doSyncWorkflowDraft, getNodesReadOnly]) diff --git a/web/app/components/workflow/hooks/use-workflow-interactions.ts b/web/app/components/workflow/hooks/use-workflow-interactions.ts index 332f4ffcd1..5d5e981460 100644 --- a/web/app/components/workflow/hooks/use-workflow-interactions.ts +++ b/web/app/components/workflow/hooks/use-workflow-interactions.ts @@ -10,13 +10,12 @@ import { import { useEdgesInteractions } from './use-edges-interactions' import { useNodesInteractions } from './use-nodes-interactions' import { useEventEmitterContextContext } from '@/context/event-emitter' +import { fetchWorkflowDraft } from '@/service/workflow' export const useWorkflowInteractions = () => { - const reactflow = useReactFlow() const workflowStore = useWorkflowStore() const { handleNodeCancelRunningStatus } = useNodesInteractions() const { handleEdgeCancelRunningStatus } = useEdgesInteractions() - const { eventEmitter } = useEventEmitterContextContext() const handleCancelDebugAndPreviewPanel = useCallback(() => { workflowStore.setState({ @@ -27,6 +26,16 @@ export const useWorkflowInteractions = () => { handleEdgeCancelRunningStatus() }, [workflowStore, handleNodeCancelRunningStatus, handleEdgeCancelRunningStatus]) + return { + handleCancelDebugAndPreviewPanel, + } +} + +export const useWorkflowUpdate = () => { + const reactflow = useReactFlow() + const workflowStore = useWorkflowStore() + const { eventEmitter } = useEventEmitterContextContext() + const handleUpdateWorkflowCanvas = useCallback((payload: WorkflowDataUpdator) => { const { nodes, @@ -44,8 +53,19 @@ export const useWorkflowInteractions = () => { setViewport(viewport) }, [eventEmitter, reactflow]) + const handleRefreshWorkflowDraft = useCallback(() => { + const { + appId, + setSyncWorkflowDraftHash, + } = workflowStore.getState() + fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => { + handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdator) + setSyncWorkflowDraftHash(response.hash) + }) + }, [handleUpdateWorkflowCanvas, workflowStore]) + return { - handleCancelDebugAndPreviewPanel, handleUpdateWorkflowCanvas, + handleRefreshWorkflowDraft, } } diff --git a/web/app/components/workflow/hooks/use-workflow-run.ts b/web/app/components/workflow/hooks/use-workflow-run.ts index 376fcf5561..17f4371414 100644 --- a/web/app/components/workflow/hooks/use-workflow-run.ts +++ b/web/app/components/workflow/hooks/use-workflow-run.ts @@ -10,7 +10,7 @@ import { NodeRunningStatus, WorkflowRunningStatus, } from '../types' -import { useWorkflowInteractions } from './use-workflow-interactions' +import { useWorkflowUpdate } from './use-workflow-interactions' import { useStore as useAppStore } from '@/app/components/app/store' import type { IOtherOptions } from '@/service/base' import { ssePost } from '@/service/base' @@ -26,7 +26,7 @@ export const useWorkflowRun = () => { const reactflow = useReactFlow() const featuresStore = useFeaturesStore() const { doSyncWorkflowDraft } = useNodesSyncDraft() - const { handleUpdateWorkflowCanvas } = useWorkflowInteractions() + const { handleUpdateWorkflowCanvas } = useWorkflowUpdate() const handleBackupDraft = useCallback(() => { const { diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index 1ea5af5ec7..bec457d0ef 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -385,6 +385,7 @@ export const useWorkflowInit = () => { } = useWorkflowTemplate() const { handleFetchAllTools } = useFetchToolsData() const appDetail = useAppStore(state => state.appDetail)! + const setSyncWorkflowDraftHash = useStore(s => s.setSyncWorkflowDraftHash) const [data, setData] = useState() const [isLoading, setIsLoading] = useState(true) workflowStore.setState({ appId: appDetail.id }) @@ -394,6 +395,7 @@ export const useWorkflowInit = () => { const res = await fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`) setData(res) + setSyncWorkflowDraftHash(res.hash) setIsLoading(false) } catch (error: any) { @@ -418,7 +420,7 @@ export const useWorkflowInit = () => { }) } } - }, [appDetail, nodesTemplate, edgesTemplate, workflowStore]) + }, [appDetail, nodesTemplate, edgesTemplate, workflowStore, setSyncWorkflowDraftHash]) useEffect(() => { handleGetInitialWorkflowData() diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index b6f652570e..0ad8e7b4f3 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -42,6 +42,7 @@ import { useWorkflowInit, useWorkflowReadOnly, useWorkflowStartRun, + useWorkflowUpdate, } from './hooks' import Header from './header' import CustomNode from './nodes' @@ -119,14 +120,17 @@ const Workflow: FC = memo(({ useEffect(() => { return () => { - handleSyncWorkflowDraft(true) + handleSyncWorkflowDraft(true, true) } }, []) + const { handleRefreshWorkflowDraft } = useWorkflowUpdate() const handleSyncWorkflowDraftWhenPageClose = useCallback(() => { if (document.visibilityState === 'hidden') syncWorkflowDraftWhenPageClose() - }, [syncWorkflowDraftWhenPageClose]) + else if (document.visibilityState === 'visible') + handleRefreshWorkflowDraft() + }, [syncWorkflowDraftWhenPageClose, handleRefreshWorkflowDraft]) useEffect(() => { document.addEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose) diff --git a/web/app/components/workflow/store.ts b/web/app/components/workflow/store.ts index 4ee28da4bc..0f328134c1 100644 --- a/web/app/components/workflow/store.ts +++ b/web/app/components/workflow/store.ts @@ -96,6 +96,8 @@ type Shape = { setNodeMenu: (nodeMenu: Shape['nodeMenu']) => void mousePosition: { pageX: number; pageY: number; elementX: number; elementY: number } setMousePosition: (mousePosition: Shape['mousePosition']) => void + syncWorkflowDraftHash: string + setSyncWorkflowDraftHash: (hash: string) => void } export const createWorkflowStore = () => { @@ -164,6 +166,8 @@ export const createWorkflowStore = () => { setNodeMenu: nodeMenu => set(() => ({ nodeMenu })), mousePosition: { pageX: 0, pageY: 0, elementX: 0, elementY: 0 }, setMousePosition: mousePosition => set(() => ({ mousePosition })), + syncWorkflowDraftHash: '', + setSyncWorkflowDraftHash: syncWorkflowDraftHash => set(() => ({ syncWorkflowDraftHash })), })) } diff --git a/web/service/workflow.ts b/web/service/workflow.ts index 0e3aa3a443..affe4aedf9 100644 --- a/web/service/workflow.ts +++ b/web/service/workflow.ts @@ -9,12 +9,12 @@ import type { } from '@/types/workflow' import type { BlockEnum } from '@/app/components/workflow/types' -export const fetchWorkflowDraft: Fetcher = (url) => { - return get(url, {}, { silent: true }) +export const fetchWorkflowDraft = (url: string) => { + return get(url, {}, { silent: true }) as Promise } export const syncWorkflowDraft = ({ url, params }: { url: string; params: Pick }) => { - return post(url, { body: params }) + return post(url, { body: params }, { silent: true }) } export const fetchNodesDefaultConfigs: Fetcher = (url) => { diff --git a/web/types/workflow.ts b/web/types/workflow.ts index 0db37a9f0d..b5c49b353f 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -48,6 +48,7 @@ export type FetchWorkflowDraftResponse = { name: string email: string } + hash: string updated_at: number }