loopMembership.ts 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. import { Node, Edge } from 'reactflow'
  2. /**
  3. * Maps every node inside a loop body to the id of the loop node that owns
  4. * it. A node not reachable from any loop node's 'loop' output is absent
  5. * from the map, including loop nodes themselves - a loop node is not a
  6. * member of its own body.
  7. *
  8. * Shared by ExecutionResultsPanel (grouping loop-body results by iteration)
  9. * and WorkflowEditorPage (deciding which cached upstream output belongs to
  10. * the same iteration as a node being re-run). Both need the identical
  11. * answer to "is this node inside this loop's body", so this is the one
  12. * place that walks the graph to find out.
  13. */
  14. export function computeNodeToLoopMap(nodes: Node[], edges: Edge[]): Map<string, string> {
  15. const nodeToLoopMap = new Map<string, string>()
  16. const loopNodes = nodes.filter(n => n.data?.type === 'loop')
  17. const outgoingEdges = new Map<string, Array<{ target: string; handle: string }>>()
  18. nodes.forEach(n => outgoingEdges.set(n.id, []))
  19. edges.forEach(e => {
  20. outgoingEdges.get(e.source)?.push({ target: e.target, handle: e.sourceHandle || 'main' })
  21. })
  22. const findLoopBodyNodes = (loopId: string, nodeId: string, visited: Set<string>) => {
  23. if (visited.has(nodeId)) return
  24. visited.add(nodeId)
  25. if (nodeId !== loopId) {
  26. nodeToLoopMap.set(nodeId, loopId)
  27. }
  28. const outEdges = outgoingEdges.get(nodeId) || []
  29. for (const edge of outEdges) {
  30. if (nodeId === loopId && edge.handle === 'done') continue
  31. const targetNode = nodes.find(n => n.id === edge.target)
  32. if (targetNode?.data?.type === 'loop' && edge.target !== loopId) continue
  33. findLoopBodyNodes(loopId, edge.target, visited)
  34. }
  35. }
  36. loopNodes.forEach(loopNode => {
  37. const outEdges = outgoingEdges.get(loopNode.id) || []
  38. const loopEdges = outEdges.filter(e => e.handle === 'loop')
  39. const visited = new Set<string>()
  40. loopEdges.forEach(edge => findLoopBodyNodes(loopNode.id, edge.target, visited))
  41. })
  42. return nodeToLoopMap
  43. }