Feature/newnew workflow loop node (#14863)

Co-authored-by: arkunzz <4873204@qq.com>
This commit is contained in:
Wood
2025-03-05 17:41:15 +08:00
committed by GitHub
parent da91217bc9
commit 2c17bb2c36
131 changed files with 6031 additions and 159 deletions

View File

@@ -21,6 +21,14 @@ export const useHelpline = () => {
showVerticalHelpLineNodes: [],
}
}
if (node.data.isInLoop) {
return {
showHorizontalHelpLineNodes: [],
showVerticalHelpLineNodes: [],
}
}
const showHorizontalHelpLineNodes = nodes.filter((n) => {
if (n.id === node.id)
return false
@@ -28,6 +36,9 @@ export const useHelpline = () => {
if (n.data.isInIteration)
return false
if (n.data.isInLoop)
return false
const nY = Math.ceil(n.position.y)
const nodeY = Math.ceil(node.position.y)
@@ -67,6 +78,8 @@ export const useHelpline = () => {
return false
if (n.data.isInIteration)
return false
if (n.data.isInLoop)
return false
const nX = Math.ceil(n.position.x)
const nodeX = Math.ceil(node.position.x)

View File

@@ -31,7 +31,7 @@ export const useNodesExtraData = () => {
}), [t, isChatMode])
}
export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean) => {
export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean, isInLoop?: boolean) => {
const nodesExtraData = useNodesExtraData()
const availablePrevBlocks = useMemo(() => {
if (!nodeType)
@@ -48,15 +48,23 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean
return useMemo(() => {
return {
availablePrevBlocks: availablePrevBlocks.filter((nType) => {
if (isInIteration && (nType === BlockEnum.Iteration || nType === BlockEnum.End))
if (isInIteration && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End))
return false
if (isInLoop && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End))
return false
return true
}),
availableNextBlocks: availableNextBlocks.filter((nType) => {
if (isInIteration && (nType === BlockEnum.Iteration || nType === BlockEnum.End))
if (isInIteration && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End))
return false
if (isInLoop && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End))
return false
return true
}),
}
}, [isInIteration, availablePrevBlocks, availableNextBlocks])
}, [isInIteration, availablePrevBlocks, availableNextBlocks, isInLoop])
}

View File

@@ -29,6 +29,8 @@ import {
CUSTOM_EDGE,
ITERATION_CHILDREN_Z_INDEX,
ITERATION_PADDING,
LOOP_CHILDREN_Z_INDEX,
LOOP_PADDING,
NODES_INITIAL_DATA,
NODE_WIDTH_X_OFFSET,
X_OFFSET,
@@ -42,9 +44,12 @@ import {
} from '../utils'
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
import type { IterationNodeType } from '../nodes/iteration/types'
import type { LoopNodeType } from '../nodes/loop/types'
import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants'
import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types'
import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions'
import { useNodeLoopInteractions } from '../nodes/loop/use-interactions'
import { useWorkflowHistoryStore } from '../workflow-history-store'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import { useHelpline } from './use-helpline'
@@ -73,6 +78,10 @@ export const useNodesInteractions = () => {
handleNodeIterationChildDrag,
handleNodeIterationChildrenCopy,
} = useNodeIterationInteractions()
const {
handleNodeLoopChildDrag,
handleNodeLoopChildrenCopy,
} = useNodeLoopInteractions()
const dragNodeStartPosition = useRef({ x: 0, y: 0 } as { x: number; y: number })
const { saveStateToHistory, undo, redo } = useWorkflowHistory()
@@ -86,6 +95,9 @@ export const useNodesInteractions = () => {
if (node.type === CUSTOM_ITERATION_START_NODE || node.type === CUSTOM_NOTE_NODE)
return
if (node.type === CUSTOM_LOOP_START_NODE || node.type === CUSTOM_NOTE_NODE)
return
dragNodeStartPosition.current = { x: node.position.x, y: node.position.y }
}, [workflowStore, getNodesReadOnly])
@@ -96,6 +108,9 @@ export const useNodesInteractions = () => {
if (node.type === CUSTOM_ITERATION_START_NODE)
return
if (node.type === CUSTOM_LOOP_START_NODE)
return
const {
getNodes,
setNodes,
@@ -105,6 +120,7 @@ export const useNodesInteractions = () => {
const nodes = getNodes()
const { restrictPosition } = handleNodeIterationChildDrag(node)
const { restrictPosition: restrictLoopPosition } = handleNodeLoopChildDrag(node)
const {
showHorizontalHelpLineNodes,
@@ -120,6 +136,8 @@ export const useNodesInteractions = () => {
currentNode.position.x = showVerticalHelpLineNodes[0].position.x
else if (restrictPosition.x !== undefined)
currentNode.position.x = restrictPosition.x
else if (restrictLoopPosition.x !== undefined)
currentNode.position.x = restrictLoopPosition.x
else
currentNode.position.x = node.position.x
@@ -127,12 +145,13 @@ export const useNodesInteractions = () => {
currentNode.position.y = showHorizontalHelpLineNodes[0].position.y
else if (restrictPosition.y !== undefined)
currentNode.position.y = restrictPosition.y
else if (restrictLoopPosition.y !== undefined)
currentNode.position.y = restrictLoopPosition.y
else
currentNode.position.y = node.position.y
})
setNodes(newNodes)
}, [store, getNodesReadOnly, handleSetHelpline, handleNodeIterationChildDrag])
}, [getNodesReadOnly, store, handleNodeIterationChildDrag, handleNodeLoopChildDrag, handleSetHelpline])
const handleNodeDragStop = useCallback<NodeDragHandler>((_, node) => {
const {
@@ -163,6 +182,9 @@ export const useNodesInteractions = () => {
if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE)
return
if (node.type === CUSTOM_LOOP_START_NODE || node.type === CUSTOM_NOTE_NODE)
return
const {
getNodes,
setNodes,
@@ -237,6 +259,9 @@ export const useNodesInteractions = () => {
if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE)
return
if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_LOOP_START_NODE)
return
const {
setEnteringNodePayload,
} = workflowStore.getState()
@@ -311,6 +336,8 @@ export const useNodesInteractions = () => {
const handleNodeClick = useCallback<NodeMouseHandler>((_, node) => {
if (node.type === CUSTOM_ITERATION_START_NODE)
return
if (node.type === CUSTOM_LOOP_START_NODE)
return
handleNodeSelect(node.id)
}, [handleNodeSelect])
@@ -344,6 +371,10 @@ export const useNodesInteractions = () => {
if (edges.find(edge => edge.source === source && edge.sourceHandle === sourceHandle && edge.target === target && edge.targetHandle === targetHandle))
return
const parendNode = nodes.find(node => node.id === targetNode?.parentId)
const isInIteration = parendNode && parendNode.data.type === BlockEnum.Iteration
const isInLoop = !!parendNode && parendNode.data.type === BlockEnum.Loop
const newEdge = {
id: `${source}-${sourceHandle}-${target}-${targetHandle}`,
type: CUSTOM_EDGE,
@@ -354,10 +385,12 @@ export const useNodesInteractions = () => {
data: {
sourceType: nodes.find(node => node.id === source)!.data.type,
targetType: nodes.find(node => node.id === target)!.data.type,
isInIteration: !!targetNode?.parentId,
iteration_id: targetNode?.parentId,
isInIteration,
iteration_id: isInIteration ? targetNode?.parentId : undefined,
isInLoop,
loop_id: isInLoop ? targetNode?.parentId : undefined,
},
zIndex: targetNode?.parentId ? ITERATION_CHILDREN_Z_INDEX : 0,
zIndex: targetNode?.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0,
}
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
[
@@ -554,6 +587,45 @@ export const useNodesInteractions = () => {
}
}
}
if (currentNode.data.type === BlockEnum.Loop) {
const loopChildren = nodes.filter(node => node.parentId === currentNode.id)
if (loopChildren.length) {
if (currentNode.data._isBundled) {
loopChildren.forEach((child) => {
handleNodeDelete(child.id)
})
return handleNodeDelete(nodeId)
}
else {
if (loopChildren.length === 1) {
handleNodeDelete(loopChildren[0].id)
handleNodeDelete(nodeId)
return
}
const { setShowConfirm, showConfirm } = workflowStore.getState()
if (!showConfirm) {
setShowConfirm({
title: t('workflow.nodes.loop.deleteTitle'),
desc: t('workflow.nodes.loop.deleteDesc') || '',
onConfirm: () => {
loopChildren.forEach((child) => {
handleNodeDelete(child.id)
})
handleNodeDelete(nodeId)
handleSyncWorkflowDraft()
setShowConfirm(undefined)
},
})
return
}
}
}
}
const connectedEdges = getConnectedEdges([{ id: nodeId } as Node], edges)
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(connectedEdges.map(edge => ({ type: 'remove', edge })), nodes)
const newNodes = produce(nodes, (draft: Node[]) => {
@@ -612,6 +684,7 @@ export const useNodesInteractions = () => {
const {
newNode,
newIterationStartNode,
newLoopStartNode,
} = generateNewNode({
data: {
...NODES_INITIAL_DATA[nodeType],
@@ -640,13 +713,28 @@ export const useNodesInteractions = () => {
}
newNode.parentId = prevNode.parentId
newNode.extent = prevNode.extent
const parentNode = nodes.find(node => node.id === prevNode.parentId) || null
const isInIteration = !!parentNode && parentNode.data.type === BlockEnum.Iteration
const isInLoop = !!parentNode && parentNode.data.type === BlockEnum.Loop
if (prevNode.parentId) {
newNode.data.isInIteration = true
newNode.data.iteration_id = prevNode.parentId
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
if (newNode.data.type === BlockEnum.Answer || newNode.data.type === BlockEnum.Tool || newNode.data.type === BlockEnum.Assigner) {
const parentIterNodeIndex = nodes.findIndex(node => node.id === prevNode.parentId)
const iterNodeData: IterationNodeType = nodes[parentIterNodeIndex].data
newNode.data.isInIteration = isInIteration
newNode.data.isInLoop = isInLoop
if (isInIteration) {
newNode.data.iteration_id = parentNode.id
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
}
if (isInLoop) {
newNode.data.loop_id = parentNode.id
newNode.zIndex = LOOP_CHILDREN_Z_INDEX
}
if (isInIteration && (newNode.data.type === BlockEnum.Answer || newNode.data.type === BlockEnum.Tool || newNode.data.type === BlockEnum.Assigner)) {
const iterNodeData: IterationNodeType = parentNode.data
iterNodeData._isShowTips = true
}
if (isInLoop && (newNode.data.type === BlockEnum.Answer || newNode.data.type === BlockEnum.Tool || newNode.data.type === BlockEnum.Assigner)) {
const iterNodeData: IterationNodeType = parentNode.data
iterNodeData._isShowTips = true
}
}
@@ -661,11 +749,13 @@ export const useNodesInteractions = () => {
data: {
sourceType: prevNode.data.type,
targetType: newNode.data.type,
isInIteration: !!prevNode.parentId,
iteration_id: prevNode.parentId,
isInIteration,
isInLoop,
iteration_id: isInIteration ? prevNode.parentId : undefined,
loop_id: isInLoop ? prevNode.parentId : undefined,
_connectedNodeIsSelected: true,
},
zIndex: prevNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0,
zIndex: prevNode.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0,
}
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
[
@@ -686,10 +776,17 @@ export const useNodesInteractions = () => {
if (node.data.type === BlockEnum.Iteration && prevNode.parentId === node.id)
node.data._children?.push(newNode.id)
if (node.data.type === BlockEnum.Loop && prevNode.parentId === node.id)
node.data._children?.push(newNode.id)
})
draft.push(newNode)
if (newIterationStartNode)
draft.push(newIterationStartNode)
if (newLoopStartNode)
draft.push(newLoopStartNode)
})
if (newNode.data.type === BlockEnum.VariableAssigner || newNode.data.type === BlockEnum.VariableAggregator) {
@@ -736,10 +833,22 @@ export const useNodesInteractions = () => {
}
newNode.parentId = nextNode.parentId
newNode.extent = nextNode.extent
if (nextNode.parentId) {
newNode.data.isInIteration = true
newNode.data.iteration_id = nextNode.parentId
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
const parentNode = nodes.find(node => node.id === nextNode.parentId) || null
const isInIteration = !!parentNode && parentNode.data.type === BlockEnum.Iteration
const isInLoop = !!parentNode && parentNode.data.type === BlockEnum.Loop
if (parentNode && nextNode.parentId) {
newNode.data.isInIteration = isInIteration
newNode.data.isInLoop = isInLoop
if (isInIteration) {
newNode.data.iteration_id = parentNode.id
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
}
if (isInLoop) {
newNode.data.loop_id = parentNode.id
newNode.zIndex = LOOP_CHILDREN_Z_INDEX
}
}
let newEdge
@@ -755,11 +864,13 @@ export const useNodesInteractions = () => {
data: {
sourceType: newNode.data.type,
targetType: nextNode.data.type,
isInIteration: !!nextNode.parentId,
iteration_id: nextNode.parentId,
isInIteration,
isInLoop,
iteration_id: isInIteration ? nextNode.parentId : undefined,
loop_id: isInLoop ? nextNode.parentId : undefined,
_connectedNodeIsSelected: true,
},
zIndex: nextNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0,
zIndex: nextNode.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0,
}
}
@@ -796,10 +907,20 @@ export const useNodesInteractions = () => {
node.data.start_node_id = newNode.id
node.data.startNodeType = newNode.data.type
}
if (node.data.type === BlockEnum.Loop && nextNode.parentId === node.id)
node.data._children?.push(newNode.id)
if (node.data.type === BlockEnum.Loop && node.data.start_node_id === nextNodeId) {
node.data.start_node_id = newNode.id
node.data.startNodeType = newNode.data.type
}
})
draft.push(newNode)
if (newIterationStartNode)
draft.push(newIterationStartNode)
if (newLoopStartNode)
draft.push(newLoopStartNode)
})
if (newEdge) {
const newEdges = produce(edges, (draft) => {
@@ -840,10 +961,22 @@ export const useNodesInteractions = () => {
}
newNode.parentId = prevNode.parentId
newNode.extent = prevNode.extent
if (prevNode.parentId) {
newNode.data.isInIteration = true
newNode.data.iteration_id = prevNode.parentId
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
const parentNode = nodes.find(node => node.id === prevNode.parentId) || null
const isInIteration = !!parentNode && parentNode.data.type === BlockEnum.Iteration
const isInLoop = !!parentNode && parentNode.data.type === BlockEnum.Loop
if (parentNode && prevNode.parentId) {
newNode.data.isInIteration = isInIteration
newNode.data.isInLoop = isInLoop
if (isInIteration) {
newNode.data.iteration_id = parentNode.id
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
}
if (isInLoop) {
newNode.data.loop_id = parentNode.id
newNode.zIndex = LOOP_CHILDREN_Z_INDEX
}
}
const currentEdgeIndex = edges.findIndex(edge => edge.source === prevNodeId && edge.target === nextNodeId)
@@ -857,13 +990,20 @@ export const useNodesInteractions = () => {
data: {
sourceType: prevNode.data.type,
targetType: newNode.data.type,
isInIteration: !!prevNode.parentId,
iteration_id: prevNode.parentId,
isInIteration,
isInLoop,
iteration_id: isInIteration ? prevNode.parentId : undefined,
loop_id: isInLoop ? prevNode.parentId : undefined,
_connectedNodeIsSelected: true,
},
zIndex: prevNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0,
zIndex: prevNode.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0,
}
let newNextEdge: Edge | null = null
const nextNodeParentNode = nodes.find(node => node.id === nextNode.parentId) || null
const isNextNodeInIteration = !!nextNodeParentNode && nextNodeParentNode.data.type === BlockEnum.Iteration
const isNextNodeInLoop = !!nextNodeParentNode && nextNodeParentNode.data.type === BlockEnum.Loop
if (nodeType !== BlockEnum.IfElse && nodeType !== BlockEnum.QuestionClassifier) {
newNextEdge = {
id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`,
@@ -875,11 +1015,13 @@ export const useNodesInteractions = () => {
data: {
sourceType: newNode.data.type,
targetType: nextNode.data.type,
isInIteration: !!nextNode.parentId,
iteration_id: nextNode.parentId,
isInIteration: isNextNodeInIteration,
isInLoop: isNextNodeInLoop,
iteration_id: isNextNodeInIteration ? nextNode.parentId : undefined,
loop_id: isNextNodeInLoop ? nextNode.parentId : undefined,
_connectedNodeIsSelected: true,
},
zIndex: nextNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0,
zIndex: nextNode.parentId ? (isNextNodeInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0,
}
}
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
@@ -908,10 +1050,14 @@ export const useNodesInteractions = () => {
if (node.data.type === BlockEnum.Iteration && prevNode.parentId === node.id)
node.data._children?.push(newNode.id)
if (node.data.type === BlockEnum.Loop && prevNode.parentId === node.id)
node.data._children?.push(newNode.id)
})
draft.push(newNode)
if (newIterationStartNode)
draft.push(newIterationStartNode)
if (newLoopStartNode)
draft.push(newLoopStartNode)
})
setNodes(newNodes)
if (newNode.data.type === BlockEnum.VariableAssigner || newNode.data.type === BlockEnum.VariableAggregator) {
@@ -969,6 +1115,7 @@ export const useNodesInteractions = () => {
const {
newNode: newCurrentNode,
newIterationStartNode,
newLoopStartNode,
} = generateNewNode({
data: {
...NODES_INITIAL_DATA[nodeType],
@@ -978,7 +1125,9 @@ export const useNodesInteractions = () => {
_connectedTargetHandleIds: [],
selected: currentNode.data.selected,
isInIteration: currentNode.data.isInIteration,
isInLoop: currentNode.data.isInLoop,
iteration_id: currentNode.data.iteration_id,
loop_id: currentNode.data.loop_id,
},
position: {
x: currentNode.position.x,
@@ -1010,6 +1159,8 @@ export const useNodesInteractions = () => {
draft.splice(index, 1, newCurrentNode)
if (newIterationStartNode)
draft.push(newIterationStartNode)
if (newLoopStartNode)
draft.push(newLoopStartNode)
})
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
@@ -1058,6 +1209,9 @@ export const useNodesInteractions = () => {
if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE)
return
if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_LOOP_START_NODE)
return
e.preventDefault()
const container = document.querySelector('#workflow-container')
const { x, y } = container!.getBoundingClientRect()
@@ -1085,13 +1239,15 @@ 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 && node.type !== CUSTOM_ITERATION_START_NODE)
const nodeToCopy = nodes.find(node => node.id === nodeId && node.data.type !== BlockEnum.Start
&& node.type !== CUSTOM_ITERATION_START_NODE && node.type !== CUSTOM_LOOP_START_NODE)
if (nodeToCopy)
setClipboardElements([nodeToCopy])
}
else {
// If no nodeId is provided, fall back to the current behavior
const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start && !node.data.isInIteration)
const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start
&& !node.data.isInIteration && !node.data.isInLoop)
if (bundledNodes.length) {
setClipboardElements(bundledNodes)
@@ -1138,6 +1294,7 @@ export const useNodesInteractions = () => {
const {
newNode,
newIterationStartNode,
newLoopStartNode,
} = generateNewNode({
type: nodeToPaste.type,
data: {
@@ -1176,6 +1333,17 @@ export const useNodesInteractions = () => {
newChildren.push(newIterationStartNode!)
}
if (nodeToPaste.data.type === BlockEnum.Loop) {
newLoopStartNode!.parentId = newNode.id;
(newNode.data as LoopNodeType).start_node_id = newLoopStartNode!.id
newChildren = handleNodeLoopChildrenCopy(nodeToPaste.id, newNode.id)
newChildren.forEach((child) => {
newNode.data._children?.push(child.id)
})
newChildren.push(newLoopStartNode!)
}
nodesToPaste.push(newNode)
if (newChildren.length)
@@ -1206,7 +1374,7 @@ export const useNodesInteractions = () => {
saveStateToHistory(WorkflowHistoryEvent.NodePaste)
handleSyncWorkflowDraft()
}
}, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy])
}, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy, handleNodeLoopChildrenCopy])
const handleNodesDuplicate = useCallback((nodeId?: string) => {
if (getNodesReadOnly())
@@ -1278,9 +1446,12 @@ export const useNodesInteractions = () => {
})
if (rightNode! && bottomNode!) {
if (width < rightNode!.position.x + rightNode.width! + ITERATION_PADDING.right)
const parentNode = nodes.find(n => n.id === rightNode.parentId)
const paddingMap = parentNode?.data.type === BlockEnum.Iteration ? ITERATION_PADDING : LOOP_PADDING
if (width < rightNode!.position.x + rightNode.width! + paddingMap.right)
return
if (height < bottomNode.position.y + bottomNode.height! + ITERATION_PADDING.bottom)
if (height < bottomNode.position.y + bottomNode.height! + paddingMap.bottom)
return
}
const newNodes = produce(nodes, (draft) => {

View File

@@ -6,6 +6,9 @@ export * from './use-workflow-node-finished'
export * from './use-workflow-node-iteration-started'
export * from './use-workflow-node-iteration-next'
export * from './use-workflow-node-iteration-finished'
export * from './use-workflow-node-loop-started'
export * from './use-workflow-node-loop-next'
export * from './use-workflow-node-loop-finished'
export * from './use-workflow-node-retry'
export * from './use-workflow-text-chunk'
export * from './use-workflow-text-replace'

View File

@@ -0,0 +1,46 @@
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import produce from 'immer'
import type { LoopFinishedResponse } from '@/types/workflow'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { DEFAULT_LOOP_TIMES } from '@/app/components/workflow/constants'
export const useWorkflowNodeLoopFinished = () => {
const store = useStoreApi()
const workflowStore = useWorkflowStore()
const handleWorkflowNodeLoopFinished = useCallback((params: LoopFinishedResponse) => {
const { data } = params
const {
workflowRunningData,
setWorkflowRunningData,
setLoopTimes,
} = workflowStore.getState()
const {
getNodes,
setNodes,
} = store.getState()
const nodes = getNodes()
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
const currentIndex = draft.tracing!.findIndex(item => item.id === data.id)
if (currentIndex > -1) {
draft.tracing![currentIndex] = {
...draft.tracing![currentIndex],
...data,
}
}
}))
setLoopTimes(DEFAULT_LOOP_TIMES)
const newNodes = produce(nodes, (draft) => {
const currentNode = draft.find(node => node.id === data.node_id)!
currentNode.data._runningStatus = data.status
})
setNodes(newNodes)
}, [workflowStore, store])
return {
handleWorkflowNodeLoopFinished,
}
}

View File

@@ -0,0 +1,35 @@
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import produce from 'immer'
import type { LoopNextResponse } from '@/types/workflow'
import { useWorkflowStore } from '@/app/components/workflow/store'
export const useWorkflowNodeLoopNext = () => {
const store = useStoreApi()
const workflowStore = useWorkflowStore()
const handleWorkflowNodeLoopNext = useCallback((params: LoopNextResponse) => {
const {
loopTimes,
setLoopTimes,
} = workflowStore.getState()
const { data } = params
const {
getNodes,
setNodes,
} = store.getState()
const nodes = getNodes()
const newNodes = produce(nodes, (draft) => {
const currentNode = draft.find(node => node.id === data.node_id)!
currentNode.data._loopIndex = loopTimes
setLoopTimes(loopTimes + 1)
})
setNodes(newNodes)
}, [workflowStore, store])
return {
handleWorkflowNodeLoopNext,
}
}

View File

@@ -0,0 +1,85 @@
import { useCallback } from 'react'
import {
useReactFlow,
useStoreApi,
} from 'reactflow'
import produce from 'immer'
import { useWorkflowStore } from '@/app/components/workflow/store'
import type { LoopStartedResponse } from '@/types/workflow'
import { NodeRunningStatus } from '@/app/components/workflow/types'
import { DEFAULT_LOOP_TIMES } from '@/app/components/workflow/constants'
export const useWorkflowNodeLoopStarted = () => {
const store = useStoreApi()
const reactflow = useReactFlow()
const workflowStore = useWorkflowStore()
const handleWorkflowNodeLoopStarted = useCallback((
params: LoopStartedResponse,
containerParams: {
clientWidth: number,
clientHeight: number,
},
) => {
const { data } = params
const {
workflowRunningData,
setWorkflowRunningData,
setLoopTimes,
} = workflowStore.getState()
const {
getNodes,
setNodes,
edges,
setEdges,
transform,
} = store.getState()
const nodes = getNodes()
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
draft.tracing!.push({
...data,
status: NodeRunningStatus.Running,
})
}))
setLoopTimes(DEFAULT_LOOP_TIMES)
const {
setViewport,
} = reactflow
const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id)
const currentNode = nodes[currentNodeIndex]
const position = currentNode.position
const zoom = transform[2]
if (!currentNode.parentId) {
setViewport({
x: (containerParams.clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom,
y: (containerParams.clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom,
zoom: transform[2],
})
}
const newNodes = produce(nodes, (draft) => {
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
draft[currentNodeIndex].data._loopLength = data.metadata.loop_length
draft[currentNodeIndex].data._waitingRun = false
})
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
const incomeEdges = draft.filter(edge => edge.target === data.node_id)
incomeEdges.forEach((edge) => {
edge.data = {
...edge.data,
_sourceRunningStatus: nodes.find(node => node.id === edge.source)!.data._runningStatus,
_targetRunningStatus: NodeRunningStatus.Running,
_waitingRun: false,
}
})
})
setEdges(newEdges)
}, [workflowStore, store, reactflow])
return {
handleWorkflowNodeLoopStarted,
}
}

View File

@@ -6,6 +6,9 @@ import {
useWorkflowNodeIterationFinished,
useWorkflowNodeIterationNext,
useWorkflowNodeIterationStarted,
useWorkflowNodeLoopFinished,
useWorkflowNodeLoopNext,
useWorkflowNodeLoopStarted,
useWorkflowNodeRetry,
useWorkflowNodeStarted,
useWorkflowStarted,
@@ -22,6 +25,9 @@ export const useWorkflowRunEvent = () => {
const { handleWorkflowNodeIterationStarted } = useWorkflowNodeIterationStarted()
const { handleWorkflowNodeIterationNext } = useWorkflowNodeIterationNext()
const { handleWorkflowNodeIterationFinished } = useWorkflowNodeIterationFinished()
const { handleWorkflowNodeLoopStarted } = useWorkflowNodeLoopStarted()
const { handleWorkflowNodeLoopNext } = useWorkflowNodeLoopNext()
const { handleWorkflowNodeLoopFinished } = useWorkflowNodeLoopFinished()
const { handleWorkflowNodeRetry } = useWorkflowNodeRetry()
const { handleWorkflowTextChunk } = useWorkflowTextChunk()
const { handleWorkflowTextReplace } = useWorkflowTextReplace()
@@ -36,6 +42,9 @@ export const useWorkflowRunEvent = () => {
handleWorkflowNodeIterationStarted,
handleWorkflowNodeIterationNext,
handleWorkflowNodeIterationFinished,
handleWorkflowNodeLoopStarted,
handleWorkflowNodeLoopNext,
handleWorkflowNodeLoopFinished,
handleWorkflowNodeRetry,
handleWorkflowTextChunk,
handleWorkflowTextReplace,

View File

@@ -36,6 +36,9 @@ export const useWorkflowRun = () => {
handleWorkflowNodeIterationStarted,
handleWorkflowNodeIterationNext,
handleWorkflowNodeIterationFinished,
handleWorkflowNodeLoopStarted,
handleWorkflowNodeLoopNext,
handleWorkflowNodeLoopFinished,
handleWorkflowNodeRetry,
handleWorkflowAgentLog,
handleWorkflowTextChunk,
@@ -118,6 +121,9 @@ export const useWorkflowRun = () => {
onIterationStart,
onIterationNext,
onIterationFinish,
onLoopStart,
onLoopNext,
onLoopFinish,
onNodeRetry,
onAgentLog,
onError,
@@ -162,7 +168,7 @@ export const useWorkflowRun = () => {
else
ttsUrl = `/apps/${params.appId}/text-to-audio`
}
const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', (_: any): any => {})
const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', (_: any): any => { })
ssePost(
url,
@@ -230,6 +236,30 @@ export const useWorkflowRun = () => {
if (onIterationFinish)
onIterationFinish(params)
},
onLoopStart: (params) => {
handleWorkflowNodeLoopStarted(
params,
{
clientWidth,
clientHeight,
},
)
if (onLoopStart)
onLoopStart(params)
},
onLoopNext: (params) => {
handleWorkflowNodeLoopNext(params)
if (onLoopNext)
onLoopNext(params)
},
onLoopFinish: (params) => {
handleWorkflowNodeLoopFinished(params)
if (onLoopFinish)
onLoopFinish(params)
},
onNodeRetry: (params) => {
handleWorkflowNodeRetry(params)
@@ -260,7 +290,27 @@ export const useWorkflowRun = () => {
...restCallback,
},
)
}, [store, workflowStore, doSyncWorkflowDraft, handleWorkflowStarted, handleWorkflowFinished, handleWorkflowFailed, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeRetry, handleWorkflowTextChunk, handleWorkflowTextReplace, handleWorkflowAgentLog, pathname])
}, [
store,
workflowStore,
doSyncWorkflowDraft,
handleWorkflowStarted,
handleWorkflowFinished,
handleWorkflowFailed,
handleWorkflowNodeStarted,
handleWorkflowNodeFinished,
handleWorkflowNodeIterationStarted,
handleWorkflowNodeIterationNext,
handleWorkflowNodeIterationFinished,
handleWorkflowNodeLoopStarted,
handleWorkflowNodeLoopNext,
handleWorkflowNodeLoopFinished,
handleWorkflowNodeRetry,
handleWorkflowTextChunk,
handleWorkflowTextReplace,
handleWorkflowAgentLog,
pathname],
)
const handleStopRun = useCallback((taskId: string) => {
const appId = useAppStore.getState().appDetail?.id

View File

@@ -44,6 +44,7 @@ export const useWorkflowVariables = () => {
parentNode,
valueSelector,
isIterationItem,
isLoopItem,
availableNodes,
isChatMode,
isConstant,
@@ -51,6 +52,7 @@ export const useWorkflowVariables = () => {
valueSelector: ValueSelector
parentNode?: Node | null
isIterationItem?: boolean
isLoopItem?: boolean
availableNodes: any[]
isChatMode: boolean
isConstant?: boolean
@@ -59,6 +61,7 @@ export const useWorkflowVariables = () => {
parentNode,
valueSelector,
isIterationItem,
isLoopItem,
availableNodes,
isChatMode,
isConstant,

View File

@@ -57,6 +57,7 @@ import {
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'
import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants'
import { useWorkflowConfig } from '@/service/use-workflow'
import { canFindTool } from '@/utils'
@@ -89,7 +90,7 @@ export const useWorkflow = () => {
const currentNode = nodes.find(node => node.id === nodeId)
if (currentNode?.parentId)
startNode = nodes.find(node => node.parentId === currentNode.parentId && node.type === CUSTOM_ITERATION_START_NODE)
startNode = nodes.find(node => node.parentId === currentNode.parentId && (node.type === CUSTOM_ITERATION_START_NODE || node.type === CUSTOM_LOOP_START_NODE))
if (!startNode)
return []
@@ -239,6 +240,15 @@ export const useWorkflow = () => {
return nodes.filter(node => node.parentId === nodeId)
}, [store])
const getLoopNodeChildren = useCallback((nodeId: string) => {
const {
getNodes,
} = store.getState()
const nodes = getNodes()
return nodes.filter(node => node.parentId === nodeId)
}, [store])
const isFromStartNode = useCallback((nodeId: string) => {
const { getNodes } = store.getState()
const nodes = getNodes()
@@ -280,7 +290,7 @@ export const useWorkflow = () => {
setNodes(newNodes)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [store])
const isVarUsedInNodes = useCallback((varSelector: ValueSelector) => {
@@ -425,6 +435,7 @@ export const useWorkflow = () => {
getNode,
getBeforeNodeById,
getIterationNodeChildren,
getLoopNodeChildren,
}
}
@@ -520,7 +531,7 @@ export const useWorkflowInit = () => {
useEffect(() => {
handleGetInitialWorkflowData()
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const handleFetchPreloadData = useCallback(async () => {
@@ -537,7 +548,7 @@ export const useWorkflowInit = () => {
workflowStore.getState().setPublishedAt(publishedWorkflow?.created_at)
}
catch (e) {
console.error(e)
}
}, [workflowStore, appDetail])
@@ -638,3 +649,26 @@ export const useIsNodeInIteration = (iterationId: string) => {
isNodeInIteration,
}
}
export const useIsNodeInLoop = (loopId: string) => {
const store = useStoreApi()
const isNodeInLoop = useCallback((nodeId: string) => {
const {
getNodes,
} = store.getState()
const nodes = getNodes()
const node = nodes.find(node => node.id === nodeId)
if (!node)
return false
if (node.parentId === loopId)
return true
return false
}, [loopId, store])
return {
isNodeInLoop,
}
}