tracing-panel.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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. showLoopingDetail,
  74. setShowLoopingDetailFalse,
  75. loopResultList,
  76. loopResultDurationMap,
  77. handleShowLoopResultList,
  78. agentOrToolLogItemStack,
  79. agentOrToolLogListMap,
  80. handleShowAgentOrToolLog,
  81. } = useLogs()
  82. const renderNode = (node: NodeTracing) => {
  83. const isParallelFirstNode = !!node.parallelDetail?.isParallelStartNode
  84. if (isParallelFirstNode) {
  85. const parallelDetail = node.parallelDetail!
  86. const isCollapsed = collapsedNodes.has(node.id)
  87. const isHovered = hoveredParallel === node.id
  88. return (
  89. <div
  90. key={node.id}
  91. className="ml-4 mb-2 relative"
  92. data-parallel-id={node.id}
  93. onMouseEnter={() => handleParallelMouseEnter(node.id)}
  94. onMouseLeave={handleParallelMouseLeave}
  95. >
  96. <div className="flex items-center mb-1">
  97. <button
  98. onClick={() => toggleCollapse(node.id)}
  99. className={cn(
  100. 'mr-2 transition-colors',
  101. isHovered ? 'rounded border-components-button-primary-border bg-components-button-primary-bg text-text-primary-on-surface' : 'text-text-secondary hover:text-text-primary',
  102. )}
  103. >
  104. {isHovered ? <RiArrowDownSLine className="w-3 h-3" /> : <RiMenu4Line className="w-3 h-3 text-text-tertiary" />}
  105. </button>
  106. <div className="system-xs-semibold-uppercase text-text-secondary flex items-center">
  107. <span>{parallelDetail.parallelTitle}</span>
  108. </div>
  109. <div
  110. className="mx-2 grow h-px bg-divider-subtle"
  111. style={{ background: 'linear-gradient(to right, rgba(16, 24, 40, 0.08), rgba(255, 255, 255, 0)' }}
  112. ></div>
  113. </div>
  114. <div className={`pl-2 relative ${isCollapsed ? 'hidden' : ''}`}>
  115. <div className={cn(
  116. 'absolute top-0 bottom-0 left-[5px] w-[2px]',
  117. isHovered ? 'bg-text-accent-secondary' : 'bg-divider-subtle',
  118. )}></div>
  119. {parallelDetail.children!.map(renderNode)}
  120. </div>
  121. </div>
  122. )
  123. }
  124. else {
  125. const isHovered = hoveredParallel === node.id
  126. return (
  127. <div key={node.id}>
  128. <div className={cn('pl-4 -mb-1.5 system-2xs-medium-uppercase', isHovered ? 'text-text-tertiary' : 'text-text-quaternary')}>
  129. {node?.parallelDetail?.branchTitle}
  130. </div>
  131. <NodePanel
  132. nodeInfo={node!}
  133. onShowIterationDetail={handleShowIterationResultList}
  134. onShowLoopDetail={handleShowLoopResultList}
  135. onShowRetryDetail={handleShowRetryResultList}
  136. onShowAgentOrToolLog={handleShowAgentOrToolLog}
  137. hideInfo={hideNodeInfo}
  138. hideProcessDetail={hideNodeProcessDetail}
  139. />
  140. </div>
  141. )
  142. }
  143. }
  144. if (showSpecialResultPanel) {
  145. return (
  146. <SpecialResultPanel
  147. showRetryDetail={showRetryDetail}
  148. setShowRetryDetailFalse={setShowRetryDetailFalse}
  149. retryResultList={retryResultList}
  150. showIteratingDetail={showIteratingDetail}
  151. setShowIteratingDetailFalse={setShowIteratingDetailFalse}
  152. iterationResultList={iterationResultList}
  153. iterationResultDurationMap={iterationResultDurationMap}
  154. showLoopingDetail={showLoopingDetail}
  155. setShowLoopingDetailFalse={setShowLoopingDetailFalse}
  156. loopResultList={loopResultList}
  157. loopResultDurationMap={loopResultDurationMap}
  158. agentOrToolLogItemStack={agentOrToolLogItemStack}
  159. agentOrToolLogListMap={agentOrToolLogListMap}
  160. handleShowAgentOrToolLog={handleShowAgentOrToolLog}
  161. />
  162. )
  163. }
  164. return (
  165. <div
  166. className={cn('py-2', className)}
  167. onClick={(e) => {
  168. e.stopPropagation()
  169. e.nativeEvent.stopImmediatePropagation()
  170. }}
  171. >
  172. {treeNodes.map(renderNode)}
  173. </div>
  174. )
  175. }
  176. export default TracingPanel