node-handle.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import type { MouseEvent } from 'react'
  2. import {
  3. memo,
  4. useCallback,
  5. useEffect,
  6. useState,
  7. } from 'react'
  8. import {
  9. Handle,
  10. Position,
  11. } from 'reactflow'
  12. import { useTranslation } from 'react-i18next'
  13. import { BlockEnum } from '../../../types'
  14. import type { Node } from '../../../types'
  15. import BlockSelector from '../../../block-selector'
  16. import type { ToolDefaultValue } from '../../../block-selector/types'
  17. import {
  18. useAvailableBlocks,
  19. useIsChatMode,
  20. useNodesInteractions,
  21. useNodesReadOnly,
  22. useWorkflow,
  23. } from '../../../hooks'
  24. import {
  25. useStore,
  26. } from '../../../store'
  27. import Tooltip from '@/app/components/base/tooltip'
  28. type NodeHandleProps = {
  29. handleId: string
  30. handleClassName?: string
  31. nodeSelectorClassName?: string
  32. } & Pick<Node, 'id' | 'data'>
  33. export const NodeTargetHandle = memo(({
  34. id,
  35. data,
  36. handleId,
  37. handleClassName,
  38. nodeSelectorClassName,
  39. }: NodeHandleProps) => {
  40. const [open, setOpen] = useState(false)
  41. const { handleNodeAdd } = useNodesInteractions()
  42. const { getNodesReadOnly } = useNodesReadOnly()
  43. const connected = data._connectedTargetHandleIds?.includes(handleId)
  44. const { availablePrevBlocks } = useAvailableBlocks(data.type, data.isInIteration)
  45. const isConnectable = !!availablePrevBlocks.length
  46. const handleOpenChange = useCallback((v: boolean) => {
  47. setOpen(v)
  48. }, [])
  49. const handleHandleClick = useCallback((e: MouseEvent) => {
  50. e.stopPropagation()
  51. if (!connected)
  52. setOpen(v => !v)
  53. }, [connected])
  54. const handleSelect = useCallback((type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => {
  55. handleNodeAdd(
  56. {
  57. nodeType: type,
  58. toolDefaultValue,
  59. },
  60. {
  61. nextNodeId: id,
  62. nextNodeTargetHandle: handleId,
  63. },
  64. )
  65. }, [handleNodeAdd, id, handleId])
  66. return (
  67. <>
  68. <Handle
  69. id={handleId}
  70. type='target'
  71. position={Position.Left}
  72. className={`
  73. !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]
  74. after:absolute after:w-0.5 after:h-2 after:left-1.5 after:top-1 after:bg-primary-500
  75. hover:scale-125 transition-all
  76. ${!connected && 'after:opacity-0'}
  77. ${data.type === BlockEnum.Start && 'opacity-0'}
  78. ${handleClassName}
  79. `}
  80. isConnectable={isConnectable}
  81. onClick={handleHandleClick}
  82. >
  83. {
  84. !connected && isConnectable && !getNodesReadOnly() && (
  85. <BlockSelector
  86. open={open}
  87. onOpenChange={handleOpenChange}
  88. onSelect={handleSelect}
  89. asChild
  90. placement='left'
  91. triggerClassName={open => `
  92. hidden absolute left-0 top-0 pointer-events-none
  93. ${nodeSelectorClassName}
  94. group-hover:!flex
  95. ${data.selected && '!flex'}
  96. ${open && '!flex'}
  97. `}
  98. availableBlocksTypes={availablePrevBlocks}
  99. />
  100. )
  101. }
  102. </Handle>
  103. </>
  104. )
  105. })
  106. NodeTargetHandle.displayName = 'NodeTargetHandle'
  107. export const NodeSourceHandle = memo(({
  108. id,
  109. data,
  110. handleId,
  111. handleClassName,
  112. nodeSelectorClassName,
  113. }: NodeHandleProps) => {
  114. const { t } = useTranslation()
  115. const notInitialWorkflow = useStore(s => s.notInitialWorkflow)
  116. const [open, setOpen] = useState(false)
  117. const { handleNodeAdd } = useNodesInteractions()
  118. const { getNodesReadOnly } = useNodesReadOnly()
  119. const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration)
  120. const isConnectable = !!availableNextBlocks.length
  121. const isChatMode = useIsChatMode()
  122. const { checkParallelLimit } = useWorkflow()
  123. const connected = data._connectedSourceHandleIds?.includes(handleId)
  124. const handleOpenChange = useCallback((v: boolean) => {
  125. setOpen(v)
  126. }, [])
  127. const handleHandleClick = useCallback((e: MouseEvent) => {
  128. e.stopPropagation()
  129. if (checkParallelLimit(id, handleId))
  130. setOpen(v => !v)
  131. }, [checkParallelLimit, id, handleId])
  132. const handleSelect = useCallback((type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => {
  133. handleNodeAdd(
  134. {
  135. nodeType: type,
  136. toolDefaultValue,
  137. },
  138. {
  139. prevNodeId: id,
  140. prevNodeSourceHandle: handleId,
  141. },
  142. )
  143. }, [handleNodeAdd, id, handleId])
  144. useEffect(() => {
  145. if (notInitialWorkflow && data.type === BlockEnum.Start && !isChatMode)
  146. setOpen(true)
  147. }, [notInitialWorkflow, data.type, isChatMode])
  148. return (
  149. <Tooltip
  150. popupContent={(
  151. <div className='system-xs-regular text-text-tertiary'>
  152. <div>
  153. <span className='system-xs-medium text-text-secondary'>{t('workflow.common.parallelTip.click.title')}</span>
  154. {t('workflow.common.parallelTip.click.desc')}
  155. </div>
  156. <div>
  157. <span className='system-xs-medium text-text-secondary'>{t('workflow.common.parallelTip.drag.title')}</span>
  158. {t('workflow.common.parallelTip.drag.desc')}
  159. </div>
  160. </div>
  161. )}
  162. >
  163. <Handle
  164. id={handleId}
  165. type='source'
  166. position={Position.Right}
  167. className={`
  168. !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]
  169. after:absolute after:w-0.5 after:h-2 after:right-1.5 after:top-1 after:bg-primary-500
  170. hover:scale-125 transition-all
  171. ${!connected && 'after:opacity-0'}
  172. ${handleClassName}
  173. `}
  174. isConnectable={isConnectable}
  175. onClick={handleHandleClick}
  176. >
  177. {
  178. isConnectable && !getNodesReadOnly() && (
  179. <BlockSelector
  180. open={open}
  181. onOpenChange={handleOpenChange}
  182. onSelect={handleSelect}
  183. asChild
  184. triggerClassName={open => `
  185. hidden absolute top-0 left-0 pointer-events-none
  186. ${nodeSelectorClassName}
  187. group-hover:!flex
  188. ${data.selected && '!flex'}
  189. ${open && '!flex'}
  190. `}
  191. availableBlocksTypes={availableNextBlocks}
  192. />
  193. )
  194. }
  195. </Handle>
  196. </Tooltip>
  197. )
  198. })
  199. NodeSourceHandle.displayName = 'NodeSourceHandle'