You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tracing-panel.tsx 6.0KB

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