tracing-panel.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. 'use client'
  2. import type { FC } from 'react'
  3. import
  4. React,
  5. {
  6. useCallback,
  7. useState,
  8. } from 'react'
  9. import cn from 'classnames'
  10. import {
  11. RiArrowDownSLine,
  12. RiMenu4Line,
  13. } from '@remixicon/react'
  14. import { useTranslation } from 'react-i18next'
  15. import { useLogs } from './hooks'
  16. import NodePanel from './node'
  17. import SpecialResultPanel from './special-result-panel'
  18. import type { NodeTracing } from '@/types/workflow'
  19. import formatNodeList from '@/app/components/workflow/run/utils/format-log'
  20. type TracingPanelProps = {
  21. list: NodeTracing[]
  22. className?: string
  23. hideNodeInfo?: boolean
  24. hideNodeProcessDetail?: boolean
  25. }
  26. const TracingPanel: FC<TracingPanelProps> = ({
  27. list,
  28. className,
  29. hideNodeInfo = false,
  30. hideNodeProcessDetail = false,
  31. }) => {
  32. const { t } = useTranslation()
  33. const treeNodes = formatNodeList(list, t)
  34. const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(new Set())
  35. const [hoveredParallel, setHoveredParallel] = useState<string | null>(null)
  36. const toggleCollapse = (id: string) => {
  37. setCollapsedNodes((prev) => {
  38. const newSet = new Set(prev)
  39. if (newSet.has(id))
  40. newSet.delete(id)
  41. else
  42. newSet.add(id)
  43. return newSet
  44. })
  45. }
  46. const handleParallelMouseEnter = useCallback((id: string) => {
  47. setHoveredParallel(id)
  48. }, [])
  49. const handleParallelMouseLeave = useCallback((e: React.MouseEvent) => {
  50. const relatedTarget = e.relatedTarget as Element | null
  51. if (relatedTarget && 'closest' in relatedTarget) {
  52. const closestParallel = relatedTarget.closest('[data-parallel-id]')
  53. if (closestParallel)
  54. setHoveredParallel(closestParallel.getAttribute('data-parallel-id'))
  55. else
  56. setHoveredParallel(null)
  57. }
  58. else {
  59. setHoveredParallel(null)
  60. }
  61. }, [])
  62. const {
  63. showSpecialResultPanel,
  64. showRetryDetail,
  65. setShowRetryDetailFalse,
  66. retryResultList,
  67. handleShowRetryResultList,
  68. showIteratingDetail,
  69. setShowIteratingDetailFalse,
  70. iterationResultList,
  71. iterationResultDurationMap,
  72. handleShowIterationResultList,
  73. agentOrToolLogItemStack,
  74. agentOrToolLogListMap,
  75. handleShowAgentOrToolLog,
  76. } = useLogs()
  77. const renderNode = (node: NodeTracing) => {
  78. const isParallelFirstNode = !!node.parallelDetail?.isParallelStartNode
  79. if (isParallelFirstNode) {
  80. const parallelDetail = node.parallelDetail!
  81. const isCollapsed = collapsedNodes.has(node.id)
  82. const isHovered = hoveredParallel === node.id
  83. return (
  84. <div
  85. key={node.id}
  86. className="ml-4 mb-2 relative"
  87. data-parallel-id={node.id}
  88. onMouseEnter={() => handleParallelMouseEnter(node.id)}
  89. onMouseLeave={handleParallelMouseLeave}
  90. >
  91. <div className="flex items-center mb-1">
  92. <button
  93. onClick={() => toggleCollapse(node.id)}
  94. className={cn(
  95. 'mr-2 transition-colors',
  96. isHovered ? 'rounded border-components-button-primary-border bg-components-button-primary-bg text-text-primary-on-surface' : 'text-text-secondary hover:text-text-primary',
  97. )}
  98. >
  99. {isHovered ? <RiArrowDownSLine className="w-3 h-3" /> : <RiMenu4Line className="w-3 h-3 text-text-tertiary" />}
  100. </button>
  101. <div className="system-xs-semibold-uppercase text-text-secondary flex items-center">
  102. <span>{parallelDetail.parallelTitle}</span>
  103. </div>
  104. <div
  105. className="mx-2 grow h-px bg-divider-subtle"
  106. style={{ background: 'linear-gradient(to right, rgba(16, 24, 40, 0.08), rgba(255, 255, 255, 0)' }}
  107. ></div>
  108. </div>
  109. <div className={`pl-2 relative ${isCollapsed ? 'hidden' : ''}`}>
  110. <div className={cn(
  111. 'absolute top-0 bottom-0 left-[5px] w-[2px]',
  112. isHovered ? 'bg-text-accent-secondary' : 'bg-divider-subtle',
  113. )}></div>
  114. {parallelDetail.children!.map(renderNode)}
  115. </div>
  116. </div>
  117. )
  118. }
  119. else {
  120. const isHovered = hoveredParallel === node.id
  121. return (
  122. <div key={node.id}>
  123. <div className={cn('pl-4 -mb-1.5 system-2xs-medium-uppercase', isHovered ? 'text-text-tertiary' : 'text-text-quaternary')}>
  124. {node?.parallelDetail?.branchTitle}
  125. </div>
  126. <NodePanel
  127. nodeInfo={node!}
  128. onShowIterationDetail={handleShowIterationResultList}
  129. onShowRetryDetail={handleShowRetryResultList}
  130. onShowAgentOrToolLog={handleShowAgentOrToolLog}
  131. hideInfo={hideNodeInfo}
  132. hideProcessDetail={hideNodeProcessDetail}
  133. />
  134. </div>
  135. )
  136. }
  137. }
  138. if (showSpecialResultPanel) {
  139. return (
  140. <SpecialResultPanel
  141. showRetryDetail={showRetryDetail}
  142. setShowRetryDetailFalse={setShowRetryDetailFalse}
  143. retryResultList={retryResultList}
  144. showIteratingDetail={showIteratingDetail}
  145. setShowIteratingDetailFalse={setShowIteratingDetailFalse}
  146. iterationResultList={iterationResultList}
  147. iterationResultDurationMap={iterationResultDurationMap}
  148. agentOrToolLogItemStack={agentOrToolLogItemStack}
  149. agentOrToolLogListMap={agentOrToolLogListMap}
  150. handleShowAgentOrToolLog={handleShowAgentOrToolLog}
  151. />
  152. )
  153. }
  154. return (
  155. <div
  156. className={cn('py-2', className)}
  157. onClick={(e) => {
  158. e.stopPropagation()
  159. e.nativeEvent.stopImmediatePropagation()
  160. }}
  161. >
  162. {treeNodes.map(renderNode)}
  163. </div>
  164. )
  165. }
  166. export default TracingPanel