feat(workflow): add human input node formatting to retain latest status

This commit is contained in:
twwu 2026-01-27 17:09:00 +08:00
parent 8a20a59d72
commit 8897633e42
3 changed files with 62 additions and 2 deletions

View File

@ -2,7 +2,6 @@
import type { FC } from 'react'
import type { WorkflowRunDetailResponse } from '@/models/log'
import type { NodeTracing } from '@/types/workflow'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'

View File

@ -0,0 +1,59 @@
import type { NodeTracing } from '@/types/workflow'
import { BlockEnum } from '@/app/components/workflow/types'
/**
* Format human-input nodes to ensure only the latest status is kept for each node.
* Human-input nodes can have multiple log entries as their status changes
* (e.g., running -> paused -> succeeded/failed).
* This function keeps only the entry with the latest index for each unique node_id.
*/
const formatHumanInputNode = (list: NodeTracing[]): NodeTracing[] => {
// Group human-input nodes by node_id
const humanInputNodeMap = new Map<string, NodeTracing>()
// Track which node_ids are human-input type
const humanInputNodeIds = new Set<string>()
// First pass: identify human-input nodes and keep the one with the highest index
list.forEach((item) => {
if (item.node_type === BlockEnum.HumanInput) {
humanInputNodeIds.add(item.node_id)
const existingNode = humanInputNodeMap.get(item.node_id)
if (!existingNode || item.index > existingNode.index) {
humanInputNodeMap.set(item.node_id, item)
}
}
})
// If no human-input nodes, return the list as is
if (humanInputNodeIds.size === 0)
return list
// Second pass: filter the list to remove duplicate human-input nodes
// and keep only the latest one for each node_id
const result: NodeTracing[] = []
const addedHumanInputNodeIds = new Set<string>()
list.forEach((item) => {
if (item.node_type === BlockEnum.HumanInput) {
// Only add the human-input node with the highest index
if (!addedHumanInputNodeIds.has(item.node_id)) {
const latestNode = humanInputNodeMap.get(item.node_id)
if (latestNode) {
result.push(latestNode)
addedHumanInputNodeIds.add(item.node_id)
}
}
// Skip duplicate human-input nodes
}
else {
// Keep all non-human-input nodes
result.push(item)
}
})
return result
}
export default formatHumanInputNode

View File

@ -2,6 +2,7 @@ import type { NodeTracing } from '@/types/workflow'
import { cloneDeep } from 'es-toolkit/object'
import { BlockEnum } from '../../../types'
import formatAgentNode from './agent'
import formatHumanInputNode from './human-input'
import { addChildrenToIterationNode } from './iteration'
import { addChildrenToLoopNode } from './loop'
import formatParallelNode from './parallel'
@ -83,7 +84,8 @@ const formatToTracingNodeList = (list: NodeTracing[], t: any) => {
* Because Handle struct node will put the node in different
*/
const formattedAgentList = formatAgentNode(allItems)
const formattedRetryList = formatRetryNode(formattedAgentList) // retry one node
const formattedHumanInputList = formatHumanInputNode(formattedAgentList) // Keep only latest status for human-input nodes
const formattedRetryList = formatRetryNode(formattedHumanInputList) // retry one node
// would change the structure of the list. Iteration and parallel can include each other.
const formattedLoopAndIterationList = formatIterationAndLoopNode(formattedRetryList, t)
const formattedParallelList = formatParallelNode(formattedLoopAndIterationList, t)