diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index cb63777683..667cc3c347 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -63,7 +63,7 @@ import NoteTool from './NoteTool.svelte' import SelectionBoundingBox from './SelectionBoundingBox.svelte' import GroupOverlay from './GroupOverlay.svelte' - import { getGroupEditorContext, computeGroupSpacing, GROUP_HEADER_HEIGHT, GROUP_TOP_MARGIN } from './groupEditor.svelte' + import { getGroupEditorContext, GROUP_HEADER_HEIGHT, GROUP_TOP_MARGIN } from './groupEditor.svelte' import SelectionTool from './SelectionTool.svelte' import PaneContextMenu from './PaneContextMenu.svelte' import { SelectionManager } from './selectionUtils.svelte' @@ -75,6 +75,9 @@ import { compoundLayout } from './compoundLayout' import { deepEqual } from 'fast-equals' import type { AssetWithAltAccessType } from '../assets/lib' + import { assetDisplaysAsInputInFlowGraph, assetDisplaysAsOutputInFlowGraph, NODE_WITH_READ_ASSET_Y_OFFSET, NODE_WITH_WRITE_ASSET_Y_OFFSET } from './renderers/nodes/AssetNode.svelte' + import { AI_TOOL_BASE_OFFSET, AI_TOOL_ROW_OFFSET, BELOW_ADDITIONAL_OFFSET } from './renderers/nodes/AIToolNode.svelte' + import { topologicalSort } from './graphBuilder.svelte' import type { ModuleActionInfo } from '$lib/components/flows/flowDiff' import { setGraphContext } from './graphContext' import { computeNoteNodes } from './noteUtils.svelte' @@ -345,15 +348,129 @@ type NodeDep = { id: string parentIds?: string[] - data?: { assets?: AssetWithAltAccessType[] } + data?: { assets?: AssetWithAltAccessType[]; module?: any } } type NodePos = { position: { x: number; y: number } } - let lastNodes: [NodeDep[], (NodeDep & NodePos)[]] | undefined = undefined + let lastNodes: [NodeDep[], Map | undefined, (NodeDep & NodePos)[]] | undefined = undefined let currentContainerDescendants: Map = new Map() - function layoutNodes(nodes: NodeDep[]): (NodeDep & NodePos)[] { - let lastResult = lastNodes?.[1] - if (lastResult && deepEqual(nodes, lastNodes?.[0])) { + const MAX_TOOLS_PER_ROW = 2 + + /** + * Pre-compute extra top/bottom space each node needs for decorations + * (assets, AI tools, group headers, group notes). + */ + function computeNodeExtraSpace( + graphNodes: NodeDep[] + ): Map | undefined { + const extraSpace = new Map() + + // 1. Assets + if ($showAssets) { + for (const node of graphNodes) { + const assets = node.data?.assets ?? [] + if (!assets.length) continue + const hasRead = assets.some(assetDisplaysAsInputInFlowGraph) + const hasWrite = assets.some(assetDisplaysAsOutputInFlowGraph) + if (hasRead || hasWrite) { + const prev = extraSpace.get(node.id) ?? { top: 0, bottom: 0 } + extraSpace.set(node.id, { + top: prev.top + (hasRead ? NODE_WITH_READ_ASSET_Y_OFFSET : 0), + bottom: prev.bottom + (hasWrite ? NODE_WITH_WRITE_ASSET_Y_OFFSET : 0) + }) + } + } + } + + // 2. AI tools + for (const node of graphNodes) { + const mod = node.data?.module + if (!mod || mod.value?.type !== 'aiagent') continue + + const agentActions = !insertable && flowModuleStates?.[node.id]?.agent_actions + + if (agentActions) { + // Execution mode: tools below + const totalRows = Math.ceil(agentActions.length / MAX_TOOLS_PER_ROW) + const space = AI_TOOL_BASE_OFFSET + AI_TOOL_ROW_OFFSET * totalRows + BELOW_ADDITIONAL_OFFSET + const prev = extraSpace.get(node.id) ?? { top: 0, bottom: 0 } + extraSpace.set(node.id, { top: prev.top, bottom: prev.bottom + space }) + } else { + // Edit mode: tools above + const tools = mod.value.tools ?? [] + const totalRows = Math.ceil(tools.length / MAX_TOOLS_PER_ROW) + (insertable ? 1 : 0) + const space = AI_TOOL_BASE_OFFSET + AI_TOOL_ROW_OFFSET * totalRows + const prev = extraSpace.get(node.id) ?? { top: 0, bottom: 0 } + extraSpace.set(node.id, { top: prev.top + space, bottom: prev.bottom }) + } + } + + // 3. Group notes (text above topmost node in each group note) + if (showNotes) { + const groupNotes = (notes ?? []).filter((n) => n.type === 'group') + if (groupNotes.length > 0) { + const sortedNodes = topologicalSort(graphNodes).reverse() + for (const groupNote of groupNotes) { + if (!groupNote.contained_node_ids?.length) continue + const topmostNodeId = sortedNodes.find((node) => + groupNote.contained_node_ids?.includes(node.id) + )?.id + if (topmostNodeId) { + const textHeight = noteTextHeights[groupNote.id] || 60 + const spacing = textHeight + 16 // padding + const prev = extraSpace.get(topmostNodeId) ?? { top: 0, bottom: 0 } + extraSpace.set(topmostNodeId, { + top: Math.max(prev.top, spacing + prev.top), + bottom: prev.bottom + }) + } + } + } + } + + // 4. Group headers (header + note + margin above topmost node in each group) + const groups = groupEditorContext?.groupEditor.getGroups() ?? [] + if (groups.length > 0) { + const groupNoteHeights = showNotes ? (groupEditorContext?.groupEditor.getNoteHeights() ?? {}) : {} + // Need to find topmost node per group - use topological sort + const sortedNodes = topologicalSort(graphNodes).reverse() + for (const group of groups) { + if (group.module_ids.length === 0) continue + // Check if it's collapsed + const collapsedNodeId = `collapsed-group:${group.id}` + const hasCollapsedNode = graphNodes.some((n) => n.id === collapsedNodeId) + let topmostNodeId: string | undefined + let isCollapsed = false + + if (hasCollapsedNode) { + topmostNodeId = collapsedNodeId + isCollapsed = true + } else { + topmostNodeId = sortedNodes.find((node) => + group.module_ids.includes(node.id) + )?.id + } + + if (topmostNodeId) { + const noteHeight = groupNoteHeights[group.id] ?? 0 + const spacing = isCollapsed + ? GROUP_HEADER_HEIGHT + noteHeight + : GROUP_HEADER_HEIGHT + noteHeight + GROUP_TOP_MARGIN + const prev = extraSpace.get(topmostNodeId) ?? { top: 0, bottom: 0 } + extraSpace.set(topmostNodeId, { + top: prev.top + spacing, + bottom: prev.bottom + }) + } + } + } + + return extraSpace.size > 0 ? extraSpace : undefined + } + + function layoutNodes(nodes: NodeDep[], nodeExtraSpace?: Map): (NodeDep & NodePos)[] { + let lastResult = lastNodes?.[2] + if (lastResult && deepEqual(nodes, lastNodes?.[0]) && deepEqual(nodeExtraSpace, lastNodes?.[1])) { console.debug('layoutNodes', 'same nodes') return lastResult } @@ -366,13 +483,13 @@ seenId.push(n.id) } - // Run recursive compound layout + // Run recursive compound layout with pre-computed extra space const { positions, bbox, containerDescendants: layoutContainerDescendants } = compoundLayout(nodes, { nodeWidth: NODE.width, nodeHeight: NODE.height, gapH: NODE.gap.horizontal, gapV: NODE.gap.vertical - }) + }, nodeExtraSpace) // Center horizontally const xCenter = (fullSize ? fullWidth : width) / 2 - bbox.width / 2 - (width - fullWidth) / 2 @@ -385,7 +502,7 @@ })) currentContainerDescendants = layoutContainerDescendants ?? new Map() - lastNodes = [nodes, newNodes] + lastNodes = [nodes, nodeExtraSpace, newNodes] return newNodes } @@ -609,17 +726,20 @@ return } - // console.log('compute') + const graphNodeDeps = Object.values(graph.nodes).map((n) => ({ + id: n.id, + parentIds: n.parentIds, + data: { assets: (n.data as any).assets, module: (n.data as any).module } + })) - let layoutedNodes = layoutNodes( - Object.values(graph.nodes).map((n) => ({ - id: n.id, - parentIds: n.parentIds, - data: { assets: (n.data as any).assets } - })) - ) + // Pre-compute extra space per node for assets, AI tools, group notes, group headers + const nodeExtraSpace = computeNodeExtraSpace(graphNodeDeps) + + // Layout with extra space baked into sugiyama + let layoutedNodes = layoutNodes(graphNodeDeps, nodeExtraSpace) let newNodes: (Node & NodeLayout)[] = layoutedNodes.map((n) => ({ ...n, ...graph.nodes[n.id] })) + // Compute asset visual nodes (no position remapping) let assetNodesResult = $showAssets ? computeAssetNodes( newNodes.map((n) => ({ @@ -629,25 +749,17 @@ })) ) : undefined - if (assetNodesResult) { - newNodes = newNodes.map((n) => ({ - ...n, - position: assetNodesResult.newNodePositions[n.id] - })) - } - let aiToolNodesResult = computeAIToolNodes(newNodes, eventHandler, insertable, flowModuleStates) - let nodesAfterAITools = newNodes.map((n) => ({ - ...n, - position: aiToolNodesResult.newNodePositions[n.id] - })) - let finalNodes = [ - ...nodesAfterAITools, + // Compute AI tool visual nodes (no position remapping) + let aiToolNodesResult = computeAIToolNodes(newNodes, eventHandler, insertable, flowModuleStates) + + let finalNodes: (Node & NodeLayout)[] = [ + ...newNodes, ...(assetNodesResult?.newAssetNodes ?? []), ...aiToolNodesResult.toolNodes ] - // Compute note nodes and positions + // Compute note nodes (no position remapping) let noteNodesResult = showNotes ? computeNoteNodes( finalNodes.map((n) => ({ @@ -668,56 +780,35 @@ ) : undefined - // Apply note positioning to nodes if notes are enabled - if (noteNodesResult) { - finalNodes = finalNodes.map((n) => ({ - ...n, - position: noteNodesResult.newNodePositions[n.id] || n.position - })) - } - - // Apply group label spacing (pushes nodes down like group notes do) - const groups = groupEditorContext?.groupEditor.getGroups() ?? [] - if (groups.length > 0) { - const noteHeights = showNotes ? (groupEditorContext?.groupEditor.getNoteHeights() ?? {}) : {} - const groupPositions = computeGroupSpacing( - groups, - finalNodes.map((n) => ({ id: n.id, position: n.position })), - noteHeights - ) - finalNodes = finalNodes.map((n) => ({ - ...n, - position: groupPositions[n.id] || n.position - })) - } - // Compute group header offsets for edges targeting topmost nodes in groups + const groups = groupEditorContext?.groupEditor.getGroups() ?? [] const groupHeaderOffsets: Record = {} - if (groups.length > 0) { - const noteHeightsForEdges = showNotes ? (groupEditorContext?.groupEditor.getNoteHeights() ?? {}) : {} + if (groups.length > 0 && nodeExtraSpace) { + // Use nodeExtraSpace directly - nodes with group headers already have the space + // The offset for edges equals the group header + note portion of the topPadding + const groupNoteHeights = showNotes ? (groupEditorContext?.groupEditor.getNoteHeights() ?? {}) : {} + const sortedNodes = topologicalSort(graphNodeDeps).reverse() for (const group of groups) { if (group.module_ids.length === 0) continue const collapsedNodeId = `collapsed-group:${group.id}` - let topY = Infinity + const hasCollapsedNode = graphNodeDeps.some((n) => n.id === collapsedNodeId) let topNodeId: string | undefined let isCollapsed = false - for (const node of finalNodes) { - if (node.id === collapsedNodeId && node.position.y < topY) { - topY = node.position.y - topNodeId = node.id - isCollapsed = true - } else if (group.module_ids.includes(node.id) && node.position.y < topY) { - topY = node.position.y - topNodeId = node.id - isCollapsed = false - } + + if (hasCollapsedNode) { + topNodeId = collapsedNodeId + isCollapsed = true + } else { + topNodeId = sortedNodes.find((node) => + group.module_ids.includes(node.id) + )?.id } + if (topNodeId) { - const noteHeight = noteHeightsForEdges[group.id] ?? 0 + const noteHeight = groupNoteHeights[group.id] ?? 0 const offset = isCollapsed ? GROUP_HEADER_HEIGHT + noteHeight : GROUP_HEADER_HEIGHT + noteHeight + GROUP_TOP_MARGIN - // Only set if larger (a node could be topmost in multiple groups theoretically) groupHeaderOffsets[topNodeId] = Math.max(groupHeaderOffsets[topNodeId] ?? 0, offset) } } diff --git a/frontend/src/lib/components/graph/compoundLayout.ts b/frontend/src/lib/components/graph/compoundLayout.ts index f793d4ce26..a1c66df892 100644 --- a/frontend/src/lib/components/graph/compoundLayout.ts +++ b/frontend/src/lib/components/graph/compoundLayout.ts @@ -231,6 +231,29 @@ function runSugiyama( * 5. Run sugiyama on the simplified graph * 6. Expand wrapper positions back to absolute positions */ +/** + * Build nodeSizes map for sugiyama from nodeExtraSpace. + * Each node's effective height = top + NODE.height + bottom. + */ +function buildNodeSizes( + nodeIds: string[], + constants: LayoutConstants, + nodeExtraSpace?: Map +): Map | undefined { + if (!nodeExtraSpace || nodeExtraSpace.size === 0) return undefined + const sizes = new Map() + for (const id of nodeIds) { + const extra = nodeExtraSpace.get(id) + if (extra && (extra.top > 0 || extra.bottom > 0)) { + sizes.set(id, { + width: constants.nodeWidth, + height: constants.nodeHeight + extra.top + extra.bottom + }) + } + } + return sizes.size > 0 ? sizes : undefined +} + const MAX_RECURSION_DEPTH = 50 function layoutLevel( @@ -239,7 +262,8 @@ function layoutLevel( constants: LayoutConstants, childrenMap: Map, containerDescendants: Map, - depth: number = 0 + depth: number = 0, + nodeExtraSpace?: Map ): LayoutResult { const positions = new Map() const nodeIdSet = new Set(nodeIds) @@ -258,8 +282,15 @@ function layoutLevel( const n = allNodes.get(id)! return { id, parentIds: (n.parentIds ?? []).filter((pid) => nodeIdSet.has(pid)) } }) - const result = runSugiyama(flatNodes, constants) + const extraSizes = buildNodeSizes( + flatNodes.map((n) => n.id), + constants, + nodeExtraSpace + ) + const result = runSugiyama(flatNodes, constants, extraSizes) for (const [id, pos] of result.positions) { + const extra = nodeExtraSpace?.get(id) + if (extra) pos.y += extra.top positions.set(id, pos) } return { positions, bbox: { width: result.width, height: result.height }, contentMinX: 0 } @@ -339,7 +370,8 @@ function layoutLevel( constants, childrenMap, containerDescendants, - depth + 1 + depth + 1, + nodeExtraSpace ) branchLayouts.push({ @@ -413,13 +445,30 @@ function layoutLevel( } // Step 5: Run sugiyama on flattened nodes - const sugResult = runSugiyama(flatNodes, constants, wrapperSizes) + // Merge wrapperSizes with nodeExtraSpace-derived sizes for non-group nodes + const extraSizes = buildNodeSizes( + flatNodes.map((n) => n.id), + constants, + nodeExtraSpace + ) + const mergedSizes = new Map() + if (extraSizes) { + for (const [id, size] of extraSizes) mergedSizes.set(id, size) + } + for (const [id, size] of wrapperSizes) mergedSizes.set(id, size) + const sugResult = runSugiyama( + flatNodes, + constants, + mergedSizes.size > 0 ? mergedSizes : undefined + ) // Step 6: Resolve absolute positions // First, set positions for regular (non-group) nodes + // Apply per-node y-offset from nodeExtraSpace so decorations above have room for (const [nid, pos] of sugResult.positions) { if (groupByHeadId.has(nid)) continue // Handle groups separately - positions.set(nid, { x: pos.x, y: pos.y }) + const extra = nodeExtraSpace?.get(nid) + positions.set(nid, { x: pos.x, y: pos.y + (extra?.top ?? 0) }) } // Now expand group wrappers into absolute positions @@ -431,7 +480,9 @@ function layoutLevel( const isBranch = gl.group.type === 'branch' // Position the head node at the top-center of the wrapper - positions.set(headId, { x: wrapperPos.x, y: wrapperPos.y }) + // Apply extra top padding so decorations above the head node have room + const headExtra = nodeExtraSpace?.get(headId) + positions.set(headId, { x: wrapperPos.x, y: wrapperPos.y + (headExtra?.top ?? 0) }) if (isBranch) { // Reuse cached branchWidths and totalWidth @@ -486,11 +537,13 @@ function layoutLevel( let maxX = -Infinity let minY = Infinity let maxY = -Infinity - for (const pos of positions.values()) { + for (const [nid, pos] of positions) { + const extra = nodeExtraSpace?.get(nid) minX = Math.min(minX, pos.x - constants.nodeWidth / 2) maxX = Math.max(maxX, pos.x + constants.nodeWidth / 2) - minY = Math.min(minY, pos.y) - maxY = Math.max(maxY, pos.y + constants.nodeHeight) + // Account for top decoration space above the node + minY = Math.min(minY, pos.y - (extra?.top ?? 0)) + maxY = Math.max(maxY, pos.y + constants.nodeHeight + (extra?.bottom ?? 0)) } // Account for group wrapper extents in bbox (e.g. LOOP_INDENT makes wrappers wider than nodes) for (const [headId, gl] of groupLayouts) { @@ -518,10 +571,15 @@ function layoutLevel( * * Takes the flat list of nodes and edges from graphBuilder and produces * absolute positions that account for compound structure (branches, loops). + * + * nodeExtraSpace: per-node top/bottom padding that should be allocated in layout. + * After layout, each node's y is shifted down by its top padding so decorations + * (assets, AI tools, group headers) have room above. */ export function compoundLayout( nodes: { id: string; parentIds?: string[] }[], - constants?: Partial + constants?: Partial, + nodeExtraSpace?: Map ): LayoutResult { const c: LayoutConstants = { nodeWidth: constants?.nodeWidth ?? NODE.width, @@ -547,7 +605,15 @@ export function compoundLayout( const nodeIds = nodes.map((n) => n.id) const containerDescendants = new Map() - const result = layoutLevel(nodeIds, allNodes, c, childrenMap, containerDescendants) + const result = layoutLevel( + nodeIds, + allNodes, + c, + childrenMap, + containerDescendants, + 0, + nodeExtraSpace + ) // Shift positions so minX=0 (left-aligned). // FlowGraphV2 centers with: xCenter = viewport/2 - bbox.width/2 diff --git a/frontend/src/lib/components/graph/noteUtils.svelte.ts b/frontend/src/lib/components/graph/noteUtils.svelte.ts index 3350e07ce8..2fa147f267 100644 --- a/frontend/src/lib/components/graph/noteUtils.svelte.ts +++ b/frontend/src/lib/components/graph/noteUtils.svelte.ts @@ -19,13 +19,11 @@ export type NodeDep = { export type NoteComputeResult = { noteNodes: (Node & NodeLayout)[] - newNodePositions: Record } export type AIToolSpacingInfo = { toolNodes: (Node & NodeLayout)[] toolEdges: any[] - newNodePositions: Record } export interface GroupNoteBounds { @@ -283,14 +281,9 @@ export function computeNoteNodes( const allNoteNodes: (Node & NodeLayout)[] = [] - // Build a map of Y positions that need extra spacing for group notes - const yPosMap: Record = {} // Y position -> spacing needed - - // Group notes that need spacing + // Find topmost node per group note for layout calculation const groupNotes = notes.filter((n) => n.type === 'group') - const topMostNodesMap: Record = {} - const sortedNodes = topologicalSort(nodes).reverse() for (const groupNote of groupNotes) { @@ -298,47 +291,12 @@ export function computeNoteNodes( const topmostNodeId = sortedNodes.find((node) => groupNote.contained_node_ids?.includes(node.id) )?.id - const topmostNode = nodes.find((node) => node.id === topmostNodeId) - if (topmostNode) { - const textHeight = noteTextHeights[groupNote.id] || 60 - const spacing = textHeight + 16 // padding - // Mark this Y position as needing spacing - yPosMap[topmostNode.position.y] = Math.max(yPosMap[topmostNode.position.y] || 0, spacing) - topMostNodesMap[groupNote.id] = topmostNode.id + if (topmostNodeId) { + topMostNodesMap[groupNote.id] = topmostNodeId } } } - // Calculate new positions for nodes (offset by group notes) - const sortedNewNodes = nodes - .map((n) => ({ position: { ...n.position }, id: n.id })) - .sort((a, b) => a.position.y - b.position.y) - - let currentYOffset = 0 - let prevYPos = NaN - - for (const node of sortedNewNodes) { - if (node.position.y !== prevYPos) { - // Add spacing for group notes at this Y level - if (yPosMap[node.position.y]) { - currentYOffset += yPosMap[node.position.y] - } - prevYPos = node.position.y - } - node.position.y += currentYOffset - } - - // Create note nodes AFTER calculating adjusted node positions - // For group notes, we need to use the adjusted node positions - const adjustedNodes = sortedNewNodes.map((n) => { - const origNode = nodes.find((orig) => orig.id === n.id) - return { - ...n, - data: origNode?.data, - type: origNode?.type - } - }) - // Calculate all z-indexes at once using hierarchy information const noteZIndexes = calculateAllNoteZIndexes(notes, nodes) @@ -346,11 +304,11 @@ export function computeNoteNodes( const isGroupNote = note.type === 'group' const zIndex = noteZIndexes[note.id] - // Calculate position and size using adjusted node positions for group notes + // Calculate position and size using node positions for group notes const { position, size } = isGroupNote ? calculateGroupNoteLayout( note, - adjustedNodes, + nodes, noteTextHeights[note.id] || 60, topMostNodesMap[note.id] ) @@ -375,13 +333,8 @@ export function computeNoteNodes( allNoteNodes.push(noteNode) } - const newNodePositions: Record = Object.fromEntries( - sortedNewNodes.map((n) => [n.id, n.position]) - ) - const result: NoteComputeResult = { - noteNodes: allNoteNodes, - newNodePositions + noteNodes: allNoteNodes } // Cache the result diff --git a/frontend/src/lib/components/graph/renderers/nodes/AIToolNode.svelte b/frontend/src/lib/components/graph/renderers/nodes/AIToolNode.svelte index 28e9d58fa4..d7fda2b128 100644 --- a/frontend/src/lib/components/graph/renderers/nodes/AIToolNode.svelte +++ b/frontend/src/lib/components/graph/renderers/nodes/AIToolNode.svelte @@ -56,7 +56,6 @@ ): { toolNodes: (Node & NodeLayout)[] toolEdges: Edge[] - newNodePositions: Record } { if ( computeAIToolNodesCache && @@ -69,14 +68,6 @@ const allToolNodes: (Node & NodeLayout)[] = [] const allToolEdges: Edge[] = [] - const yPosMap: Record< - number, - { - rows: number - placement: 'above' | 'below' - } - > = {} - for (const node of nodes) { if (node.type !== 'module' || node.data.module.value.type !== 'aiagent') continue // by default we assume we will show tools above @@ -135,17 +126,6 @@ } const totalRows = Math.ceil(tools.length / MAX_TOOLS_PER_ROW) + (insertable ? 1 : 0) // + 1 for add tool node when insertable - if (agentActions) { - yPosMap[node.position.y] = { - rows: totalRows, - placement: 'below' - } - } else { - yPosMap[node.position.y] = { - rows: totalRows, - placement: 'above' - } - } const toolNodes: (Node & AiToolN)[] = tools.map((tool, i) => { let inputToolXGap = 12 @@ -215,36 +195,9 @@ } } - const sortedNewNodes = nodes - .filter((n) => n.type !== 'asset') - .map((n) => ({ id: n.id, position: $state.snapshot(n.position) })) - .sort((a, b) => a.position.y - b.position.y) - let currentYOffset = 0 - let prevYPos = NaN - for (const node of sortedNewNodes) { - if (node.position.y !== prevYPos) { - // if agent actions, we need to shift the node above - if (yPosMap[prevYPos]?.placement === 'below') { - currentYOffset += AI_TOOL_BASE_OFFSET + AI_TOOL_ROW_OFFSET * yPosMap[prevYPos].rows - } - if (yPosMap[node.position.y]?.placement === 'above') { - currentYOffset += AI_TOOL_BASE_OFFSET + AI_TOOL_ROW_OFFSET * yPosMap[node.position.y].rows - } - - prevYPos = node.position.y - } - - node.position.y += currentYOffset - } - let ret: ReturnType = { toolNodes: allToolNodes, - toolEdges: allToolEdges, - newNodePositions: Object.fromEntries( - sortedNewNodes.map((n) => { - return [n.id, n.position] - }) - ) + toolEdges: allToolEdges } computeAIToolNodesCache = { diff --git a/frontend/src/lib/components/graph/renderers/nodes/AssetNode.svelte b/frontend/src/lib/components/graph/renderers/nodes/AssetNode.svelte index aec1fb03f8..50e4f9c87a 100644 --- a/frontend/src/lib/components/graph/renderers/nodes/AssetNode.svelte +++ b/frontend/src/lib/components/graph/renderers/nodes/AssetNode.svelte @@ -19,8 +19,6 @@ export function computeAssetNodes(nodes: NodeDep[]): { newAssetNodes: (Node & NodeLayout)[] newAssetEdges: Edge[] - // Nodes need to be offset on the y axis to make space for the asset nodes - newNodePositions: Record } { if (computeAssetNodesCache && deepEqual(nodes, computeAssetNodesCache[0])) { return computeAssetNodesCache[1] @@ -30,8 +28,6 @@ const allAssetNodes: (Node & NodeLayout)[] = [] const allAssetEdges: Edge[] = [] - const yPosMap: Record = {} - for (const node of nodes) { const assets = node.data.assets ?? [] if (!assets.length) continue @@ -47,13 +43,6 @@ const overflowedInputAssets = inputAssets.slice(3) const overflowedOutputAssets = outputAssets.slice(3) - // This allows calculating which nodes to offset on the y axis to - // make space for the asset nodes - if (inputAssets.length || outputAssets.length) - yPosMap[node.position.y] = yPosMap[node.position.y] ?? {} - if (inputAssets.length) yPosMap[node.position.y].r = true - if (outputAssets.length) yPosMap[node.position.y].w = true - // All asset nodes displayed on top const inputAssetNodes: (Node & AssetN)[] = displayedInputAssets.map((asset, i) => { let inputAssetXGap = 12 @@ -187,26 +176,9 @@ }) } - // Shift all nodes to make space for the new asset nodes - const sortedNewNodes = nodes - .map((n) => ({ position: { ...n.position }, id: n.id })) - .sort((a, b) => a.position.y - b.position.y) - - let currentYOffset = 0 - let prevYPos = NaN - for (const node of sortedNewNodes) { - if (node.position.y !== prevYPos) { - if (yPosMap[prevYPos]?.w) currentYOffset += NODE_WITH_WRITE_ASSET_Y_OFFSET - if (yPosMap[node.position.y]?.r) currentYOffset += NODE_WITH_READ_ASSET_Y_OFFSET - prevYPos = node.position.y - } - node.position.y += currentYOffset - } - let ret: ReturnType = { newAssetNodes: allAssetNodes, - newAssetEdges: allAssetEdges, - newNodePositions: Object.fromEntries(sortedNewNodes.map((n) => [n.id, n.position])) + newAssetEdges: allAssetEdges } computeAssetNodesCache = [clone(nodes), ret] return ret