feat(workflow): improve group node interaction handling

- Enhanced `useNodesInteractions` to better manage group node handlers and connections, ensuring accurate identification of leaf nodes and their branches.
- Updated logic to create handlers based on node connections, differentiating between internal and external connections.
- Refined initial node setup to include target branches for group nodes, improving the overall interaction model for grouped elements.
This commit is contained in:
zhsama 2026-01-05 17:42:31 +08:00
parent 50bed78d7a
commit 9012dced6a
3 changed files with 68 additions and 42 deletions

View File

@ -103,7 +103,23 @@ function createGroupEdgePair(params: {
}): { realEdge: Edge, uiEdge: Edge } | null {
const { groupNodeId, handlerId, targetNodeId, targetHandle, nodes, baseEdgeData = {}, zIndex = 0 } = params
const { originalNodeId, originalSourceHandle } = parseGroupHandlerId(handlerId)
const groupNode = nodes.find(node => node.id === groupNodeId)
const groupData = groupNode?.data as GroupNodeData | undefined
const handler = groupData?.handlers?.find(h => h.id === handlerId)
let originalNodeId: string
let originalSourceHandle: string
if (handler?.nodeId && handler?.sourceHandle) {
originalNodeId = handler.nodeId
originalSourceHandle = handler.sourceHandle
}
else {
const parsed = parseGroupHandlerId(handlerId)
originalNodeId = parsed.originalNodeId
originalSourceHandle = parsed.originalSourceHandle
}
const originalNode = nodes.find(node => node.id === originalNodeId)
const targetNode = nodes.find(node => node.id === targetNodeId)
@ -2580,9 +2596,40 @@ export const useNodesInteractions = () => {
const outboundEdges = edges.filter(edge => bundledNodeIdSet.has(edge.source) && !bundledNodeIdSet.has(edge.target))
// leaf node: no outbound edges to other nodes in the selection
const leafNodeIds = bundledNodes
.filter(node => !edges.some(edge => edge.source === node.id && bundledNodeIdSet.has(edge.target)))
.map(node => node.id)
const handlers: GroupHandler[] = []
const leafNodeIdSet = new Set<string>()
bundledNodes.forEach((node: Node) => {
const targetBranches = node.data._targetBranches || [{ id: 'source', name: node.data.title }]
targetBranches.forEach((branch) => {
// A branch should be a handler if it's either:
// 1. Connected to a node OUTSIDE the group
// 2. NOT connected to any node INSIDE the group
const isConnectedInside = edges.some(edge =>
edge.source === node.id
&& (edge.sourceHandle === branch.id || (!edge.sourceHandle && branch.id === 'source'))
&& bundledNodeIdSet.has(edge.target),
)
const isConnectedOutside = edges.some(edge =>
edge.source === node.id
&& (edge.sourceHandle === branch.id || (!edge.sourceHandle && branch.id === 'source'))
&& !bundledNodeIdSet.has(edge.target),
)
if (isConnectedOutside || !isConnectedInside) {
const handlerId = `${node.id}-${branch.id}`
handlers.push({
id: handlerId,
label: branch.name || node.data.title || node.id,
nodeId: node.id,
sourceHandle: branch.id,
})
leafNodeIdSet.add(node.id)
}
})
})
const leafNodeIds = Array.from(leafNodeIdSet)
leafNodeIds.forEach(id => bundledNodeIdIsLeaf.add(id))
const members: GroupMember[] = bundledNodes.map((node) => {
@ -2592,42 +2639,6 @@ export const useNodesInteractions = () => {
label: node.data.title,
}
})
// Build handlers from all leaf nodes
// For multi-branch nodes (if-else, classifier), create one handler per branch
// For regular nodes, create one handler with 'source' handle
const handlerMap = new Map<string, GroupHandler>()
leafNodeIds.forEach((nodeId) => {
const node = bundledNodes.find(n => n.id === nodeId)
if (!node)
return
const targetBranches = node.data._targetBranches
if (targetBranches && targetBranches.length > 0) {
// Multi-branch node: create handler for each branch
targetBranches.forEach((branch: { id: string, name?: string }) => {
const handlerId = `${nodeId}-${branch.id}`
handlerMap.set(handlerId, {
id: handlerId,
label: branch.name || node.data.title || nodeId,
nodeId,
sourceHandle: branch.id,
})
})
}
else {
// Regular node: single 'source' handler
const handlerId = `${nodeId}-source`
handlerMap.set(handlerId, {
id: handlerId,
label: node.data.title || nodeId,
nodeId,
sourceHandle: 'source',
})
}
})
const handlers: GroupHandler[] = Array.from(handlerMap.values())
// head nodes: nodes that receive input from outside the group
const headNodeIds = [...new Set(inboundEdges.map(edge => edge.target))]
@ -2644,6 +2655,10 @@ export const useNodesInteractions = () => {
headNodeIds,
leafNodeIds,
selected: true,
_targetBranches: handlers.map(handler => ({
id: handler.id,
name: handler.label || handler.id,
})),
}
const { newNode: groupNode } = generateNewNode({

View File

@ -24,14 +24,15 @@ const GroupNode = (props: NodeProps<GroupNodeData>) => {
: []
), [data._children, data.members])
// handler 列表:优先使用传入的 handlers缺省时用 members 的 label 填充。
const handlers: GroupHandler[] = useMemo(() => (
data.handlers?.length
? data.handlers
: members.length
? members.map(member => ({
id: member.id,
id: `${member.id}-source`,
label: member.label || member.id,
nodeId: member.id,
sourceHandle: 'source',
}))
: []
), [data.handlers, members])

View File

@ -380,6 +380,16 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
})
}
if (node.data.type === BlockEnum.Group) {
const groupData = node.data as GroupNodeData
if (groupData.handlers?.length) {
node.data._targetBranches = groupData.handlers.map(handler => ({
id: handler.id,
name: handler.label || handler.id,
}))
}
}
if (node.data.type === BlockEnum.Iteration) {
const iterationNodeData = node.data as IterationNodeType
iterationNodeData._children = iterationOrLoopNodeMap[node.id] || []