node.tsx 6.3 KB

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