node.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import type {
  2. FC,
  3. ReactElement,
  4. } from 'react'
  5. import {
  6. cloneElement,
  7. memo,
  8. useEffect,
  9. useMemo,
  10. useRef,
  11. } from 'react'
  12. import {
  13. RiAlertFill,
  14. RiCheckboxCircleFill,
  15. RiErrorWarningFill,
  16. RiLoader2Line,
  17. } from '@remixicon/react'
  18. import { useTranslation } from 'react-i18next'
  19. import type { NodeProps } from '../../types'
  20. import {
  21. BlockEnum,
  22. NodeRunningStatus,
  23. } from '../../types'
  24. import {
  25. useNodesReadOnly,
  26. useToolIcon,
  27. } from '../../hooks'
  28. import { hasErrorHandleNode } from '../../utils'
  29. import { useNodeIterationInteractions } from '../iteration/use-interactions'
  30. import type { IterationNodeType } from '../iteration/types'
  31. import {
  32. NodeSourceHandle,
  33. NodeTargetHandle,
  34. } from './components/node-handle'
  35. import NodeResizer from './components/node-resizer'
  36. import NodeControl from './components/node-control'
  37. import ErrorHandleOnNode from './components/error-handle/error-handle-on-node'
  38. import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
  39. import cn from '@/utils/classnames'
  40. import BlockIcon from '@/app/components/workflow/block-icon'
  41. import Tooltip from '@/app/components/base/tooltip'
  42. type BaseNodeProps = {
  43. children: ReactElement
  44. } & NodeProps
  45. const BaseNode: FC<BaseNodeProps> = ({
  46. id,
  47. data,
  48. children,
  49. }) => {
  50. const { t } = useTranslation()
  51. const nodeRef = useRef<HTMLDivElement>(null)
  52. const { nodesReadOnly } = useNodesReadOnly()
  53. const { handleNodeIterationChildSizeChange } = useNodeIterationInteractions()
  54. const toolIcon = useToolIcon(data)
  55. useEffect(() => {
  56. if (nodeRef.current && data.selected && data.isInIteration) {
  57. const resizeObserver = new ResizeObserver(() => {
  58. handleNodeIterationChildSizeChange(id)
  59. })
  60. resizeObserver.observe(nodeRef.current)
  61. return () => {
  62. resizeObserver.disconnect()
  63. }
  64. }
  65. }, [data.isInIteration, data.selected, id, handleNodeIterationChildSizeChange])
  66. const showSelectedBorder = data.selected || data._isBundled || data._isEntering
  67. const {
  68. showRunningBorder,
  69. showSuccessBorder,
  70. showFailedBorder,
  71. showExceptionBorder,
  72. } = useMemo(() => {
  73. return {
  74. showRunningBorder: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder,
  75. showSuccessBorder: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder,
  76. showFailedBorder: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder,
  77. showExceptionBorder: data._runningStatus === NodeRunningStatus.Exception && !showSelectedBorder,
  78. }
  79. }, [data._runningStatus, showSelectedBorder])
  80. return (
  81. <div
  82. className={cn(
  83. 'flex border-[2px] rounded-2xl',
  84. showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent',
  85. !showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight',
  86. data._waitingRun && 'opacity-70',
  87. )}
  88. ref={nodeRef}
  89. style={{
  90. width: data.type === BlockEnum.Iteration ? data.width : 'auto',
  91. height: data.type === BlockEnum.Iteration ? data.height : 'auto',
  92. }}
  93. >
  94. <div
  95. className={cn(
  96. 'group relative pb-1 shadow-xs',
  97. 'border border-transparent rounded-[15px]',
  98. data.type !== BlockEnum.Iteration && 'w-[240px] bg-workflow-block-bg',
  99. data.type === BlockEnum.Iteration && 'flex flex-col w-full h-full bg-[#fcfdff]/80',
  100. !data._runningStatus && 'hover:shadow-lg',
  101. showRunningBorder && '!border-state-accent-solid',
  102. showSuccessBorder && '!border-state-success-solid',
  103. showFailedBorder && '!border-state-destructive-solid',
  104. showExceptionBorder && '!border-state-warning-solid',
  105. data._isBundled && '!shadow-lg',
  106. )}
  107. >
  108. {
  109. data._inParallelHovering && (
  110. <div className='absolute left-2 -top-2.5 top system-2xs-medium-uppercase text-text-tertiary z-10'>
  111. {t('workflow.common.parallelRun')}
  112. </div>
  113. )
  114. }
  115. {
  116. data._showAddVariablePopup && (
  117. <AddVariablePopupWithPosition
  118. nodeId={id}
  119. nodeData={data}
  120. />
  121. )
  122. }
  123. {
  124. data.type === BlockEnum.Iteration && (
  125. <NodeResizer
  126. nodeId={id}
  127. nodeData={data}
  128. />
  129. )
  130. }
  131. {
  132. !data._isCandidate && (
  133. <NodeTargetHandle
  134. id={id}
  135. data={data}
  136. handleClassName='!top-4 !-left-[9px] !translate-y-0'
  137. handleId='target'
  138. />
  139. )
  140. }
  141. {
  142. data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._isCandidate && (
  143. <NodeSourceHandle
  144. id={id}
  145. data={data}
  146. handleClassName='!top-4 !-right-[9px] !translate-y-0'
  147. handleId='source'
  148. />
  149. )
  150. }
  151. {
  152. !data._runningStatus && !nodesReadOnly && !data._isCandidate && (
  153. <NodeControl
  154. id={id}
  155. data={data}
  156. />
  157. )
  158. }
  159. <div className={cn(
  160. 'flex items-center px-3 pt-3 pb-2 rounded-t-2xl',
  161. data.type === BlockEnum.Iteration && 'bg-[rgba(250,252,255,0.9)]',
  162. )}>
  163. <BlockIcon
  164. className='shrink-0 mr-2'
  165. type={data.type}
  166. size='md'
  167. toolIcon={toolIcon}
  168. />
  169. <div
  170. title={data.title}
  171. className='grow mr-1 system-sm-semibold-uppercase text-text-primary truncate flex items-center'
  172. >
  173. <div>
  174. {data.title}
  175. </div>
  176. {
  177. data.type === BlockEnum.Iteration && (data as IterationNodeType).is_parallel && (
  178. <Tooltip popupContent={
  179. <div className='w-[180px]'>
  180. <div className='font-extrabold'>
  181. {t('workflow.nodes.iteration.parallelModeEnableTitle')}
  182. </div>
  183. {t('workflow.nodes.iteration.parallelModeEnableDesc')}
  184. </div>}
  185. >
  186. <div className='flex justify-center items-center px-[5px] py-[3px] ml-1 border-[1px] border-text-warning rounded-[5px] text-text-warning system-2xs-medium-uppercase '>
  187. {t('workflow.nodes.iteration.parallelModeUpper')}
  188. </div>
  189. </Tooltip>
  190. )
  191. }
  192. </div>
  193. {
  194. data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && (
  195. <div className='mr-1.5 text-xs font-medium text-text-accent'>
  196. {data._iterationIndex > data._iterationLength ? data._iterationLength : data._iterationIndex}/{data._iterationLength}
  197. </div>
  198. )
  199. }
  200. {
  201. (data._runningStatus === NodeRunningStatus.Running || data._singleRunningStatus === NodeRunningStatus.Running) && (
  202. <RiLoader2Line className='w-3.5 h-3.5 text-text-accent animate-spin' />
  203. )
  204. }
  205. {
  206. data._runningStatus === NodeRunningStatus.Succeeded && (
  207. <RiCheckboxCircleFill className='w-3.5 h-3.5 text-text-success' />
  208. )
  209. }
  210. {
  211. data._runningStatus === NodeRunningStatus.Failed && (
  212. <RiErrorWarningFill className='w-3.5 h-3.5 text-text-destructive' />
  213. )
  214. }
  215. {
  216. data._runningStatus === NodeRunningStatus.Exception && (
  217. <RiAlertFill className='w-3.5 h-3.5 text-text-warning-secondary' />
  218. )
  219. }
  220. </div>
  221. {
  222. data.type !== BlockEnum.Iteration && (
  223. cloneElement(children, { id, data })
  224. )
  225. }
  226. {
  227. data.type === BlockEnum.Iteration && (
  228. <div className='grow pl-1 pr-1 pb-1'>
  229. {cloneElement(children, { id, data })}
  230. </div>
  231. )
  232. }
  233. {
  234. hasErrorHandleNode(data.type) && (
  235. <ErrorHandleOnNode
  236. id={id}
  237. data={data}
  238. />
  239. )
  240. }
  241. {
  242. data.desc && data.type !== BlockEnum.Iteration && (
  243. <div className='px-3 pt-1 pb-2 system-xs-regular text-text-tertiary whitespace-pre-line break-words'>
  244. {data.desc}
  245. </div>
  246. )
  247. }
  248. </div>
  249. </div>
  250. )
  251. }
  252. export default memo(BaseNode)