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:
takatost
2024-09-10 15:23:16 +08:00
committed by GitHub
parent 5da0182800
commit dabfd74622
156 changed files with 11158 additions and 5605 deletions

View File

@@ -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,
}

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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,