node.tsx 6.7 KB

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