您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

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