From a430284aa21e3ae1f0d5654e55b2ad2852519cc2 Mon Sep 17 00:00:00 2001 From: wwf <yearningwang@iqtogether.com> Date: 星期三, 04 六月 2025 15:17:49 +0800 Subject: [PATCH] 初始化 --- app/components/workflow/run/tracing-panel.tsx | 247 ++++++++++++++++++++++++++++++++----------------- 1 files changed, 161 insertions(+), 86 deletions(-) diff --git a/app/components/workflow/run/tracing-panel.tsx b/app/components/workflow/run/tracing-panel.tsx index a6e9bf9..ad78971 100644 --- a/app/components/workflow/run/tracing-panel.tsx +++ b/app/components/workflow/run/tracing-panel.tsx @@ -12,27 +12,162 @@ RiMenu4Line, } from '@remixicon/react' import { useTranslation } from 'react-i18next' -import { useLogs } from './hooks' import NodePanel from './node' -import SpecialResultPanel from './special-result-panel' -import type { NodeTracing } from '@/types/workflow' -import formatNodeList from '@/app/components/workflow/run/utils/format-log' +import { + BlockEnum, +} from '@/app/components/workflow/types' +import type { IterationDurationMap, NodeTracing } from '@/types/workflow' type TracingPanelProps = { list: NodeTracing[] + onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void + onShowRetryDetail?: (detail: NodeTracing[]) => void className?: string hideNodeInfo?: boolean hideNodeProcessDetail?: boolean } +type TracingNodeProps = { + id: string + uniqueId: string + isParallel: boolean + data: NodeTracing | null + children: TracingNodeProps[] + parallelTitle?: string + branchTitle?: string + hideNodeInfo?: boolean + hideNodeProcessDetail?: boolean +} + +function buildLogTree(nodes: NodeTracing[], t: (key: string) => string): TracingNodeProps[] { + const rootNodes: TracingNodeProps[] = [] + const parallelStacks: { [key: string]: TracingNodeProps } = {} + const levelCounts: { [key: string]: number } = {} + const parallelChildCounts: { [key: string]: Set<string> } = {} + let uniqueIdCounter = 0 + const getUniqueId = () => { + uniqueIdCounter++ + return `unique-${uniqueIdCounter}` + } + + const 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 `${t('workflow.common.parallel')}-${levelNumber}${letter}` + } + + const 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 `${t('workflow.common.branch')}-${levelNumber}${letter}-${branchLetter}` + } + + // Count parallel children (for figuring out if we need to use letters) + for (const node of nodes) { + const parent_parallel_id = node.parent_parallel_id ?? node.execution_metadata?.parent_parallel_id ?? null + const parallel_id = node.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.parallel_id ?? node.execution_metadata?.parallel_id ?? null + const parent_parallel_id = node.parent_parallel_id ?? node.execution_metadata?.parent_parallel_id ?? null + const parallel_start_node_id = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null + const parent_parallel_start_node_id = node.parent_parallel_start_node_id ?? node.execution_metadata?.parent_parallel_start_node_id ?? null + + if (!parallel_id || node.node_type === BlockEnum.End) { + rootNodes.push({ + id: node.id, + uniqueId: getUniqueId(), + isParallel: false, + data: node, + children: [], + }) + } + else { + if (!parallelStacks[parallel_id]) { + const newParallelGroup: TracingNodeProps = { + id: parallel_id, + uniqueId: getUniqueId(), + isParallel: true, + data: null, + children: [], + parallelTitle: '', + } + parallelStacks[parallel_id] = newParallelGroup + + if (parent_parallel_id && parallelStacks[parent_parallel_id]) { + const sameBranchIndex = parallelStacks[parent_parallel_id].children.findLastIndex(c => + c.data?.execution_metadata?.parallel_start_node_id === parent_parallel_start_node_id || c.data?.parallel_start_node_id === parent_parallel_start_node_id, + ) + parallelStacks[parent_parallel_id].children.splice(sameBranchIndex + 1, 0, 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) : '' + if (branchTitle) { + parallelStacks[parallel_id].children.push({ + id: node.id, + uniqueId: getUniqueId(), + isParallel: false, + data: node, + children: [], + branchTitle, + }) + } + else { + let sameBranchIndex = parallelStacks[parallel_id].children.findLastIndex(c => + c.data?.execution_metadata?.parallel_start_node_id === parallel_start_node_id || c.data?.parallel_start_node_id === parallel_start_node_id, + ) + if (parallelStacks[parallel_id].children[sameBranchIndex + 1]?.isParallel) + sameBranchIndex++ + + parallelStacks[parallel_id].children.splice(sameBranchIndex + 1, 0, { + id: node.id, + uniqueId: getUniqueId(), + isParallel: false, + data: node, + children: [], + branchTitle, + }) + } + } + } + + return rootNodes +} + const TracingPanel: FC<TracingPanelProps> = ({ list, + onShowIterationDetail, + onShowRetryDetail, className, hideNodeInfo = false, hideNodeProcessDetail = false, }) => { const { t } = useTranslation() - const treeNodes = formatNodeList(list, t) + const treeNodes = buildLogTree(list, t) const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(new Set()) const [hoveredParallel, setHoveredParallel] = useState<string | null>(null) @@ -68,47 +203,19 @@ } }, []) - const { - showSpecialResultPanel, - - showRetryDetail, - setShowRetryDetailFalse, - retryResultList, - handleShowRetryResultList, - - showIteratingDetail, - setShowIteratingDetailFalse, - iterationResultList, - iterationResultDurationMap, - handleShowIterationResultList, - - showLoopingDetail, - setShowLoopingDetailFalse, - loopResultList, - loopResultDurationMap, - loopResultVariableMap, - handleShowLoopResultList, - - agentOrToolLogItemStack, - agentOrToolLogListMap, - handleShowAgentOrToolLog, - } = useLogs() - - const renderNode = (node: NodeTracing) => { - const isParallelFirstNode = !!node.parallelDetail?.isParallelStartNode - if (isParallelFirstNode) { - const parallelDetail = node.parallelDetail! + const renderNode = (node: TracingNodeProps) => { + if (node.isParallel) { const isCollapsed = collapsedNodes.has(node.id) const isHovered = hoveredParallel === node.id return ( <div - key={node.id} - className="relative mb-2 ml-4" + key={node.uniqueId} + className="ml-4 mb-2 relative" data-parallel-id={node.id} onMouseEnter={() => handleParallelMouseEnter(node.id)} onMouseLeave={handleParallelMouseLeave} > - <div className="mb-1 flex items-center"> + <div className="flex items-center mb-1"> <button onClick={() => toggleCollapse(node.id)} className={cn( @@ -116,22 +223,22 @@ isHovered ? 'rounded border-components-button-primary-border bg-components-button-primary-bg text-text-primary-on-surface' : 'text-text-secondary hover:text-text-primary', )} > - {isHovered ? <RiArrowDownSLine className="h-3 w-3" /> : <RiMenu4Line className="h-3 w-3 text-text-tertiary" />} + {isHovered ? <RiArrowDownSLine className="w-3 h-3" /> : <RiMenu4Line className="w-3 h-3 text-text-tertiary" />} </button> - <div className="system-xs-semibold-uppercase flex items-center text-text-secondary"> - <span>{parallelDetail.parallelTitle}</span> + <div className="system-xs-semibold-uppercase text-text-secondary flex items-center"> + <span>{node.parallelTitle}</span> </div> <div - className="mx-2 h-px grow bg-divider-subtle" + className="mx-2 flex-grow h-px bg-divider-subtle" style={{ background: 'linear-gradient(to right, rgba(16, 24, 40, 0.08), rgba(255, 255, 255, 0)' }} ></div> </div> - <div className={`relative pl-2 ${isCollapsed ? 'hidden' : ''}`}> + <div className={`pl-2 relative ${isCollapsed ? 'hidden' : ''}`}> <div className={cn( - 'absolute bottom-0 left-[5px] top-0 w-[2px]', + 'absolute top-0 bottom-0 left-[5px] w-[2px]', isHovered ? 'bg-text-accent-secondary' : 'bg-divider-subtle', )}></div> - {parallelDetail.children!.map(renderNode)} + {node.children.map(renderNode)} </div> </div> ) @@ -139,17 +246,16 @@ else { const isHovered = hoveredParallel === node.id return ( - <div key={node.id}> - <div className={cn('system-2xs-medium-uppercase -mb-1.5 pl-4', isHovered ? 'text-text-tertiary' : 'text-text-quaternary')}> - {node?.parallelDetail?.branchTitle} + <div key={node.uniqueId}> + <div className={cn('pl-4 -mb-1.5 system-2xs-medium-uppercase', isHovered ? 'text-text-tertiary' : 'text-text-quaternary')}> + {node.branchTitle} </div> <NodePanel - nodeInfo={node!} - allExecutions={list} - onShowIterationDetail={handleShowIterationResultList} - onShowLoopDetail={handleShowLoopResultList} - onShowRetryDetail={handleShowRetryResultList} - onShowAgentOrToolLog={handleShowAgentOrToolLog} + nodeInfo={node.data!} + onShowIterationDetail={onShowIterationDetail} + onShowRetryDetail={onShowRetryDetail} + justShowIterationNavArrow={true} + justShowRetryNavArrow={true} hideInfo={hideNodeInfo} hideProcessDetail={hideNodeProcessDetail} /> @@ -158,39 +264,8 @@ } } - if (showSpecialResultPanel) { - return ( - <SpecialResultPanel - showRetryDetail={showRetryDetail} - setShowRetryDetailFalse={setShowRetryDetailFalse} - retryResultList={retryResultList} - - showIteratingDetail={showIteratingDetail} - setShowIteratingDetailFalse={setShowIteratingDetailFalse} - iterationResultList={iterationResultList} - iterationResultDurationMap={iterationResultDurationMap} - - showLoopingDetail={showLoopingDetail} - setShowLoopingDetailFalse={setShowLoopingDetailFalse} - loopResultList={loopResultList} - loopResultDurationMap={loopResultDurationMap} - loopResultVariableMap={loopResultVariableMap} - - agentOrToolLogItemStack={agentOrToolLogItemStack} - agentOrToolLogListMap={agentOrToolLogListMap} - handleShowAgentOrToolLog={handleShowAgentOrToolLog} - /> - ) - } - return ( - <div - className={cn('py-2', className)} - onClick={(e) => { - e.stopPropagation() - e.nativeEvent.stopImmediatePropagation() - }} - > + <div className={cn(className || 'bg-components-panel-bg', 'py-2')}> {treeNodes.map(renderNode)} </div> ) -- Gitblit v1.8.0