custom-edge.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import {
  2. memo,
  3. useCallback,
  4. useMemo,
  5. useState,
  6. } from 'react'
  7. import { intersection } from 'lodash-es'
  8. import type { EdgeProps } from 'reactflow'
  9. import {
  10. BaseEdge,
  11. EdgeLabelRenderer,
  12. Position,
  13. getBezierPath,
  14. } from 'reactflow'
  15. import {
  16. useAvailableBlocks,
  17. useNodesInteractions,
  18. } from './hooks'
  19. import BlockSelector from './block-selector'
  20. import type {
  21. Edge,
  22. OnSelectBlock,
  23. } from './types'
  24. import { NodeRunningStatus } from './types'
  25. import { getEdgeColor } from './utils'
  26. import { ITERATION_CHILDREN_Z_INDEX } from './constants'
  27. import CustomEdgeLinearGradientRender from './custom-edge-linear-gradient-render'
  28. import cn from '@/utils/classnames'
  29. import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
  30. const CustomEdge = ({
  31. id,
  32. data,
  33. source,
  34. sourceHandleId,
  35. target,
  36. targetHandleId,
  37. sourceX,
  38. sourceY,
  39. targetX,
  40. targetY,
  41. selected,
  42. }: EdgeProps) => {
  43. const [
  44. edgePath,
  45. labelX,
  46. labelY,
  47. ] = getBezierPath({
  48. sourceX: sourceX - 8,
  49. sourceY,
  50. sourcePosition: Position.Right,
  51. targetX: targetX + 8,
  52. targetY,
  53. targetPosition: Position.Left,
  54. curvature: 0.16,
  55. })
  56. const [open, setOpen] = useState(false)
  57. const { handleNodeAdd } = useNodesInteractions()
  58. const { availablePrevBlocks } = useAvailableBlocks((data as Edge['data'])!.targetType, (data as Edge['data'])?.isInIteration)
  59. const { availableNextBlocks } = useAvailableBlocks((data as Edge['data'])!.sourceType, (data as Edge['data'])?.isInIteration)
  60. const {
  61. _sourceRunningStatus,
  62. _targetRunningStatus,
  63. } = data
  64. const linearGradientId = useMemo(() => {
  65. if (
  66. (
  67. _sourceRunningStatus === NodeRunningStatus.Succeeded
  68. || _sourceRunningStatus === NodeRunningStatus.Failed
  69. || _sourceRunningStatus === NodeRunningStatus.Exception
  70. ) && (
  71. _targetRunningStatus === NodeRunningStatus.Succeeded
  72. || _targetRunningStatus === NodeRunningStatus.Failed
  73. || _targetRunningStatus === NodeRunningStatus.Exception
  74. || _targetRunningStatus === NodeRunningStatus.Running
  75. )
  76. )
  77. return id
  78. }, [_sourceRunningStatus, _targetRunningStatus, id])
  79. const handleOpenChange = useCallback((v: boolean) => {
  80. setOpen(v)
  81. }, [])
  82. const handleInsert = useCallback<OnSelectBlock>((nodeType, toolDefaultValue) => {
  83. handleNodeAdd(
  84. {
  85. nodeType,
  86. toolDefaultValue,
  87. },
  88. {
  89. prevNodeId: source,
  90. prevNodeSourceHandle: sourceHandleId || 'source',
  91. nextNodeId: target,
  92. nextNodeTargetHandle: targetHandleId || 'target',
  93. },
  94. )
  95. }, [handleNodeAdd, source, sourceHandleId, target, targetHandleId])
  96. const stroke = useMemo(() => {
  97. if (selected)
  98. return getEdgeColor(NodeRunningStatus.Running)
  99. if (linearGradientId)
  100. return `url(#${linearGradientId})`
  101. if (data?._connectedNodeIsHovering)
  102. return getEdgeColor(NodeRunningStatus.Running, sourceHandleId === ErrorHandleTypeEnum.failBranch)
  103. return getEdgeColor()
  104. }, [data._connectedNodeIsHovering, linearGradientId, selected, sourceHandleId])
  105. return (
  106. <>
  107. {
  108. linearGradientId && (
  109. <CustomEdgeLinearGradientRender
  110. id={linearGradientId}
  111. startColor={getEdgeColor(_sourceRunningStatus)}
  112. stopColor={getEdgeColor(_targetRunningStatus)}
  113. position={{
  114. x1: sourceX,
  115. y1: sourceY,
  116. x2: targetX,
  117. y2: targetY,
  118. }}
  119. />
  120. )
  121. }
  122. <BaseEdge
  123. id={id}
  124. path={edgePath}
  125. style={{
  126. stroke,
  127. strokeWidth: 2,
  128. opacity: data._waitingRun ? 0.7 : 1,
  129. }}
  130. />
  131. <EdgeLabelRenderer>
  132. <div
  133. className={cn(
  134. 'nopan nodrag hover:scale-125',
  135. data?._hovering ? 'block' : 'hidden',
  136. open && '!block',
  137. data.isInIteration && `z-[${ITERATION_CHILDREN_Z_INDEX}]`,
  138. )}
  139. style={{
  140. position: 'absolute',
  141. transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
  142. pointerEvents: 'all',
  143. opacity: data._waitingRun ? 0.7 : 1,
  144. }}
  145. >
  146. <BlockSelector
  147. open={open}
  148. onOpenChange={handleOpenChange}
  149. asChild
  150. onSelect={handleInsert}
  151. availableBlocksTypes={intersection(availablePrevBlocks, availableNextBlocks)}
  152. triggerClassName={() => 'hover:scale-150 transition-all'}
  153. />
  154. </div>
  155. </EdgeLabelRenderer>
  156. </>
  157. )
  158. }
  159. export default memo(CustomEdge)