feat: Parallel Execution of Nodes in Workflows (#8192)
Co-authored-by: StyleZhang <jasonapring2015@outlook.com> Co-authored-by: Yi <yxiaoisme@gmail.com> Co-authored-by: -LAN- <laipz8200@outlook.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
useReactFlow,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import { unionBy } from 'lodash-es'
|
||||
import type { ToolDefaultValue } from '../block-selector/types'
|
||||
import type {
|
||||
Edge,
|
||||
@@ -25,6 +26,7 @@ import type {
|
||||
import { BlockEnum } from '../types'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import {
|
||||
CUSTOM_EDGE,
|
||||
ITERATION_CHILDREN_Z_INDEX,
|
||||
ITERATION_PADDING,
|
||||
NODES_INITIAL_DATA,
|
||||
@@ -40,6 +42,7 @@ import {
|
||||
} from '../utils'
|
||||
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
|
||||
import type { IterationNodeType } from '../nodes/iteration/types'
|
||||
import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
|
||||
import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types'
|
||||
import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions'
|
||||
import { useWorkflowHistoryStore } from '../workflow-history-store'
|
||||
@@ -60,6 +63,7 @@ export const useNodesInteractions = () => {
|
||||
const { store: workflowHistoryStore } = useWorkflowHistoryStore()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const {
|
||||
checkNestedParallelLimit,
|
||||
getAfterNodesInSameBranch,
|
||||
} = useWorkflow()
|
||||
const { getNodesReadOnly } = useNodesReadOnly()
|
||||
@@ -79,7 +83,7 @@ export const useNodesInteractions = () => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
if (node.data.isIterationStart || node.type === CUSTOM_NOTE_NODE)
|
||||
if (node.type === CUSTOM_ITERATION_START_NODE || node.type === CUSTOM_NOTE_NODE)
|
||||
return
|
||||
|
||||
dragNodeStartPosition.current = { x: node.position.x, y: node.position.y }
|
||||
@@ -89,7 +93,7 @@ export const useNodesInteractions = () => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
if (node.data.isIterationStart)
|
||||
if (node.type === CUSTOM_ITERATION_START_NODE)
|
||||
return
|
||||
|
||||
const {
|
||||
@@ -156,7 +160,7 @@ export const useNodesInteractions = () => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
if (node.type === CUSTOM_NOTE_NODE)
|
||||
if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE)
|
||||
return
|
||||
|
||||
const {
|
||||
@@ -207,13 +211,30 @@ export const useNodesInteractions = () => {
|
||||
})
|
||||
})
|
||||
setEdges(newEdges)
|
||||
const connectedEdges = getConnectedEdges([node], edges).filter(edge => edge.target === node.id)
|
||||
|
||||
const targetNodes: Node[] = []
|
||||
for (let i = 0; i < connectedEdges.length; i++) {
|
||||
const sourceConnectedEdges = getConnectedEdges([{ id: connectedEdges[i].source } as Node], edges).filter(edge => edge.source === connectedEdges[i].source && edge.sourceHandle === connectedEdges[i].sourceHandle)
|
||||
targetNodes.push(...sourceConnectedEdges.map(edge => nodes.find(n => n.id === edge.target)!))
|
||||
}
|
||||
const uniqTargetNodes = unionBy(targetNodes, 'id')
|
||||
if (uniqTargetNodes.length > 1) {
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((n) => {
|
||||
if (uniqTargetNodes.some(targetNode => n.id === targetNode.id))
|
||||
n.data._inParallelHovering = true
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}
|
||||
}, [store, workflowStore, getNodesReadOnly])
|
||||
|
||||
const handleNodeLeave = useCallback<NodeMouseHandler>((_, node) => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
if (node.type === CUSTOM_NOTE_NODE)
|
||||
if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE)
|
||||
return
|
||||
|
||||
const {
|
||||
@@ -229,6 +250,7 @@ export const useNodesInteractions = () => {
|
||||
const newNodes = produce(getNodes(), (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.data._isEntering = false
|
||||
node.data._inParallelHovering = false
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
@@ -287,6 +309,8 @@ export const useNodesInteractions = () => {
|
||||
}, [store, handleSyncWorkflowDraft])
|
||||
|
||||
const handleNodeClick = useCallback<NodeMouseHandler>((_, node) => {
|
||||
if (node.type === CUSTOM_ITERATION_START_NODE)
|
||||
return
|
||||
handleNodeSelect(node.id)
|
||||
}, [handleNodeSelect])
|
||||
|
||||
@@ -314,25 +338,15 @@ export const useNodesInteractions = () => {
|
||||
if (targetNode?.parentId !== sourceNode?.parentId)
|
||||
return
|
||||
|
||||
if (targetNode?.data.isIterationStart)
|
||||
return
|
||||
|
||||
if (sourceNode?.type === CUSTOM_NOTE_NODE || targetNode?.type === CUSTOM_NOTE_NODE)
|
||||
return
|
||||
|
||||
const needDeleteEdges = edges.filter((edge) => {
|
||||
if (
|
||||
(edge.source === source && edge.sourceHandle === sourceHandle)
|
||||
|| (edge.target === target && edge.targetHandle === targetHandle && targetNode?.data.type !== BlockEnum.VariableAssigner && targetNode?.data.type !== BlockEnum.VariableAggregator)
|
||||
)
|
||||
return true
|
||||
if (edges.find(edge => edge.source === source && edge.sourceHandle === sourceHandle && edge.target === target && edge.targetHandle === targetHandle))
|
||||
return
|
||||
|
||||
return false
|
||||
})
|
||||
const needDeleteEdgesIds = needDeleteEdges.map(edge => edge.id)
|
||||
const newEdge = {
|
||||
id: `${source}-${sourceHandle}-${target}-${targetHandle}`,
|
||||
type: 'custom',
|
||||
type: CUSTOM_EDGE,
|
||||
source: source!,
|
||||
target: target!,
|
||||
sourceHandle,
|
||||
@@ -347,7 +361,6 @@ export const useNodesInteractions = () => {
|
||||
}
|
||||
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
||||
[
|
||||
...needDeleteEdges.map(edge => ({ type: 'remove', edge })),
|
||||
{ type: 'add', edge: newEdge },
|
||||
],
|
||||
nodes,
|
||||
@@ -362,19 +375,26 @@ export const useNodesInteractions = () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const filtered = draft.filter(edge => !needDeleteEdgesIds.includes(edge.id))
|
||||
|
||||
filtered.push(newEdge)
|
||||
|
||||
return filtered
|
||||
draft.push(newEdge)
|
||||
})
|
||||
setEdges(newEdges)
|
||||
|
||||
handleSyncWorkflowDraft()
|
||||
saveStateToHistory(WorkflowHistoryEvent.NodeConnect)
|
||||
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory])
|
||||
if (checkNestedParallelLimit(newNodes, newEdges, targetNode?.parentId)) {
|
||||
setNodes(newNodes)
|
||||
setEdges(newEdges)
|
||||
|
||||
handleSyncWorkflowDraft()
|
||||
saveStateToHistory(WorkflowHistoryEvent.NodeConnect)
|
||||
}
|
||||
else {
|
||||
const {
|
||||
setConnectingNodePayload,
|
||||
setEnteringNodePayload,
|
||||
} = workflowStore.getState()
|
||||
setConnectingNodePayload(undefined)
|
||||
setEnteringNodePayload(undefined)
|
||||
}
|
||||
}, [getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, saveStateToHistory, checkNestedParallelLimit])
|
||||
|
||||
const handleNodeConnectStart = useCallback<OnConnectStart>((_, { nodeId, handleType, handleId }) => {
|
||||
if (getNodesReadOnly())
|
||||
@@ -393,14 +413,12 @@ export const useNodesInteractions = () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (!node.data.isIterationStart) {
|
||||
setConnectingNodePayload({
|
||||
nodeId,
|
||||
nodeType: node.data.type,
|
||||
handleType,
|
||||
handleId,
|
||||
})
|
||||
}
|
||||
setConnectingNodePayload({
|
||||
nodeId,
|
||||
nodeType: node.data.type,
|
||||
handleType,
|
||||
handleId,
|
||||
})
|
||||
}
|
||||
}, [store, workflowStore, getNodesReadOnly])
|
||||
|
||||
@@ -510,6 +528,12 @@ export const useNodesInteractions = () => {
|
||||
return handleNodeDelete(nodeId)
|
||||
}
|
||||
else {
|
||||
if (iterationChildren.length === 1) {
|
||||
handleNodeDelete(iterationChildren[0].id)
|
||||
handleNodeDelete(nodeId)
|
||||
|
||||
return
|
||||
}
|
||||
const { setShowConfirm, showConfirm } = workflowStore.getState()
|
||||
|
||||
if (!showConfirm) {
|
||||
@@ -541,14 +565,8 @@ export const useNodesInteractions = () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (node.id === currentNode.parentId) {
|
||||
if (node.id === currentNode.parentId)
|
||||
node.data._children = node.data._children?.filter(child => child !== nodeId)
|
||||
|
||||
if (currentNode.id === (node as Node<IterationNodeType>).data.start_node_id) {
|
||||
(node as Node<IterationNodeType>).data.start_node_id = '';
|
||||
(node as Node<IterationNodeType>).data.startNodeType = undefined
|
||||
}
|
||||
}
|
||||
})
|
||||
draft.splice(currentNodeIndex, 1)
|
||||
})
|
||||
@@ -559,7 +577,7 @@ export const useNodesInteractions = () => {
|
||||
setEdges(newEdges)
|
||||
handleSyncWorkflowDraft()
|
||||
|
||||
if (currentNode.type === 'custom-note')
|
||||
if (currentNode.type === CUSTOM_NOTE_NODE)
|
||||
saveStateToHistory(WorkflowHistoryEvent.NoteDelete)
|
||||
|
||||
else
|
||||
@@ -591,7 +609,10 @@ export const useNodesInteractions = () => {
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const nodesWithSameType = nodes.filter(node => node.data.type === nodeType)
|
||||
const newNode = generateNewNode({
|
||||
const {
|
||||
newNode,
|
||||
newIterationStartNode,
|
||||
} = generateNewNode({
|
||||
data: {
|
||||
...NODES_INITIAL_DATA[nodeType],
|
||||
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`),
|
||||
@@ -627,7 +648,7 @@ export const useNodesInteractions = () => {
|
||||
|
||||
const newEdge: Edge = {
|
||||
id: `${prevNodeId}-${prevNodeSourceHandle}-${newNode.id}-${targetHandle}`,
|
||||
type: 'custom',
|
||||
type: CUSTOM_EDGE,
|
||||
source: prevNodeId,
|
||||
sourceHandle: prevNodeSourceHandle,
|
||||
target: newNode.id,
|
||||
@@ -662,8 +683,10 @@ export const useNodesInteractions = () => {
|
||||
node.data._children?.push(newNode.id)
|
||||
})
|
||||
draft.push(newNode)
|
||||
if (newIterationStartNode)
|
||||
draft.push(newIterationStartNode)
|
||||
})
|
||||
setNodes(newNodes)
|
||||
|
||||
if (newNode.data.type === BlockEnum.VariableAssigner || newNode.data.type === BlockEnum.VariableAggregator) {
|
||||
const { setShowAssignVariablePopup } = workflowStore.getState()
|
||||
|
||||
@@ -687,7 +710,14 @@ export const useNodesInteractions = () => {
|
||||
})
|
||||
draft.push(newEdge)
|
||||
})
|
||||
setEdges(newEdges)
|
||||
|
||||
if (checkNestedParallelLimit(newNodes, newEdges, prevNode.parentId)) {
|
||||
setNodes(newNodes)
|
||||
setEdges(newEdges)
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (!prevNodeId && nextNodeId) {
|
||||
const nextNodeIndex = nodes.findIndex(node => node.id === nextNodeId)
|
||||
@@ -706,15 +736,13 @@ export const useNodesInteractions = () => {
|
||||
newNode.data.iteration_id = nextNode.parentId
|
||||
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
|
||||
}
|
||||
if (nextNode.data.isIterationStart)
|
||||
newNode.data.isIterationStart = true
|
||||
|
||||
let newEdge
|
||||
|
||||
if ((nodeType !== BlockEnum.IfElse) && (nodeType !== BlockEnum.QuestionClassifier)) {
|
||||
newEdge = {
|
||||
id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`,
|
||||
type: 'custom',
|
||||
type: CUSTOM_EDGE,
|
||||
source: newNode.id,
|
||||
sourceHandle,
|
||||
target: nextNodeId,
|
||||
@@ -763,13 +791,11 @@ export const useNodesInteractions = () => {
|
||||
node.data.start_node_id = newNode.id
|
||||
node.data.startNodeType = newNode.data.type
|
||||
}
|
||||
|
||||
if (node.id === nextNodeId && node.data.isIterationStart)
|
||||
node.data.isIterationStart = false
|
||||
})
|
||||
draft.push(newNode)
|
||||
if (newIterationStartNode)
|
||||
draft.push(newIterationStartNode)
|
||||
})
|
||||
setNodes(newNodes)
|
||||
if (newEdge) {
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
draft.forEach((item) => {
|
||||
@@ -780,7 +806,21 @@ export const useNodesInteractions = () => {
|
||||
})
|
||||
draft.push(newEdge)
|
||||
})
|
||||
setEdges(newEdges)
|
||||
|
||||
if (checkNestedParallelLimit(newNodes, newEdges, nextNode.parentId)) {
|
||||
setNodes(newNodes)
|
||||
setEdges(newEdges)
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (checkNestedParallelLimit(newNodes, edges))
|
||||
setNodes(newNodes)
|
||||
|
||||
else
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (prevNodeId && nextNodeId) {
|
||||
@@ -804,7 +844,7 @@ export const useNodesInteractions = () => {
|
||||
const currentEdgeIndex = edges.findIndex(edge => edge.source === prevNodeId && edge.target === nextNodeId)
|
||||
const newPrevEdge = {
|
||||
id: `${prevNodeId}-${prevNodeSourceHandle}-${newNode.id}-${targetHandle}`,
|
||||
type: 'custom',
|
||||
type: CUSTOM_EDGE,
|
||||
source: prevNodeId,
|
||||
sourceHandle: prevNodeSourceHandle,
|
||||
target: newNode.id,
|
||||
@@ -822,7 +862,7 @@ export const useNodesInteractions = () => {
|
||||
if (nodeType !== BlockEnum.IfElse && nodeType !== BlockEnum.QuestionClassifier) {
|
||||
newNextEdge = {
|
||||
id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`,
|
||||
type: 'custom',
|
||||
type: CUSTOM_EDGE,
|
||||
source: newNode.id,
|
||||
sourceHandle,
|
||||
target: nextNodeId,
|
||||
@@ -865,6 +905,8 @@ export const useNodesInteractions = () => {
|
||||
node.data._children?.push(newNode.id)
|
||||
})
|
||||
draft.push(newNode)
|
||||
if (newIterationStartNode)
|
||||
draft.push(newIterationStartNode)
|
||||
})
|
||||
setNodes(newNodes)
|
||||
if (newNode.data.type === BlockEnum.VariableAssigner || newNode.data.type === BlockEnum.VariableAggregator) {
|
||||
@@ -898,7 +940,7 @@ export const useNodesInteractions = () => {
|
||||
}
|
||||
handleSyncWorkflowDraft()
|
||||
saveStateToHistory(WorkflowHistoryEvent.NodeAdd)
|
||||
}, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch])
|
||||
}, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch, checkNestedParallelLimit])
|
||||
|
||||
const handleNodeChange = useCallback((
|
||||
currentNodeId: string,
|
||||
@@ -919,7 +961,10 @@ export const useNodesInteractions = () => {
|
||||
const currentNode = nodes.find(node => node.id === currentNodeId)!
|
||||
const connectedEdges = getConnectedEdges([currentNode], edges)
|
||||
const nodesWithSameType = nodes.filter(node => node.data.type === nodeType)
|
||||
const newCurrentNode = generateNewNode({
|
||||
const {
|
||||
newNode: newCurrentNode,
|
||||
newIterationStartNode,
|
||||
} = generateNewNode({
|
||||
data: {
|
||||
...NODES_INITIAL_DATA[nodeType],
|
||||
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`),
|
||||
@@ -929,7 +974,6 @@ export const useNodesInteractions = () => {
|
||||
selected: currentNode.data.selected,
|
||||
isInIteration: currentNode.data.isInIteration,
|
||||
iteration_id: currentNode.data.iteration_id,
|
||||
isIterationStart: currentNode.data.isIterationStart,
|
||||
},
|
||||
position: {
|
||||
x: currentNode.position.x,
|
||||
@@ -955,18 +999,12 @@ export const useNodesInteractions = () => {
|
||||
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
|
||||
}
|
||||
}
|
||||
if (node.id === currentNode.parentId && currentNode.data.isIterationStart) {
|
||||
node.data._children = [
|
||||
newCurrentNode.id,
|
||||
...(node.data._children || []),
|
||||
].filter(child => child !== currentNodeId)
|
||||
node.data.start_node_id = newCurrentNode.id
|
||||
node.data.startNodeType = newCurrentNode.data.type
|
||||
}
|
||||
})
|
||||
const index = draft.findIndex(node => node.id === currentNodeId)
|
||||
|
||||
draft.splice(index, 1, newCurrentNode)
|
||||
if (newIterationStartNode)
|
||||
draft.push(newIterationStartNode)
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
@@ -1011,7 +1049,7 @@ export const useNodesInteractions = () => {
|
||||
}, [store])
|
||||
|
||||
const handleNodeContextMenu = useCallback((e: MouseEvent, node: Node) => {
|
||||
if (node.type === CUSTOM_NOTE_NODE)
|
||||
if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE)
|
||||
return
|
||||
|
||||
e.preventDefault()
|
||||
@@ -1041,7 +1079,7 @@ export const useNodesInteractions = () => {
|
||||
|
||||
if (nodeId) {
|
||||
// If nodeId is provided, copy that specific node
|
||||
const nodeToCopy = nodes.find(node => node.id === nodeId && node.data.type !== BlockEnum.Start)
|
||||
const nodeToCopy = nodes.find(node => node.id === nodeId && node.data.type !== BlockEnum.Start && node.type !== CUSTOM_ITERATION_START_NODE)
|
||||
if (nodeToCopy)
|
||||
setClipboardElements([nodeToCopy])
|
||||
}
|
||||
@@ -1087,7 +1125,10 @@ export const useNodesInteractions = () => {
|
||||
clipboardElements.forEach((nodeToPaste, index) => {
|
||||
const nodeType = nodeToPaste.data.type
|
||||
|
||||
const newNode = generateNewNode({
|
||||
const {
|
||||
newNode,
|
||||
newIterationStartNode,
|
||||
} = generateNewNode({
|
||||
type: nodeToPaste.type,
|
||||
data: {
|
||||
...NODES_INITIAL_DATA[nodeType],
|
||||
@@ -1106,24 +1147,17 @@ export const useNodesInteractions = () => {
|
||||
zIndex: nodeToPaste.zIndex,
|
||||
})
|
||||
newNode.id = newNode.id + index
|
||||
|
||||
// If only the iteration start node is copied, remove the isIterationStart flag
|
||||
// This new node is movable and can be placed anywhere
|
||||
if (clipboardElements.length === 1 && newNode.data.isIterationStart)
|
||||
newNode.data.isIterationStart = false
|
||||
|
||||
let newChildren: Node[] = []
|
||||
if (nodeToPaste.data.type === BlockEnum.Iteration) {
|
||||
newNode.data._children = [];
|
||||
(newNode.data as IterationNodeType).start_node_id = ''
|
||||
newIterationStartNode!.parentId = newNode.id;
|
||||
(newNode.data as IterationNodeType).start_node_id = newIterationStartNode!.id
|
||||
|
||||
newChildren = handleNodeIterationChildrenCopy(nodeToPaste.id, newNode.id)
|
||||
|
||||
newChildren.forEach((child) => {
|
||||
newNode.data._children?.push(child.id)
|
||||
if (child.data.isIterationStart)
|
||||
(newNode.data as IterationNodeType).start_node_id = child.id
|
||||
})
|
||||
newChildren.push(newIterationStartNode!)
|
||||
}
|
||||
|
||||
nodesToPaste.push(newNode)
|
||||
@@ -1230,6 +1264,42 @@ export const useNodesInteractions = () => {
|
||||
saveStateToHistory(WorkflowHistoryEvent.NodeResize)
|
||||
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory])
|
||||
|
||||
const handleNodeDisconnect = useCallback((nodeId: string) => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const currentNode = nodes.find(node => node.id === nodeId)!
|
||||
const connectedEdges = getConnectedEdges([currentNode], edges)
|
||||
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
||||
connectedEdges.map(edge => ({ type: 'remove', edge })),
|
||||
nodes,
|
||||
)
|
||||
const newNodes = produce(nodes, (draft: Node[]) => {
|
||||
draft.forEach((node) => {
|
||||
if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
|
||||
node.data = {
|
||||
...node.data,
|
||||
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
return draft.filter(edge => !connectedEdges.find(connectedEdge => connectedEdge.id === edge.id))
|
||||
})
|
||||
setEdges(newEdges)
|
||||
handleSyncWorkflowDraft()
|
||||
saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
|
||||
}, [store, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory])
|
||||
|
||||
const handleHistoryBack = useCallback(() => {
|
||||
if (getNodesReadOnly() || getWorkflowReadOnly())
|
||||
return
|
||||
@@ -1282,6 +1352,7 @@ export const useNodesInteractions = () => {
|
||||
handleNodesDuplicate,
|
||||
handleNodesDelete,
|
||||
handleNodeResize,
|
||||
handleNodeDisconnect,
|
||||
handleHistoryBack,
|
||||
handleHistoryForward,
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { useCallback } from 'react'
|
||||
import {
|
||||
getIncomers,
|
||||
useReactFlow,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
@@ -8,6 +9,7 @@ import { v4 as uuidV4 } from 'uuid'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import { useNodesSyncDraft } from '../hooks'
|
||||
import type { Node } from '../types'
|
||||
import {
|
||||
NodeRunningStatus,
|
||||
WorkflowRunningStatus,
|
||||
@@ -140,9 +142,6 @@ export const useWorkflowRun = () => {
|
||||
resultText: '',
|
||||
})
|
||||
|
||||
let isInIteration = false
|
||||
let iterationLength = 0
|
||||
|
||||
let ttsUrl = ''
|
||||
let ttsIsPublic = false
|
||||
if (params.token) {
|
||||
@@ -186,7 +185,7 @@ export const useWorkflowRun = () => {
|
||||
draft.forEach((edge) => {
|
||||
edge.data = {
|
||||
...edge.data,
|
||||
_run: false,
|
||||
_runned: false,
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -249,19 +248,20 @@ export const useWorkflowRun = () => {
|
||||
setEdges,
|
||||
transform,
|
||||
} = store.getState()
|
||||
if (isInIteration) {
|
||||
const nodes = getNodes()
|
||||
const node = nodes.find(node => node.id === data.node_id)
|
||||
if (node?.parentId) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
const iterations = tracing[tracing.length - 1]
|
||||
const currIteration = iterations.details![iterations.details!.length - 1]
|
||||
currIteration.push({
|
||||
const iterations = tracing.find(trace => trace.node_id === node?.parentId)
|
||||
const currIteration = iterations?.details![node.data.iteration_index] || iterations?.details![iterations.details!.length - 1]
|
||||
currIteration?.push({
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
} as any)
|
||||
}))
|
||||
}
|
||||
else {
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.tracing!.push({
|
||||
...data,
|
||||
@@ -288,11 +288,12 @@ export const useWorkflowRun = () => {
|
||||
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const incomeNodesId = getIncomers({ id: data.node_id } as Node, newNodes, edges).filter(node => node.data._runningStatus === NodeRunningStatus.Succeeded).map(node => node.id)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const edge = draft.find(edge => edge.target === data.node_id && edge.source === prevNodeId)
|
||||
|
||||
if (edge)
|
||||
edge.data = { ...edge.data, _run: true } as any
|
||||
draft.forEach((edge) => {
|
||||
if (edge.target === data.node_id && incomeNodesId.includes(edge.source))
|
||||
edge.data = { ...edge.data, _runned: true } as any
|
||||
})
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}
|
||||
@@ -309,25 +310,46 @@ export const useWorkflowRun = () => {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
if (isInIteration) {
|
||||
const nodes = getNodes()
|
||||
const nodeParentId = nodes.find(node => node.id === data.node_id)!.parentId
|
||||
if (nodeParentId) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
const iterations = tracing[tracing.length - 1]
|
||||
const currIteration = iterations.details![iterations.details!.length - 1]
|
||||
const nodeInfo = currIteration[currIteration.length - 1]
|
||||
const iterations = tracing.find(trace => trace.node_id === nodeParentId) // the iteration node
|
||||
|
||||
currIteration[currIteration.length - 1] = {
|
||||
...nodeInfo,
|
||||
...data,
|
||||
status: NodeRunningStatus.Succeeded,
|
||||
} as any
|
||||
if (iterations && iterations.details) {
|
||||
const iterationIndex = data.execution_metadata?.iteration_index || 0
|
||||
if (!iterations.details[iterationIndex])
|
||||
iterations.details[iterationIndex] = []
|
||||
|
||||
const currIteration = iterations.details[iterationIndex]
|
||||
const nodeIndex = currIteration.findIndex(node =>
|
||||
node.node_id === data.node_id && (
|
||||
node.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id || node.parallel_id === data.execution_metadata?.parallel_id),
|
||||
)
|
||||
if (data.status === NodeRunningStatus.Succeeded) {
|
||||
if (nodeIndex !== -1) {
|
||||
currIteration[nodeIndex] = {
|
||||
...currIteration[nodeIndex],
|
||||
...data,
|
||||
} as any
|
||||
}
|
||||
else {
|
||||
currIteration.push({
|
||||
...data,
|
||||
} as any)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
else {
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const currentIndex = draft.tracing!.findIndex(trace => trace.node_id === data.node_id)
|
||||
|
||||
const currentIndex = draft.tracing!.findIndex((trace) => {
|
||||
if (!trace.execution_metadata?.parallel_id)
|
||||
return trace.node_id === data.node_id
|
||||
return trace.node_id === data.node_id && trace.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id
|
||||
})
|
||||
if (currentIndex > -1 && draft.tracing) {
|
||||
draft.tracing[currentIndex] = {
|
||||
...(draft.tracing[currentIndex].extras
|
||||
@@ -337,16 +359,14 @@ export const useWorkflowRun = () => {
|
||||
} as any
|
||||
}
|
||||
}))
|
||||
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
|
||||
currentNode.data._runningStatus = data.status as any
|
||||
})
|
||||
setNodes(newNodes)
|
||||
|
||||
prevNodeId = data.node_id
|
||||
}
|
||||
|
||||
if (onNodeFinished)
|
||||
onNodeFinished(params)
|
||||
},
|
||||
@@ -371,8 +391,6 @@ export const useWorkflowRun = () => {
|
||||
details: [],
|
||||
} as any)
|
||||
}))
|
||||
isInIteration = true
|
||||
iterationLength = data.metadata.iterator_length
|
||||
|
||||
const {
|
||||
setViewport,
|
||||
@@ -398,7 +416,7 @@ export const useWorkflowRun = () => {
|
||||
const edge = draft.find(edge => edge.target === data.node_id && edge.source === prevNodeId)
|
||||
|
||||
if (edge)
|
||||
edge.data = { ...edge.data, _run: true } as any
|
||||
edge.data = { ...edge.data, _runned: true } as any
|
||||
})
|
||||
setEdges(newEdges)
|
||||
|
||||
@@ -418,13 +436,13 @@ export const useWorkflowRun = () => {
|
||||
} = store.getState()
|
||||
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const iteration = draft.tracing![draft.tracing!.length - 1]
|
||||
if (iteration.details!.length >= iterationLength)
|
||||
return
|
||||
|
||||
iteration.details!.push([])
|
||||
const iteration = draft.tracing!.find(trace => trace.node_id === data.node_id)
|
||||
if (iteration) {
|
||||
if (iteration.details!.length >= iteration.metadata.iterator_length!)
|
||||
return
|
||||
}
|
||||
iteration?.details!.push([])
|
||||
}))
|
||||
|
||||
const nodes = getNodes()
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
@@ -450,13 +468,14 @@ export const useWorkflowRun = () => {
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
tracing[tracing.length - 1] = {
|
||||
...tracing[tracing.length - 1],
|
||||
...data,
|
||||
status: NodeRunningStatus.Succeeded,
|
||||
} as any
|
||||
const currIterationNode = tracing.find(trace => trace.node_id === data.node_id)
|
||||
if (currIterationNode) {
|
||||
Object.assign(currIterationNode, {
|
||||
...data,
|
||||
status: NodeRunningStatus.Succeeded,
|
||||
})
|
||||
}
|
||||
}))
|
||||
isInIteration = false
|
||||
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
@@ -470,6 +489,12 @@ export const useWorkflowRun = () => {
|
||||
if (onIterationFinish)
|
||||
onIterationFinish(params)
|
||||
},
|
||||
onParallelBranchStarted: (params) => {
|
||||
// console.log(params, 'parallel start')
|
||||
},
|
||||
onParallelBranchFinished: (params) => {
|
||||
// console.log(params, 'finished')
|
||||
},
|
||||
onTextChunk: (params) => {
|
||||
const { data: { text } } = params
|
||||
const {
|
||||
|
@@ -10,13 +10,13 @@ export const useWorkflowTemplate = () => {
|
||||
const isChatMode = useIsChatMode()
|
||||
const nodesInitialData = useNodesInitialData()
|
||||
|
||||
const startNode = generateNewNode({
|
||||
const { newNode: startNode } = generateNewNode({
|
||||
data: nodesInitialData.start,
|
||||
position: START_INITIAL_POSITION,
|
||||
})
|
||||
|
||||
if (isChatMode) {
|
||||
const llmNode = generateNewNode({
|
||||
const { newNode: llmNode } = generateNewNode({
|
||||
id: 'llm',
|
||||
data: {
|
||||
...nodesInitialData.llm,
|
||||
@@ -31,7 +31,7 @@ export const useWorkflowTemplate = () => {
|
||||
},
|
||||
} as any)
|
||||
|
||||
const answerNode = generateNewNode({
|
||||
const { newNode: answerNode } = generateNewNode({
|
||||
id: 'answer',
|
||||
data: {
|
||||
...nodesInitialData.answer,
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
} from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import { uniqBy } from 'lodash-es'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import {
|
||||
getIncomers,
|
||||
@@ -29,6 +30,11 @@ import {
|
||||
useWorkflowStore,
|
||||
} from '../store'
|
||||
import {
|
||||
getParallelInfo,
|
||||
} from '../utils'
|
||||
import {
|
||||
PARALLEL_DEPTH_LIMIT,
|
||||
PARALLEL_LIMIT,
|
||||
SUPPORT_OUTPUT_VARS_NODE,
|
||||
} from '../constants'
|
||||
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
|
||||
@@ -50,6 +56,7 @@ import {
|
||||
} from '@/service/tools'
|
||||
import I18n from '@/context/i18n'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
|
||||
|
||||
export const useIsChatMode = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
@@ -58,6 +65,7 @@ export const useIsChatMode = () => {
|
||||
}
|
||||
|
||||
export const useWorkflow = () => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
@@ -77,7 +85,7 @@ export const useWorkflow = () => {
|
||||
const currentNode = nodes.find(node => node.id === nodeId)
|
||||
|
||||
if (currentNode?.parentId)
|
||||
startNode = nodes.find(node => node.parentId === currentNode.parentId && node.data.isIterationStart)
|
||||
startNode = nodes.find(node => node.parentId === currentNode.parentId && node.type === CUSTOM_ITERATION_START_NODE)
|
||||
|
||||
if (!startNode)
|
||||
return []
|
||||
@@ -275,6 +283,45 @@ export const useWorkflow = () => {
|
||||
return isUsed
|
||||
}, [isVarUsedInNodes])
|
||||
|
||||
const checkParallelLimit = useCallback((nodeId: string) => {
|
||||
const {
|
||||
getNodes,
|
||||
edges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const currentNode = nodes.find(node => node.id === nodeId)!
|
||||
const sourceNodeOutgoers = getOutgoers(currentNode, nodes, edges)
|
||||
if (sourceNodeOutgoers.length > PARALLEL_LIMIT - 1) {
|
||||
const { setShowTips } = workflowStore.getState()
|
||||
setShowTips(t('workflow.common.parallelTip.limit', { num: PARALLEL_LIMIT }))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}, [store, workflowStore, t])
|
||||
|
||||
const checkNestedParallelLimit = useCallback((nodes: Node[], edges: Edge[], parentNodeId?: string) => {
|
||||
const {
|
||||
parallelList,
|
||||
hasAbnormalEdges,
|
||||
} = getParallelInfo(nodes, edges, parentNodeId)
|
||||
|
||||
if (hasAbnormalEdges)
|
||||
return false
|
||||
|
||||
for (let i = 0; i < parallelList.length; i++) {
|
||||
const parallel = parallelList[i]
|
||||
|
||||
if (parallel.depth > PARALLEL_DEPTH_LIMIT) {
|
||||
const { setShowTips } = workflowStore.getState()
|
||||
setShowTips(t('workflow.common.parallelTip.depthLimit', { num: PARALLEL_DEPTH_LIMIT }))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}, [t, workflowStore])
|
||||
|
||||
const isValidConnection = useCallback(({ source, target }: Connection) => {
|
||||
const {
|
||||
edges,
|
||||
@@ -284,12 +331,15 @@ export const useWorkflow = () => {
|
||||
const sourceNode: Node = nodes.find(node => node.id === source)!
|
||||
const targetNode: Node = nodes.find(node => node.id === target)!
|
||||
|
||||
if (targetNode.data.isIterationStart)
|
||||
if (!checkParallelLimit(source!))
|
||||
return false
|
||||
|
||||
if (sourceNode.type === CUSTOM_NOTE_NODE || targetNode.type === CUSTOM_NOTE_NODE)
|
||||
return false
|
||||
|
||||
if (sourceNode.parentId !== targetNode.parentId)
|
||||
return false
|
||||
|
||||
if (sourceNode && targetNode) {
|
||||
const sourceNodeAvailableNextNodes = nodesExtraData[sourceNode.data.type].availableNextNodes
|
||||
const targetNodeAvailablePrevNodes = [...nodesExtraData[targetNode.data.type].availablePrevNodes, BlockEnum.Start]
|
||||
@@ -316,7 +366,7 @@ export const useWorkflow = () => {
|
||||
}
|
||||
|
||||
return !hasCycle(targetNode)
|
||||
}, [store, nodesExtraData])
|
||||
}, [store, nodesExtraData, checkParallelLimit])
|
||||
|
||||
const formatTimeFromNow = useCallback((time: number) => {
|
||||
return dayjs(time).locale(locale === 'zh-Hans' ? 'zh-cn' : locale).fromNow()
|
||||
@@ -339,6 +389,8 @@ export const useWorkflow = () => {
|
||||
isVarUsedInNodes,
|
||||
removeUsedVarInNodes,
|
||||
isNodeVarsUsedInNodes,
|
||||
checkParallelLimit,
|
||||
checkNestedParallelLimit,
|
||||
isValidConnection,
|
||||
formatTimeFromNow,
|
||||
getNode,
|
||||
|
Reference in New Issue
Block a user