Prechádzať zdrojové kódy

Fix auto-layout: connect loops to spacers and handle exit edges

- Always add edge from loop to its spacer node (not just for done edges)
- Track edges that exit from loop bodies to other main flow nodes
- Route exit edges through loop spacers to ensure proper vertical ordering
- This fixes nested loops being placed above their parent loops

The key issue was that loops without connected "done" outputs had floating
spacers with no incoming edges, causing dagre to place them incorrectly.
fszontagh 6 mesiacov pred
rodič
commit
e5c8c45548
1 zmenil súbory, kde vykonal 44 pridanie a 0 odobranie
  1. 44 0
      webui/src/pages/WorkflowEditorPage.tsx

+ 44 - 0
webui/src/pages/WorkflowEditorPage.tsx

@@ -1337,11 +1337,31 @@ function WorkflowEditorInner() {
       }
     })
 
+    // Track edges that exit from loop bodies to main flow nodes
+    // These need to be represented as loop -> target edges to maintain ordering
+    const loopExitEdges = new Map<string, Set<string>>()
+    loopNodes.forEach(loopId => loopExitEdges.set(loopId, new Set()))
+
+    edges.forEach(e => {
+      const sourceLoop = nodeToLoop.get(e.source)
+      const targetInBody = nodeToLoop.has(e.target)
+
+      // If source is in a loop body and target is NOT in any body and not a loop, track as exit edge
+      if (sourceLoop && !targetInBody && !loopNodes.has(e.target)) {
+        loopExitEdges.get(sourceLoop)!.add(e.target)
+      }
+      // If source is in a loop body and target is another loop (not the same loop), track as exit edge
+      if (sourceLoop && loopNodes.has(e.target) && e.target !== sourceLoop) {
+        loopExitEdges.get(sourceLoop)!.add(e.target)
+      }
+    })
+
     // Add edges - for loop nodes, route through spacer
     edges.forEach(e => {
       const sourceInBody = nodeToLoop.has(e.source)
       const targetInBody = nodeToLoop.has(e.target)
 
+      // Skip edges where source or target is a body node (handled separately)
       if (sourceInBody || targetInBody) return
 
       // For loop "done" edges, route through the spacer
@@ -1360,6 +1380,30 @@ function WorkflowEditorInner() {
       g.setEdge(e.source, e.target)
     })
 
+    // Connect each loop to its spacer (if it has one)
+    // This ensures the spacer is positioned directly below the loop
+    loopNodes.forEach(loopId => {
+      const spacerId = loopId + '_spacer'
+      if (g.hasNode(spacerId)) {
+        g.setEdge(loopId, spacerId)
+      }
+    })
+
+    // Add edges from loops (via spacers) to nodes that their body exits to
+    // This ensures proper ordering when body nodes connect to main flow nodes
+    loopNodes.forEach(loopId => {
+      const exitTargets = loopExitEdges.get(loopId)!
+      const spacerId = loopId + '_spacer'
+      exitTargets.forEach(targetId => {
+        // Route through spacer if it exists, to place target below the loop body
+        if (g.hasNode(spacerId)) {
+          g.setEdge(spacerId, targetId)
+        } else {
+          g.setEdge(loopId, targetId)
+        }
+      })
+    })
+
     dagre.layout(g)
 
     const newPositions = new Map<string, { x: number; y: number }>()