mirror of
https://github.com/langgenius/dify.git
synced 2026-04-27 19:27:23 +08:00
fix(web): assign in-progress tracing items to latest loop/iteration record (#34661)
Co-authored-by: Blackoutta <37723456+Blackoutta@users.noreply.github.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> Co-authored-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
7f4bf19186
commit
5d4d60bb95
@ -0,0 +1,126 @@
|
|||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import type { LoopVariableMap, NodeTracing } from '@/types/workflow'
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import { BlockEnum } from '../../../types'
|
||||||
|
import LoopResultPanel from '../loop-result-panel'
|
||||||
|
|
||||||
|
const mockCodeEditor = vi.hoisted(() => vi.fn())
|
||||||
|
const mockTracingPanel = vi.hoisted(() => vi.fn())
|
||||||
|
|
||||||
|
vi.mock('@/app/components/workflow/nodes/_base/components/editor/code-editor', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: (props: { title: ReactNode, value: unknown }) => {
|
||||||
|
mockCodeEditor(props)
|
||||||
|
return (
|
||||||
|
<section data-testid="code-editor">
|
||||||
|
<div>{props.title}</div>
|
||||||
|
<div>{JSON.stringify(props.value)}</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/app/components/workflow/run/tracing-panel', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: (props: { list: NodeTracing[], className?: string }) => {
|
||||||
|
mockTracingPanel(props)
|
||||||
|
return <div data-testid="tracing-panel">{props.list.length}</div>
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const createNodeTracing = (id: string, executionMetadata?: NonNullable<NodeTracing['execution_metadata']>): NodeTracing => ({
|
||||||
|
id,
|
||||||
|
index: 0,
|
||||||
|
predecessor_node_id: '',
|
||||||
|
node_id: `node-${id}`,
|
||||||
|
node_type: BlockEnum.Code,
|
||||||
|
title: `Node ${id}`,
|
||||||
|
inputs: {},
|
||||||
|
inputs_truncated: false,
|
||||||
|
process_data: {},
|
||||||
|
process_data_truncated: false,
|
||||||
|
outputs: {},
|
||||||
|
outputs_truncated: false,
|
||||||
|
status: 'succeeded',
|
||||||
|
error: '',
|
||||||
|
elapsed_time: 0,
|
||||||
|
execution_metadata: executionMetadata,
|
||||||
|
metadata: {
|
||||||
|
iterator_length: 0,
|
||||||
|
iterator_index: 0,
|
||||||
|
loop_length: 0,
|
||||||
|
loop_index: 0,
|
||||||
|
},
|
||||||
|
created_at: 0,
|
||||||
|
created_by: {
|
||||||
|
id: 'user-1',
|
||||||
|
name: 'Tester',
|
||||||
|
email: 'tester@example.com',
|
||||||
|
},
|
||||||
|
finished_at: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('LoopResultPanel', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Loop variables should be resolved by the actual run key, not the rendered row position.
|
||||||
|
describe('Loop Variable Resolution', () => {
|
||||||
|
it('should read loop variables by the actual loop index when rows are compacted', () => {
|
||||||
|
const loopVariableMap: LoopVariableMap = {
|
||||||
|
2: { item: 'alpha' },
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<LoopResultPanel
|
||||||
|
list={[[
|
||||||
|
createNodeTracing('loop-2-step-1', {
|
||||||
|
total_tokens: 0,
|
||||||
|
total_price: 0,
|
||||||
|
currency: 'USD',
|
||||||
|
loop_index: 2,
|
||||||
|
}),
|
||||||
|
]]}
|
||||||
|
onBack={vi.fn()}
|
||||||
|
loopVariableMap={loopVariableMap}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('workflow.singleRun.loop 1'))
|
||||||
|
|
||||||
|
expect(screen.getByTestId('code-editor')).toHaveTextContent('{"item":"alpha"}')
|
||||||
|
expect(mockCodeEditor).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
value: loopVariableMap[2],
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should read loop variables by parallel run id when available', () => {
|
||||||
|
const loopVariableMap: LoopVariableMap = {
|
||||||
|
'parallel-1': { item: 'beta' },
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<LoopResultPanel
|
||||||
|
list={[[
|
||||||
|
createNodeTracing('parallel-step-1', {
|
||||||
|
total_tokens: 0,
|
||||||
|
total_price: 0,
|
||||||
|
currency: 'USD',
|
||||||
|
parallel_mode_run_id: 'parallel-1',
|
||||||
|
}),
|
||||||
|
]]}
|
||||||
|
onBack={vi.fn()}
|
||||||
|
loopVariableMap={loopVariableMap}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('workflow.singleRun.loop 1'))
|
||||||
|
|
||||||
|
expect(screen.getByTestId('code-editor')).toHaveTextContent('{"item":"beta"}')
|
||||||
|
expect(mockCodeEditor).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
value: loopVariableMap['parallel-1'],
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -19,6 +19,18 @@ import { cn } from '@/utils/classnames'
|
|||||||
|
|
||||||
const i18nPrefix = 'singleRun'
|
const i18nPrefix = 'singleRun'
|
||||||
|
|
||||||
|
const getLoopRunKey = (loop: NodeTracing[], fallbackIndex: number) => {
|
||||||
|
const executionMetadata = loop[0]?.execution_metadata
|
||||||
|
|
||||||
|
if (executionMetadata?.parallel_mode_run_id !== undefined)
|
||||||
|
return executionMetadata.parallel_mode_run_id
|
||||||
|
|
||||||
|
if (executionMetadata?.loop_index !== undefined)
|
||||||
|
return String(executionMetadata.loop_index)
|
||||||
|
|
||||||
|
return String(fallbackIndex)
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
list: NodeTracing[][]
|
list: NodeTracing[][]
|
||||||
onBack: () => void
|
onBack: () => void
|
||||||
@ -42,10 +54,8 @@ const LoopResultPanel: FC<Props> = ({
|
|||||||
}))
|
}))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const countLoopDuration = (loop: NodeTracing[], loopDurationMap: LoopDurationMap): string => {
|
const countLoopDuration = (loop: NodeTracing[], index: number, loopDurationMap: LoopDurationMap): string => {
|
||||||
const loopRunIndex = loop[0]?.execution_metadata?.loop_index as number
|
const loopItem = loopDurationMap[getLoopRunKey(loop, index)]
|
||||||
const loopRunId = loop[0]?.execution_metadata?.parallel_mode_run_id
|
|
||||||
const loopItem = loopDurationMap[loopRunId || loopRunIndex]
|
|
||||||
const duration = loopItem
|
const duration = loopItem
|
||||||
return `${(duration && duration > 0.01) ? duration.toFixed(2) : 0.01}s`
|
return `${(duration && duration > 0.01) ? duration.toFixed(2) : 0.01}s`
|
||||||
}
|
}
|
||||||
@ -59,13 +69,13 @@ const LoopResultPanel: FC<Props> = ({
|
|||||||
return <RiErrorWarningLine className="h-4 w-4 text-text-destructive" />
|
return <RiErrorWarningLine className="h-4 w-4 text-text-destructive" />
|
||||||
|
|
||||||
if (isRunning)
|
if (isRunning)
|
||||||
return <RiLoader2Line className="h-3.5 w-3.5 animate-spin text-primary-600" />
|
return <RiLoader2Line className="text-primary-600 h-3.5 w-3.5 animate-spin" />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasDurationMap && (
|
{hasDurationMap && (
|
||||||
<div className="system-xs-regular text-text-tertiary">
|
<div className="system-xs-regular text-text-tertiary">
|
||||||
{countLoopDuration(loop, loopDurationMap)}
|
{countLoopDuration(loop, index, loopDurationMap)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<RiArrowRightSLine
|
<RiArrowRightSLine
|
||||||
@ -98,7 +108,7 @@ const LoopResultPanel: FC<Props> = ({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full cursor-pointer items-center justify-between px-3',
|
'flex w-full cursor-pointer items-center justify-between px-3',
|
||||||
expandedLoops[index] ? 'pb-2 pt-3' : 'py-3',
|
expandedLoops[index] ? 'pt-3 pb-2' : 'py-3',
|
||||||
'rounded-xl text-left',
|
'rounded-xl text-left',
|
||||||
)}
|
)}
|
||||||
onClick={() => toggleLoop(index)}
|
onClick={() => toggleLoop(index)}
|
||||||
@ -107,7 +117,7 @@ const LoopResultPanel: FC<Props> = ({
|
|||||||
<div className="flex h-4 w-4 shrink-0 items-center justify-center rounded-[5px] border-divider-subtle bg-util-colors-cyan-cyan-500">
|
<div className="flex h-4 w-4 shrink-0 items-center justify-center rounded-[5px] border-divider-subtle bg-util-colors-cyan-cyan-500">
|
||||||
<Loop className="h-3 w-3 text-text-primary-on-surface" />
|
<Loop className="h-3 w-3 text-text-primary-on-surface" />
|
||||||
</div>
|
</div>
|
||||||
<span className="system-sm-semibold-uppercase grow text-text-primary">
|
<span className="grow system-sm-semibold-uppercase text-text-primary">
|
||||||
{t(`${i18nPrefix}.loop`, { ns: 'workflow' })}
|
{t(`${i18nPrefix}.loop`, { ns: 'workflow' })}
|
||||||
{' '}
|
{' '}
|
||||||
{index + 1}
|
{index + 1}
|
||||||
@ -129,14 +139,14 @@ const LoopResultPanel: FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
loopVariableMap?.[index] && (
|
loopVariableMap?.[getLoopRunKey(loop, index)] && (
|
||||||
<div className="p-2 pb-0">
|
<div className="p-2 pb-0">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
readOnly
|
readOnly
|
||||||
title={<div>{t('nodes.loop.loopVariables', { ns: 'workflow' }).toLocaleUpperCase()}</div>}
|
title={<div>{t('nodes.loop.loopVariables', { ns: 'workflow' }).toLocaleUpperCase()}</div>}
|
||||||
language={CodeLanguage.json}
|
language={CodeLanguage.json}
|
||||||
height={112}
|
height={112}
|
||||||
value={loopVariableMap[index]}
|
value={loopVariableMap[getLoopRunKey(loop, index)]}
|
||||||
isJSONStringifyBeauty
|
isJSONStringifyBeauty
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { NodeTracing } from '@/types/workflow'
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
import { noop } from 'es-toolkit/function'
|
import { noop } from 'es-toolkit/function'
|
||||||
import format from '..'
|
import format, { addChildrenToIterationNode } from '..'
|
||||||
import graphToLogStruct from '../../graph-to-log-struct'
|
import graphToLogStruct from '../../graph-to-log-struct'
|
||||||
|
|
||||||
describe('iteration', () => {
|
describe('iteration', () => {
|
||||||
@ -9,15 +9,48 @@ describe('iteration', () => {
|
|||||||
it('result should have no nodes in iteration node', () => {
|
it('result should have no nodes in iteration node', () => {
|
||||||
expect(result.find(item => !!item.execution_metadata?.iteration_id)).toBeUndefined()
|
expect(result.find(item => !!item.execution_metadata?.iteration_id)).toBeUndefined()
|
||||||
})
|
})
|
||||||
// test('iteration should put nodes in details', () => {
|
|
||||||
// expect(result).toEqual([
|
it('should place the first child of a new iteration at a new record when its index is missing', () => {
|
||||||
// startNode,
|
const parent = { node_id: 'iter1', node_type: 'iteration', execution_metadata: {} } as unknown as NodeTracing
|
||||||
// {
|
const child0 = { node_id: 'code', execution_metadata: { iteration_id: 'iter1', iteration_index: 0 } } as unknown as NodeTracing
|
||||||
// ...iterationNode,
|
const streaming = { node_id: 'code', execution_metadata: { iteration_id: 'iter1' } } as unknown as NodeTracing
|
||||||
// details: [
|
|
||||||
// [iterations[0], iterations[1]],
|
const result = addChildrenToIterationNode(parent, [child0, streaming])
|
||||||
// ],
|
expect(result.details![0]).toEqual([child0])
|
||||||
// },
|
expect(result.details![1]).toEqual([streaming])
|
||||||
// ])
|
})
|
||||||
// })
|
|
||||||
|
it('should keep missing iteration_index items in the current record when the node has not restarted', () => {
|
||||||
|
const parent = {
|
||||||
|
node_id: 'iter1',
|
||||||
|
node_type: 'iteration',
|
||||||
|
execution_metadata: {
|
||||||
|
iteration_duration_map: { 0: 1.2, 1: 0.4 },
|
||||||
|
},
|
||||||
|
} as unknown as NodeTracing
|
||||||
|
const child0 = { node_id: 'code', execution_metadata: { iteration_id: 'iter1', iteration_index: 0 } } as unknown as NodeTracing
|
||||||
|
const child1 = { node_id: 'code', execution_metadata: { iteration_id: 'iter1', iteration_index: 1 } } as unknown as NodeTracing
|
||||||
|
const streaming = { node_id: 'tool', execution_metadata: { iteration_id: 'iter1' } } as unknown as NodeTracing
|
||||||
|
|
||||||
|
const result = addChildrenToIterationNode(parent, [child0, child1, streaming])
|
||||||
|
expect(result.details![0]).toEqual([child0])
|
||||||
|
expect(result.details![1]).toEqual([child1, streaming])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not jump to the latest iteration when an earlier item is missing iteration_index', () => {
|
||||||
|
const parent = {
|
||||||
|
node_id: 'iter1',
|
||||||
|
node_type: 'iteration',
|
||||||
|
execution_metadata: {
|
||||||
|
iteration_duration_map: { 0: 1.2, 1: 0.4 },
|
||||||
|
},
|
||||||
|
} as unknown as NodeTracing
|
||||||
|
const code0 = { node_id: 'code', execution_metadata: { iteration_id: 'iter1', iteration_index: 0 } } as unknown as NodeTracing
|
||||||
|
const tool = { node_id: 'tool', execution_metadata: { iteration_id: 'iter1' } } as unknown as NodeTracing
|
||||||
|
const code1 = { node_id: 'code', execution_metadata: { iteration_id: 'iter1', iteration_index: 1 } } as unknown as NodeTracing
|
||||||
|
|
||||||
|
const result = addChildrenToIterationNode(parent, [code0, tool, code1])
|
||||||
|
expect(result.details![0]).toEqual([code0, tool])
|
||||||
|
expect(result.details![1]).toEqual([code1])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4,15 +4,31 @@ import formatParallelNode from '../parallel'
|
|||||||
|
|
||||||
export function addChildrenToIterationNode(iterationNode: NodeTracing, childrenNodes: NodeTracing[]): NodeTracing {
|
export function addChildrenToIterationNode(iterationNode: NodeTracing, childrenNodes: NodeTracing[]): NodeTracing {
|
||||||
const details: NodeTracing[][] = []
|
const details: NodeTracing[][] = []
|
||||||
childrenNodes.forEach((item, index) => {
|
let lastResolvedIndex = -1
|
||||||
|
|
||||||
|
childrenNodes.forEach((item) => {
|
||||||
if (!item.execution_metadata)
|
if (!item.execution_metadata)
|
||||||
return
|
return
|
||||||
const { iteration_index = 0 } = item.execution_metadata
|
const { iteration_index } = item.execution_metadata
|
||||||
const runIndex: number = iteration_index !== undefined ? iteration_index : index
|
let runIndex: number
|
||||||
|
|
||||||
|
if (iteration_index !== undefined) {
|
||||||
|
runIndex = iteration_index
|
||||||
|
}
|
||||||
|
else if (lastResolvedIndex >= 0) {
|
||||||
|
const currentGroup = details[lastResolvedIndex] || []
|
||||||
|
const seenSameNodeInCurrentGroup = currentGroup.some(node => node.node_id === item.node_id)
|
||||||
|
runIndex = seenSameNodeInCurrentGroup ? lastResolvedIndex + 1 : lastResolvedIndex
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
runIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
if (!details[runIndex])
|
if (!details[runIndex])
|
||||||
details[runIndex] = []
|
details[runIndex] = []
|
||||||
|
|
||||||
details[runIndex].push(item)
|
details[runIndex].push(item)
|
||||||
|
lastResolvedIndex = runIndex
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
...iterationNode,
|
...iterationNode,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { NodeTracing } from '@/types/workflow'
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
import { noop } from 'es-toolkit/function'
|
import { noop } from 'es-toolkit/function'
|
||||||
import format from '..'
|
import format, { addChildrenToLoopNode } from '..'
|
||||||
import graphToLogStruct from '../../graph-to-log-struct'
|
import graphToLogStruct from '../../graph-to-log-struct'
|
||||||
|
|
||||||
describe('loop', () => {
|
describe('loop', () => {
|
||||||
@ -21,4 +21,48 @@ describe('loop', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should place the first child of a new loop run at a new record when its index is missing', () => {
|
||||||
|
const parent = { node_id: 'loop1', node_type: 'loop', execution_metadata: {} } as unknown as NodeTracing
|
||||||
|
const child0 = { node_id: 'code', execution_metadata: { loop_id: 'loop1', loop_index: 0 } } as unknown as NodeTracing
|
||||||
|
const streaming = { node_id: 'code', execution_metadata: { loop_id: 'loop1' } } as unknown as NodeTracing
|
||||||
|
|
||||||
|
const result = addChildrenToLoopNode(parent, [child0, streaming])
|
||||||
|
expect(result.details![0]).toEqual([child0])
|
||||||
|
expect(result.details![1]).toEqual([streaming])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should keep missing loop_index items in the current record when the node has not restarted', () => {
|
||||||
|
const parent = {
|
||||||
|
node_id: 'loop1',
|
||||||
|
node_type: 'loop',
|
||||||
|
execution_metadata: {
|
||||||
|
loop_duration_map: { 0: 1.2, 1: 0.4 },
|
||||||
|
},
|
||||||
|
} as unknown as NodeTracing
|
||||||
|
const child0 = { node_id: 'code', execution_metadata: { loop_id: 'loop1', loop_index: 0 } } as unknown as NodeTracing
|
||||||
|
const child1 = { node_id: 'code', execution_metadata: { loop_id: 'loop1', loop_index: 1 } } as unknown as NodeTracing
|
||||||
|
const streaming = { node_id: 'tool', execution_metadata: { loop_id: 'loop1' } } as unknown as NodeTracing
|
||||||
|
|
||||||
|
const result = addChildrenToLoopNode(parent, [child0, child1, streaming])
|
||||||
|
expect(result.details![0]).toEqual([child0])
|
||||||
|
expect(result.details![1]).toEqual([child1, streaming])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not jump to the latest loop when an earlier item is missing loop_index', () => {
|
||||||
|
const parent = {
|
||||||
|
node_id: 'loop1',
|
||||||
|
node_type: 'loop',
|
||||||
|
execution_metadata: {
|
||||||
|
loop_duration_map: { 0: 1.2, 1: 0.4 },
|
||||||
|
},
|
||||||
|
} as unknown as NodeTracing
|
||||||
|
const code0 = { node_id: 'code', execution_metadata: { loop_id: 'loop1', loop_index: 0 } } as unknown as NodeTracing
|
||||||
|
const tool = { node_id: 'tool', execution_metadata: { loop_id: 'loop1' } } as unknown as NodeTracing
|
||||||
|
const code1 = { node_id: 'code', execution_metadata: { loop_id: 'loop1', loop_index: 1 } } as unknown as NodeTracing
|
||||||
|
|
||||||
|
const result = addChildrenToLoopNode(parent, [code0, tool, code1])
|
||||||
|
expect(result.details![0]).toEqual([code0, tool])
|
||||||
|
expect(result.details![1]).toEqual([code1])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,20 +3,49 @@ import { BlockEnum } from '@/app/components/workflow/types'
|
|||||||
import formatParallelNode from '../parallel'
|
import formatParallelNode from '../parallel'
|
||||||
|
|
||||||
export function addChildrenToLoopNode(loopNode: NodeTracing, childrenNodes: NodeTracing[]): NodeTracing {
|
export function addChildrenToLoopNode(loopNode: NodeTracing, childrenNodes: NodeTracing[]): NodeTracing {
|
||||||
const details: NodeTracing[][] = []
|
const detailsByKey = new Map<string, NodeTracing[]>()
|
||||||
|
let lastResolvedIndex = -1
|
||||||
|
const order: string[] = []
|
||||||
|
|
||||||
|
const ensureGroup = (key: string) => {
|
||||||
|
const group = detailsByKey.get(key)
|
||||||
|
if (group)
|
||||||
|
return group
|
||||||
|
|
||||||
|
const newGroup: NodeTracing[] = []
|
||||||
|
detailsByKey.set(key, newGroup)
|
||||||
|
order.push(key)
|
||||||
|
return newGroup
|
||||||
|
}
|
||||||
|
|
||||||
childrenNodes.forEach((item) => {
|
childrenNodes.forEach((item) => {
|
||||||
if (!item.execution_metadata)
|
if (!item.execution_metadata)
|
||||||
return
|
return
|
||||||
const { parallel_mode_run_id, loop_index = 0 } = item.execution_metadata
|
const { parallel_mode_run_id, loop_index } = item.execution_metadata
|
||||||
const runIndex: number = (parallel_mode_run_id || loop_index) as number
|
let runIndex: number | string
|
||||||
if (!details[runIndex])
|
|
||||||
details[runIndex] = []
|
|
||||||
|
|
||||||
details[runIndex].push(item)
|
if (parallel_mode_run_id !== undefined) {
|
||||||
|
runIndex = parallel_mode_run_id
|
||||||
|
}
|
||||||
|
else if (loop_index !== undefined) {
|
||||||
|
runIndex = loop_index
|
||||||
|
}
|
||||||
|
else if (lastResolvedIndex >= 0) {
|
||||||
|
const currentGroup = detailsByKey.get(String(lastResolvedIndex)) || []
|
||||||
|
const seenSameNodeInCurrentGroup = currentGroup.some(node => node.node_id === item.node_id)
|
||||||
|
runIndex = seenSameNodeInCurrentGroup ? lastResolvedIndex + 1 : lastResolvedIndex
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
runIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureGroup(String(runIndex)).push(item)
|
||||||
|
if (typeof runIndex === 'number')
|
||||||
|
lastResolvedIndex = runIndex
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
...loopNode,
|
...loopNode,
|
||||||
details,
|
details: order.map(key => detailsByKey.get(key) || []),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11073,11 +11073,6 @@
|
|||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/workflow/run/loop-log/loop-result-panel.tsx": {
|
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
|
||||||
"count": 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"app/components/workflow/run/loop-result-panel.tsx": {
|
"app/components/workflow/run/loop-result-panel.tsx": {
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
"tailwindcss/enforce-consistent-class-order": {
|
||||||
"count": 4
|
"count": 4
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user