Browse Source

fix: workflow restore (#3711)

zxhlyh 1 year ago
parent
commit
83caffe000

+ 2 - 0
web/app/components/workflow/constants.ts

@@ -402,3 +402,5 @@ export const TOOL_OUTPUT_STRUCT: Var[] = [
     type: VarType.arrayFile,
   },
 ]
+
+export const WORKFLOW_DATA_UPDATE = 'WORKFLOW_DATA_UPDATE'

+ 1 - 0
web/app/components/workflow/header/index.tsx

@@ -73,6 +73,7 @@ const Header: FC = () => {
 
   const handleRestore = useCallback(() => {
     workflowStore.setState({ isRestoring: false })
+    workflowStore.setState({ backupDraft: undefined })
     handleSyncWorkflowDraft(true)
   }, [handleSyncWorkflowDraft, workflowStore])
 

+ 16 - 5
web/app/components/workflow/hooks/use-workflow.ts

@@ -19,6 +19,7 @@ import type {
   Viewport,
 } from 'reactflow'
 import {
+  changeNodesAndEdgesId,
   getLayoutByDagre,
   initialEdges,
   initialNodes,
@@ -39,6 +40,7 @@ import {
 import {
   AUTO_LAYOUT_OFFSET,
   SUPPORT_OUTPUT_VARS_NODE,
+  WORKFLOW_DATA_UPDATE,
 } from '../constants'
 import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
 import { useNodesExtraData } from './use-nodes-data'
@@ -56,6 +58,8 @@ import {
   fetchAllCustomTools,
 } from '@/service/tools'
 import I18n from '@/context/i18n'
+import { useEventEmitterContextContext } from '@/context/event-emitter'
+
 export const useIsChatMode = () => {
   const appDetail = useAppStore(s => s.appDetail)
 
@@ -69,6 +73,7 @@ export const useWorkflow = () => {
   const workflowStore = useWorkflowStore()
   const nodesExtraData = useNodesExtraData()
   const { handleSyncWorkflowDraft } = useNodesSyncDraft()
+  const { eventEmitter } = useEventEmitterContextContext()
 
   const handleLayout = useCallback(async () => {
     workflowStore.setState({ nodeAnimation: true })
@@ -314,15 +319,21 @@ export const useWorkflow = () => {
   }, [locale])
 
   const renderTreeFromRecord = useCallback((nodes: Node[], edges: Edge[], viewport?: Viewport) => {
-    const { setNodes } = store.getState()
-    const { setViewport, setEdges } = reactflow
+    const { setViewport } = reactflow
+
+    const [newNodes, newEdges] = changeNodesAndEdgesId(nodes, edges)
 
-    setNodes(initialNodes(nodes, edges))
-    setEdges(initialEdges(edges, nodes))
+    eventEmitter?.emit({
+      type: WORKFLOW_DATA_UPDATE,
+      payload: {
+        nodes: initialNodes(newNodes, newEdges),
+        edges: initialEdges(newEdges, newNodes),
+      },
+    } as any)
 
     if (viewport)
       setViewport(viewport)
-  }, [store, reactflow])
+  }, [reactflow, eventEmitter])
 
   const getNode = useCallback((nodeId?: string) => {
     const { getNodes } = store.getState()

+ 17 - 2
web/app/components/workflow/index.tsx

@@ -14,6 +14,8 @@ import {
 import ReactFlow, {
   Background,
   ReactFlowProvider,
+  useEdgesState,
+  useNodesState,
   useOnViewportChange,
 } from 'reactflow'
 import type { Viewport } from 'reactflow'
@@ -46,9 +48,11 @@ import {
   initialEdges,
   initialNodes,
 } from './utils'
+import { WORKFLOW_DATA_UPDATE } from './constants'
 import Loading from '@/app/components/base/loading'
 import { FeaturesProvider } from '@/app/components/base/features'
 import type { Features as FeaturesData } from '@/app/components/base/features/types'
+import { useEventEmitterContextContext } from '@/context/event-emitter'
 
 const nodeTypes = {
   custom: CustomNode,
@@ -63,10 +67,12 @@ type WorkflowProps = {
   viewport?: Viewport
 }
 const Workflow: FC<WorkflowProps> = memo(({
-  nodes,
-  edges,
+  nodes: originalNodes,
+  edges: originalEdges,
   viewport,
 }) => {
+  const [nodes, setNodes] = useNodesState(originalNodes)
+  const [edges, setEdges] = useEdgesState(originalEdges)
   const showFeaturesPanel = useStore(state => state.showFeaturesPanel)
   const nodeAnimation = useStore(s => s.nodeAnimation)
   const {
@@ -76,6 +82,15 @@ const Workflow: FC<WorkflowProps> = memo(({
   const { workflowReadOnly } = useWorkflowReadOnly()
   const { nodesReadOnly } = useNodesReadOnly()
 
+  const { eventEmitter } = useEventEmitterContextContext()
+
+  eventEmitter?.useSubscription((v: any) => {
+    if (v.type === WORKFLOW_DATA_UPDATE) {
+      setNodes(v.payload.nodes)
+      setEdges(v.payload.edges)
+    }
+  })
+
   useEffect(() => {
     setAutoFreeze(false)
 

+ 5 - 2
web/app/components/workflow/nodes/_base/components/node-handle.tsx

@@ -16,6 +16,7 @@ import type { ToolDefaultValue } from '../../../block-selector/types'
 import {
   useNodesExtraData,
   useNodesInteractions,
+  useNodesReadOnly,
 } from '../../../hooks'
 import { useStore } from '../../../store'
 
@@ -35,6 +36,7 @@ export const NodeTargetHandle = memo(({
   const [open, setOpen] = useState(false)
   const { handleNodeAdd } = useNodesInteractions()
   const nodesExtraData = useNodesExtraData()
+  const { getNodesReadOnly } = useNodesReadOnly()
   const connected = data._connectedTargetHandleIds?.includes(handleId)
   const availablePrevNodes = nodesExtraData[data.type].availablePrevNodes
   const isConnectable = !!availablePrevNodes.length
@@ -78,7 +80,7 @@ export const NodeTargetHandle = memo(({
         onClick={handleHandleClick}
       >
         {
-          !connected && isConnectable && !data._isInvalidConnection && (
+          !connected && isConnectable && !data._isInvalidConnection && !getNodesReadOnly() && (
             <BlockSelector
               open={open}
               onOpenChange={handleOpenChange}
@@ -113,6 +115,7 @@ export const NodeSourceHandle = memo(({
   const [open, setOpen] = useState(false)
   const { handleNodeAdd } = useNodesInteractions()
   const nodesExtraData = useNodesExtraData()
+  const { getNodesReadOnly } = useNodesReadOnly()
   const availableNextNodes = nodesExtraData[data.type].availableNextNodes
   const isConnectable = !!availableNextNodes.length
   const connected = data._connectedSourceHandleIds?.includes(handleId)
@@ -159,7 +162,7 @@ export const NodeSourceHandle = memo(({
         onClick={handleHandleClick}
       >
         {
-          !connected && isConnectable && !data._isInvalidConnection && (
+          !connected && isConnectable && !data._isInvalidConnection && !getNodesReadOnly() && (
             <BlockSelector
               open={open}
               onOpenChange={handleOpenChange}

+ 2 - 2
web/app/components/workflow/nodes/_base/node.tsx

@@ -58,7 +58,7 @@ const BaseNode: FC<BaseNodeProps> = ({
         `}
       >
         {
-          data.type !== BlockEnum.VariableAssigner && !data._runningStatus && !nodesReadOnly && (
+          data.type !== BlockEnum.VariableAssigner && !data._runningStatus && (
             <NodeTargetHandle
               id={id}
               data={data}
@@ -68,7 +68,7 @@ const BaseNode: FC<BaseNodeProps> = ({
           )
         }
         {
-          data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._runningStatus && !nodesReadOnly && (
+          data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._runningStatus && (
             <NodeSourceHandle
               id={id}
               data={data}

+ 26 - 0
web/app/components/workflow/utils.ts

@@ -4,6 +4,7 @@ import {
   getOutgoers,
 } from 'reactflow'
 import dagre from 'dagre'
+import { v4 as uuid4 } from 'uuid'
 import {
   cloneDeep,
   uniqBy,
@@ -331,3 +332,28 @@ export const getToolCheckParams = (
     language,
   }
 }
+
+export const changeNodesAndEdgesId = (nodes: Node[], edges: Edge[]) => {
+  const idMap = nodes.reduce((acc, node) => {
+    acc[node.id] = uuid4()
+
+    return acc
+  }, {} as Record<string, string>)
+
+  const newNodes = nodes.map((node) => {
+    return {
+      ...node,
+      id: idMap[node.id],
+    }
+  })
+
+  const newEdges = edges.map((edge) => {
+    return {
+      ...edge,
+      source: idMap[edge.source],
+      target: idMap[edge.target],
+    }
+  })
+
+  return [newNodes, newEdges] as [Node[], Edge[]]
+}

+ 2 - 0
web/package.json

@@ -83,6 +83,7 @@
     "sortablejs": "^1.15.0",
     "swr": "^2.1.0",
     "use-context-selector": "^1.4.1",
+    "uuid": "^9.0.1",
     "zustand": "^4.5.1"
   },
   "devDependencies": {
@@ -104,6 +105,7 @@
     "@types/react-window-infinite-loader": "^1.0.6",
     "@types/recordrtc": "^5.6.11",
     "@types/sortablejs": "^1.15.1",
+    "@types/uuid": "^9.0.8",
     "autoprefixer": "^10.4.14",
     "cross-env": "^7.0.3",
     "eslint": "^8.36.0",

File diff suppressed because it is too large
+ 583 - 307
web/yarn.lock