iteration-result-panel.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback, useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import {
  6. RiArrowRightSLine,
  7. RiCloseLine,
  8. RiErrorWarningLine,
  9. RiLoader2Line,
  10. } from '@remixicon/react'
  11. import { ArrowNarrowLeft } from '../../base/icons/src/vender/line/arrows'
  12. import { NodeRunningStatus } from '../types'
  13. import TracingPanel from './tracing-panel'
  14. import { Iteration } from '@/app/components/base/icons/src/vender/workflow'
  15. import cn from '@/utils/classnames'
  16. import type { IterationDurationMap, NodeTracing } from '@/types/workflow'
  17. const i18nPrefix = 'workflow.singleRun'
  18. type Props = {
  19. list: NodeTracing[][]
  20. onHide: () => void
  21. onBack: () => void
  22. noWrap?: boolean
  23. iterDurationMap?: IterationDurationMap
  24. }
  25. const IterationResultPanel: FC<Props> = ({
  26. list,
  27. onHide,
  28. onBack,
  29. noWrap,
  30. iterDurationMap,
  31. }) => {
  32. const { t } = useTranslation()
  33. const [expandedIterations, setExpandedIterations] = useState<Record<number, boolean>>({})
  34. const toggleIteration = useCallback((index: number) => {
  35. setExpandedIterations(prev => ({
  36. ...prev,
  37. [index]: !prev[index],
  38. }))
  39. }, [])
  40. const countIterDuration = (iteration: NodeTracing[], iterDurationMap: IterationDurationMap): string => {
  41. const IterRunIndex = iteration[0].execution_metadata.iteration_index as number
  42. const iterRunId = iteration[0].execution_metadata.parallel_mode_run_id
  43. const iterItem = iterDurationMap[iterRunId || IterRunIndex]
  44. const duration = iterItem
  45. return `${(duration && duration > 0.01) ? duration.toFixed(2) : 0.01}s`
  46. }
  47. const iterationStatusShow = (index: number, iteration: NodeTracing[], iterDurationMap?: IterationDurationMap) => {
  48. const hasFailed = iteration.some(item => item.status === NodeRunningStatus.Failed)
  49. const isRunning = iteration.some(item => item.status === NodeRunningStatus.Running)
  50. const hasDurationMap = iterDurationMap && Object.keys(iterDurationMap).length !== 0
  51. if (hasFailed)
  52. return <RiErrorWarningLine className='w-4 h-4 text-text-destructive' />
  53. if (isRunning)
  54. return <RiLoader2Line className='w-3.5 h-3.5 text-primary-600 animate-spin' />
  55. return (
  56. <>
  57. {hasDurationMap && (
  58. <div className='system-xs-regular text-text-tertiary'>
  59. {countIterDuration(iteration, iterDurationMap)}
  60. </div>
  61. )}
  62. <RiArrowRightSLine
  63. className={cn(
  64. 'w-4 h-4 text-text-tertiary transition-transform duration-200 flex-shrink-0',
  65. expandedIterations[index] && 'transform rotate-90',
  66. )}
  67. />
  68. </>
  69. )
  70. }
  71. const main = (
  72. <>
  73. <div className={cn(!noWrap && 'shrink-0 ', 'px-4 pt-3')}>
  74. <div className='shrink-0 flex justify-between items-center h-8'>
  75. <div className='system-xl-semibold text-text-primary truncate'>
  76. {t(`${i18nPrefix}.testRunIteration`)}
  77. </div>
  78. <div className='ml-2 shrink-0 p-1 cursor-pointer' onClick={onHide}>
  79. <RiCloseLine className='w-4 h-4 text-text-tertiary' />
  80. </div>
  81. </div>
  82. <div className='flex items-center py-2 space-x-1 text-text-accent-secondary cursor-pointer' onClick={onBack}>
  83. <ArrowNarrowLeft className='w-4 h-4' />
  84. <div className='system-sm-medium'>{t(`${i18nPrefix}.back`)}</div>
  85. </div>
  86. </div>
  87. {/* List */}
  88. <div className={cn(!noWrap ? 'flex-grow overflow-auto' : 'max-h-full', 'p-2 bg-components-panel-bg')}>
  89. {list.map((iteration, index) => (
  90. <div key={index} className={cn('mb-1 overflow-hidden rounded-xl bg-background-section-burn border-none')}>
  91. <div
  92. className={cn(
  93. 'flex items-center justify-between w-full px-3 cursor-pointer',
  94. expandedIterations[index] ? 'pt-3 pb-2' : 'py-3',
  95. 'rounded-xl text-left',
  96. )}
  97. onClick={() => toggleIteration(index)}
  98. >
  99. <div className={cn('flex items-center gap-2 flex-grow')}>
  100. <div className='flex items-center justify-center w-4 h-4 rounded-[5px] border-divider-subtle bg-util-colors-cyan-cyan-500 flex-shrink-0'>
  101. <Iteration className='w-3 h-3 text-text-primary-on-surface' />
  102. </div>
  103. <span className='system-sm-semibold-uppercase text-text-primary flex-grow'>
  104. {t(`${i18nPrefix}.iteration`)} {index + 1}
  105. </span>
  106. {iterationStatusShow(index, iteration, iterDurationMap)}
  107. </div>
  108. </div>
  109. {expandedIterations[index] && <div
  110. className="flex-grow h-px bg-divider-subtle"
  111. ></div>}
  112. <div className={cn(
  113. 'overflow-hidden transition-all duration-200',
  114. expandedIterations[index] ? 'max-h-[1000px] opacity-100' : 'max-h-0 opacity-0',
  115. )}>
  116. <TracingPanel
  117. list={iteration}
  118. className='bg-background-section-burn'
  119. />
  120. </div>
  121. </div>
  122. ))}
  123. </div>
  124. </>
  125. )
  126. const handleNotBubble = useCallback((e: React.MouseEvent) => {
  127. // if not do this, it will trigger the message log modal disappear(useClickAway)
  128. e.stopPropagation()
  129. e.nativeEvent.stopImmediatePropagation()
  130. }, [])
  131. if (noWrap)
  132. return main
  133. return (
  134. <div
  135. className='absolute inset-0 z-10 rounded-2xl pt-10'
  136. style={{
  137. backgroundColor: 'rgba(16, 24, 40, 0.20)',
  138. }}
  139. onClick={handleNotBubble}
  140. >
  141. <div className='h-full rounded-2xl bg-components-panel-bg flex flex-col'>
  142. {main}
  143. </div>
  144. </div >
  145. )
  146. }
  147. export default React.memo(IterationResultPanel)