Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 19 additions & 35 deletions packages/ui/src/views/agentexecutions/ExecutionDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,19 +394,25 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose,
})

// Identify iteration nodes and their children
const iterationGroups = new Map() // parentId -> Map of iterationIndex -> nodes
const iterationGroups = new Map() // parent uniqueNodeId -> Map of iterationIndex -> nodes
const lastSeenIndices = new Map()

// Group iteration child nodes by their parent and iteration index
nodes.forEach((node, index) => {
lastSeenIndices.set(node.nodeId, index)
if (node.data?.parentNodeId && node.data?.iterationIndex !== undefined) {
const parentId = node.data.parentNodeId
const iterationIndex = node.data.iterationIndex
const parentIndex = lastSeenIndices.get(parentId)
const parentUniqueNodeId = parentIndex !== undefined ? `${parentId}_${parentIndex}` : null

if (!iterationGroups.has(parentId)) {
iterationGroups.set(parentId, new Map())
if (!parentUniqueNodeId) return
Comment on lines +397 to +409

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of performing a backward linear scan ($O(N)$) for every iteration child node to find its parent node's unique ID, we can maintain a map of the most recently seen index for each nodeId as we iterate. This optimizes the lookup to $O(1)$ time complexity, preventing potential UI performance degradation when rendering large execution trees.

        const iterationGroups = new Map() // parent uniqueNodeId -> Map of iterationIndex -> nodes
        const lastSeenIndices = new Map()

        // Group iteration child nodes by their parent and iteration index
        nodes.forEach((node, index) => {
            lastSeenIndices.set(node.nodeId, index)
            if (node.data?.parentNodeId && node.data?.iterationIndex !== undefined) {
                const parentId = node.data.parentNodeId
                const iterationIndex = node.data.iterationIndex
                const parentIndex = lastSeenIndices.get(parentId)
                const parentUniqueNodeId = parentIndex !== undefined ? parentId + '_' + parentIndex : null

                if (!parentUniqueNodeId) return

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 9728aa1 to keep a last-seen parent index map during traversal, which removes the per-child backward scan while preserving the unique node id format.


if (!iterationGroups.has(parentUniqueNodeId)) {
iterationGroups.set(parentUniqueNodeId, new Map())
}

const iterationMap = iterationGroups.get(parentId)
const iterationMap = iterationGroups.get(parentUniqueNodeId)
if (!iterationMap.has(iterationIndex)) {
iterationMap.set(iterationIndex, [])
}
Expand All @@ -416,16 +422,10 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose,
})

// Create virtual iteration container nodes
iterationGroups.forEach((iterationMap, parentId) => {
iterationGroups.forEach((iterationMap, parentUniqueNodeId) => {
iterationMap.forEach((nodeIds, iterationIndex) => {
// Find the parent iteration node
let parentNode = null
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].nodeId === parentId) {
parentNode = nodes[i]
break
}
}
// Find the parent iteration node instance
const parentNode = nodeMap.get(parentUniqueNodeId)

if (!parentNode) return

Expand All @@ -435,7 +435,7 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose,
const iterationContext = firstChild?.data?.iterationContext || { index: iterationIndex }

// Create a virtual node for this iteration
const iterationNodeId = `${parentId}_${iterationIndex}`
const iterationNodeId = `${parentUniqueNodeId}_iteration_${iterationIndex}`
const iterationLabel = `Iteration #${iterationIndex}`

// Determine status based on child nodes
Expand All @@ -457,7 +457,7 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose,
iterationIndex,
iterationContext,
isVirtualNode: true,
parentIterationId: parentId
parentIterationId: parentNode.nodeId
},
previousNodeIds: [], // Will be handled in the main tree building
status: iterationStatus,
Expand Down Expand Up @@ -522,31 +522,14 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose,
})

// Second pass: Build the iteration sub-trees
iterationGroups.forEach((iterationMap, parentId) => {
// Find all instances of the parent node
const parentInstances = []
nodes.forEach((node, index) => {
if (node.nodeId === parentId) {
parentInstances.push(`${node.nodeId}_${index}`)
}
})

// Find the latest instance of the parent node that exists in the tree
let latestParent = null
for (let i = parentInstances.length - 1; i >= 0; i--) {
const parentId = parentInstances[i]
const parent = nodeMap.get(parentId)
if (parent) {
latestParent = parent
break
}
}
iterationGroups.forEach((iterationMap, parentUniqueNodeId) => {
const latestParent = nodeMap.get(parentUniqueNodeId)

if (!latestParent) return

// Add all virtual iteration nodes to the parent
iterationMap.forEach((nodeIds, iterationIndex) => {
const iterationNodeId = `${parentId}_${iterationIndex}`
const iterationNodeId = `${parentUniqueNodeId}_iteration_${iterationIndex}`
const virtualNode = nodeMap.get(iterationNodeId)
if (virtualNode) {
latestParent.children.push(virtualNode)
Expand All @@ -572,6 +555,7 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose,
potentialParent.nodeId === prevNodeId &&
potentialParent.data?.iterationIndex === node.data?.iterationIndex &&
potentialParent.data?.parentNodeId === node.data?.parentNodeId &&
potentialParent.virtualParentId === node.virtualParentId &&
!parentFound
) {
potentialParent.children.push(node)
Expand Down
54 changes: 19 additions & 35 deletions packages/ui/src/views/chatmessage/AgentExecutedDataCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -369,19 +369,25 @@ const AgentExecutedDataCard = ({ status, execution, agentflowId, sessionId }) =>
})

// Identify iteration nodes and their children
const iterationGroups = new Map() // parentId -> Map of iterationIndex -> nodes
const iterationGroups = new Map() // parent uniqueNodeId -> Map of iterationIndex -> nodes
const lastSeenIndices = new Map()

// Group iteration child nodes by their parent and iteration index
nodes.forEach((node, index) => {
lastSeenIndices.set(node.nodeId, index)
if (node.data?.parentNodeId && node.data?.iterationIndex !== undefined) {
const parentId = node.data.parentNodeId
const iterationIndex = node.data.iterationIndex
const parentIndex = lastSeenIndices.get(parentId)
const parentUniqueNodeId = parentIndex !== undefined ? `${parentId}_${parentIndex}` : null

if (!iterationGroups.has(parentId)) {
iterationGroups.set(parentId, new Map())
if (!parentUniqueNodeId) return
Comment on lines +372 to +384

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of performing a backward linear scan ($O(N)$) for every iteration child node to find its parent node's unique ID, we can maintain a map of the most recently seen index for each nodeId as we iterate. This optimizes the lookup to $O(1)$ time complexity, preventing potential UI performance degradation when rendering large execution trees.

        const iterationGroups = new Map() // parent uniqueNodeId -> Map of iterationIndex -> nodes
        const lastSeenIndices = new Map()

        // Group iteration child nodes by their parent and iteration index
        nodes.forEach((node, index) => {
            lastSeenIndices.set(node.nodeId, index)
            if (node.data?.parentNodeId && node.data?.iterationIndex !== undefined) {
                const parentId = node.data.parentNodeId
                const iterationIndex = node.data.iterationIndex
                const parentIndex = lastSeenIndices.get(parentId)
                const parentUniqueNodeId = parentIndex !== undefined ? parentId + '_' + parentIndex : null

                if (!parentUniqueNodeId) return

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 9728aa1 to keep a last-seen parent index map during traversal, which removes the per-child backward scan while preserving the unique node id format.


if (!iterationGroups.has(parentUniqueNodeId)) {
iterationGroups.set(parentUniqueNodeId, new Map())
}

const iterationMap = iterationGroups.get(parentId)
const iterationMap = iterationGroups.get(parentUniqueNodeId)
if (!iterationMap.has(iterationIndex)) {
iterationMap.set(iterationIndex, [])
}
Expand All @@ -391,16 +397,10 @@ const AgentExecutedDataCard = ({ status, execution, agentflowId, sessionId }) =>
})

// Create virtual iteration container nodes
iterationGroups.forEach((iterationMap, parentId) => {
iterationGroups.forEach((iterationMap, parentUniqueNodeId) => {
iterationMap.forEach((nodeIds, iterationIndex) => {
// Find the parent iteration node
let parentNode = null
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].nodeId === parentId) {
parentNode = nodes[i]
break
}
}
// Find the parent iteration node instance
const parentNode = nodeMap.get(parentUniqueNodeId)

if (!parentNode) return

Expand All @@ -410,7 +410,7 @@ const AgentExecutedDataCard = ({ status, execution, agentflowId, sessionId }) =>
const iterationContext = firstChild?.data?.iterationContext || { index: iterationIndex }

// Create a virtual node for this iteration
const iterationNodeId = `${parentId}_${iterationIndex}`
const iterationNodeId = `${parentUniqueNodeId}_iteration_${iterationIndex}`
const iterationLabel = `Iteration #${iterationIndex}`

// Determine status based on child nodes
Expand All @@ -432,7 +432,7 @@ const AgentExecutedDataCard = ({ status, execution, agentflowId, sessionId }) =>
iterationIndex,
iterationContext,
isVirtualNode: true,
parentIterationId: parentId
parentIterationId: parentNode.nodeId
},
previousNodeIds: [], // Will be handled in the main tree building
status: iterationStatus,
Expand Down Expand Up @@ -497,31 +497,14 @@ const AgentExecutedDataCard = ({ status, execution, agentflowId, sessionId }) =>
})

// Second pass: Build the iteration sub-trees
iterationGroups.forEach((iterationMap, parentId) => {
// Find all instances of the parent node
const parentInstances = []
nodes.forEach((node, index) => {
if (node.nodeId === parentId) {
parentInstances.push(`${node.nodeId}_${index}`)
}
})

// Find the latest instance of the parent node that exists in the tree
let latestParent = null
for (let i = parentInstances.length - 1; i >= 0; i--) {
const parentId = parentInstances[i]
const parent = nodeMap.get(parentId)
if (parent) {
latestParent = parent
break
}
}
iterationGroups.forEach((iterationMap, parentUniqueNodeId) => {
const latestParent = nodeMap.get(parentUniqueNodeId)

if (!latestParent) return

// Add all virtual iteration nodes to the parent
iterationMap.forEach((nodeIds, iterationIndex) => {
const iterationNodeId = `${parentId}_${iterationIndex}`
const iterationNodeId = `${parentUniqueNodeId}_iteration_${iterationIndex}`
const virtualNode = nodeMap.get(iterationNodeId)
if (virtualNode) {
latestParent.children.push(virtualNode)
Expand All @@ -547,6 +530,7 @@ const AgentExecutedDataCard = ({ status, execution, agentflowId, sessionId }) =>
potentialParent.nodeId === prevNodeId &&
potentialParent.data?.iterationIndex === node.data?.iterationIndex &&
potentialParent.data?.parentNodeId === node.data?.parentNodeId &&
potentialParent.virtualParentId === node.virtualParentId &&
!parentFound
) {
potentialParent.children.push(node)
Expand Down