123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- 'use client'
- import type { FC } from 'react'
- import
- React,
- {
- useCallback,
- useState,
- } from 'react'
- import cn from 'classnames'
- import {
- RiArrowDownSLine,
- 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'
- type TracingPanelProps = {
- list: NodeTracing[]
- className?: string
- hideNodeInfo?: boolean
- hideNodeProcessDetail?: boolean
- }
- const TracingPanel: FC<TracingPanelProps> = ({
- list,
- className,
- hideNodeInfo = false,
- hideNodeProcessDetail = false,
- }) => {
- const { t } = useTranslation()
- const treeNodes = formatNodeList(list, t)
- const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(new Set())
- const [hoveredParallel, setHoveredParallel] = useState<string | null>(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 Element | null
- if (relatedTarget && 'closest' in relatedTarget) {
- const closestParallel = relatedTarget.closest('[data-parallel-id]')
- if (closestParallel)
- setHoveredParallel(closestParallel.getAttribute('data-parallel-id'))
- else
- setHoveredParallel(null)
- }
- else {
- setHoveredParallel(null)
- }
- }, [])
- const {
- showSpecialResultPanel,
- showRetryDetail,
- setShowRetryDetailFalse,
- retryResultList,
- handleShowRetryResultList,
- showIteratingDetail,
- setShowIteratingDetailFalse,
- iterationResultList,
- iterationResultDurationMap,
- handleShowIterationResultList,
- showLoopingDetail,
- setShowLoopingDetailFalse,
- loopResultList,
- loopResultDurationMap,
- handleShowLoopResultList,
- agentOrToolLogItemStack,
- agentOrToolLogListMap,
- handleShowAgentOrToolLog,
- } = useLogs()
- const renderNode = (node: NodeTracing) => {
- const isParallelFirstNode = !!node.parallelDetail?.isParallelStartNode
- if (isParallelFirstNode) {
- const parallelDetail = node.parallelDetail!
- const isCollapsed = collapsedNodes.has(node.id)
- const isHovered = hoveredParallel === node.id
- return (
- <div
- key={node.id}
- className="ml-4 mb-2 relative"
- data-parallel-id={node.id}
- onMouseEnter={() => handleParallelMouseEnter(node.id)}
- onMouseLeave={handleParallelMouseLeave}
- >
- <div className="flex items-center mb-1">
- <button
- onClick={() => toggleCollapse(node.id)}
- className={cn(
- 'mr-2 transition-colors',
- 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="w-3 h-3" /> : <RiMenu4Line className="w-3 h-3 text-text-tertiary" />}
- </button>
- <div className="system-xs-semibold-uppercase text-text-secondary flex items-center">
- <span>{parallelDetail.parallelTitle}</span>
- </div>
- <div
- className="mx-2 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={`pl-2 relative ${isCollapsed ? 'hidden' : ''}`}>
- <div className={cn(
- 'absolute top-0 bottom-0 left-[5px] w-[2px]',
- isHovered ? 'bg-text-accent-secondary' : 'bg-divider-subtle',
- )}></div>
- {parallelDetail.children!.map(renderNode)}
- </div>
- </div>
- )
- }
- else {
- const isHovered = hoveredParallel === node.id
- return (
- <div key={node.id}>
- <div className={cn('pl-4 -mb-1.5 system-2xs-medium-uppercase', isHovered ? 'text-text-tertiary' : 'text-text-quaternary')}>
- {node?.parallelDetail?.branchTitle}
- </div>
- <NodePanel
- nodeInfo={node!}
- onShowIterationDetail={handleShowIterationResultList}
- onShowLoopDetail={handleShowLoopResultList}
- onShowRetryDetail={handleShowRetryResultList}
- onShowAgentOrToolLog={handleShowAgentOrToolLog}
- hideInfo={hideNodeInfo}
- hideProcessDetail={hideNodeProcessDetail}
- />
- </div>
- )
- }
- }
- 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}
- agentOrToolLogItemStack={agentOrToolLogItemStack}
- agentOrToolLogListMap={agentOrToolLogListMap}
- handleShowAgentOrToolLog={handleShowAgentOrToolLog}
- />
- )
- }
- return (
- <div
- className={cn('py-2', className)}
- onClick={(e) => {
- e.stopPropagation()
- e.nativeEvent.stopImmediatePropagation()
- }}
- >
- {treeNodes.map(renderNode)}
- </div>
- )
- }
- export default TracingPanel
|