From 3e257ae907fe90f3bcadf33e3839b25191094220 Mon Sep 17 00:00:00 2001 From: Yi Date: Thu, 29 Aug 2024 16:38:51 +0800 Subject: [PATCH] update the workflow parallel log --- web/app/components/workflow/run/node.tsx | 20 +- .../components/workflow/run/tracing-panel.tsx | 203 +++++++++++++++++- web/types/workflow.ts | 4 + 3 files changed, 208 insertions(+), 19 deletions(-) diff --git a/web/app/components/workflow/run/node.tsx b/web/app/components/workflow/run/node.tsx index 66f996f13b..f55b6c1c21 100644 --- a/web/app/components/workflow/run/node.tsx +++ b/web/app/components/workflow/run/node.tsx @@ -4,7 +4,7 @@ import type { FC } from 'react' import { useCallback, useEffect, useState } from 'react' import { RiArrowRightSLine, - RiCheckboxCircleLine, + RiCheckboxCircleFill, RiErrorWarningLine, RiLoader2Line, } from '@remixicon/react' @@ -72,20 +72,20 @@ const NodePanel: FC = ({ onShowIterationDetail?.(nodeInfo.details || []) } return ( -
-
+
+
setCollapseState(!collapseState)} > {!hideProcessDetail && ( @@ -93,14 +93,14 @@ const NodePanel: FC = ({
{nodeInfo.title}
{nodeInfo.status !== 'running' && !hideInfo && (
{`${getTime(nodeInfo.elapsed_time || 0)} ยท ${getTokenCount(nodeInfo.execution_metadata?.total_tokens || 0)} tokens`}
)} {nodeInfo.status === 'succeeded' && ( - + )} {nodeInfo.status === 'failed' && ( @@ -109,7 +109,7 @@ const NodePanel: FC = ({ )} {nodeInfo.status === 'running' && ( -
+
Running
diff --git a/web/app/components/workflow/run/tracing-panel.tsx b/web/app/components/workflow/run/tracing-panel.tsx index c80bcb41db..1689dd49f9 100644 --- a/web/app/components/workflow/run/tracing-panel.tsx +++ b/web/app/components/workflow/run/tracing-panel.tsx @@ -1,5 +1,16 @@ 'use client' import type { FC } from 'react' +import +React, +{ + useCallback, + useState, +} from 'react' +import cn from 'classnames' +import { + RiArrowDownSLine, + RiMenu4Line, +} from '@remixicon/react' import NodePanel from './node' import type { NodeTracing } from '@/types/workflow' @@ -8,17 +19,191 @@ type TracingPanelProps = { onShowIterationDetail: (detail: NodeTracing[][]) => void } +type TracingNodeProps = { + id: string + isParallel: boolean + data: NodeTracing | null + children: TracingNodeProps[] + parallelTitle?: string + branchTitle?: string +} + +function buildLogTree(nodes: NodeTracing[]): TracingNodeProps[] { + const rootNodes: TracingNodeProps[] = [] + const parallelStacks: { [key: string]: TracingNodeProps } = {} + const levelCounts: { [key: string]: number } = {} + const parallelChildCounts: { [key: string]: Set } = {} + + function getParallelTitle(parentId: string | null): string { + const levelKey = parentId || 'root' + if (!levelCounts[levelKey]) + levelCounts[levelKey] = 0 + + levelCounts[levelKey]++ + + const parentTitle = parentId ? parallelStacks[parentId].parallelTitle : '' + const levelNumber = parentTitle ? parseInt(parentTitle.split('-')[1]) + 1 : 1 + const letter = parallelChildCounts[levelKey]?.size > 1 ? String.fromCharCode(64 + levelCounts[levelKey]) : '' + return `PARALLEL-${levelNumber}${letter}` + } + + function getBranchTitle(parentId: string | null, branchNum: number): string { + const levelKey = parentId || 'root' + const parentTitle = parentId ? parallelStacks[parentId].parallelTitle : '' + const levelNumber = parentTitle ? parseInt(parentTitle.split('-')[1]) + 1 : 1 + const letter = parallelChildCounts[levelKey]?.size > 1 ? String.fromCharCode(64 + levelCounts[levelKey]) : '' + const branchLetter = String.fromCharCode(64 + branchNum) + return `BRANCH-${levelNumber}${letter}-${branchLetter}` + } + + // Count parallel children (for figuring out if we need to use letters) + for (const node of nodes) { + console.log(node) + const parent_parallel_id = node.execution_metadata?.parent_parallel_id ?? null + const parallel_id = node.execution_metadata?.parallel_id ?? null + + if (parallel_id) { + const parentKey = parent_parallel_id || 'root' + if (!parallelChildCounts[parentKey]) + parallelChildCounts[parentKey] = new Set() + + parallelChildCounts[parentKey].add(parallel_id) + } + } + + for (const node of nodes) { + const parallel_id = node.execution_metadata?.parallel_id ?? null + const parent_parallel_id = node.execution_metadata?.parent_parallel_id ?? null + const parallel_start_node_id = node.execution_metadata?.parallel_start_node_id ?? null + + if (!parallel_id) { + rootNodes.push({ + id: node.id, + isParallel: false, + data: node, + children: [], + }) + } + else { + if (!parallelStacks[parallel_id]) { + const newParallelGroup: TracingNodeProps = { + id: parallel_id, + isParallel: true, + data: null, + children: [], + parallelTitle: '', + } + parallelStacks[parallel_id] = newParallelGroup + + if (parent_parallel_id && parallelStacks[parent_parallel_id]) { + parallelStacks[parent_parallel_id].children.push(newParallelGroup) + newParallelGroup.parallelTitle = getParallelTitle(parent_parallel_id) + } + else { + newParallelGroup.parallelTitle = getParallelTitle(parent_parallel_id) + rootNodes.push(newParallelGroup) + } + } + const branchTitle = parallel_start_node_id === node.node_id ? getBranchTitle(parent_parallel_id, parallelStacks[parallel_id].children.length + 1) : '' + parallelStacks[parallel_id].children.push({ + id: node.id, + isParallel: false, + data: node, + children: [], + branchTitle, + }) + } + } + + return rootNodes +} + const TracingPanel: FC = ({ list, onShowIterationDetail }) => { - return ( -
- {list.map(node => ( - >(new Set()) + const [hoveredParallel, setHoveredParallel] = useState(null) + + const toggleCollapse = (id: string) => { + setCollapsedNodes((prev) => { + const newSet = new Set(prev) + if (newSet.has(id)) + newSet.delete(id) + else + newSet.add(id) + + return newSet + }) + } + + const handleParallelMouseEnter = useCallback((id: string) => { + setHoveredParallel(id) + }, []) + + const handleParallelMouseLeave = useCallback((e: React.MouseEvent) => { + const relatedTarget = e.relatedTarget as HTMLElement + const closestParallel = relatedTarget?.closest('[data-parallel-id]') + if (closestParallel) + setHoveredParallel(closestParallel.getAttribute('data-parallel-id')) + else + setHoveredParallel(null) + }, []) + + const renderNode = (node: TracingNodeProps) => { + if (node.isParallel) { + const isCollapsed = collapsedNodes.has(node.id) + const isHovered = hoveredParallel === node.id + return ( +
- ))} + className="ml-4 mb-2 relative" + data-parallel-id={node.id} + onMouseEnter={() => handleParallelMouseEnter(node.id)} + onMouseLeave={handleParallelMouseLeave} + > +
+ +
+ {node.parallelTitle} +
+
+
+
+
+ {node.children.map(renderNode)} +
+
+ ) + } + else { + return ( +
+
+ {node.branchTitle} +
+ +
+ ) + } + } + + return ( +
+ {treeNodes.map(renderNode)}
) } diff --git a/web/types/workflow.ts b/web/types/workflow.ts index 73fa14e094..ed3c77a0ec 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -26,6 +26,10 @@ export type NodeTracing = { currency: string iteration_id?: string iteration_index?: number + parallel_id?: string + parallel_start_node_id?: string + parent_parallel_id?: string + parent_parallel_start_node_id?: string } metadata: { iterator_length: number