mirror of
				https://github.com/langgenius/dify.git
				synced 2025-10-31 10:53:02 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			241 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			241 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import type {
 | |
|   FC,
 | |
|   ReactElement,
 | |
| } from 'react'
 | |
| import {
 | |
|   cloneElement,
 | |
|   memo,
 | |
|   useEffect,
 | |
|   useMemo,
 | |
|   useRef,
 | |
| } from 'react'
 | |
| import {
 | |
|   RiCheckboxCircleLine,
 | |
|   RiErrorWarningLine,
 | |
|   RiLoader2Line,
 | |
| } from '@remixicon/react'
 | |
| import { useTranslation } from 'react-i18next'
 | |
| import type { NodeProps } from '../../types'
 | |
| import {
 | |
|   BlockEnum,
 | |
|   NodeRunningStatus,
 | |
| } from '../../types'
 | |
| import {
 | |
|   useNodesReadOnly,
 | |
|   useToolIcon,
 | |
| } from '../../hooks'
 | |
| import { useNodeIterationInteractions } from '../iteration/use-interactions'
 | |
| import type { IterationNodeType } from '../iteration/types'
 | |
| import {
 | |
|   NodeSourceHandle,
 | |
|   NodeTargetHandle,
 | |
| } from './components/node-handle'
 | |
| import NodeResizer from './components/node-resizer'
 | |
| import NodeControl from './components/node-control'
 | |
| import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
 | |
| import cn from '@/utils/classnames'
 | |
| import BlockIcon from '@/app/components/workflow/block-icon'
 | |
| import Tooltip from '@/app/components/base/tooltip'
 | |
| 
 | |
| type BaseNodeProps = {
 | |
|   children: ReactElement
 | |
| } & NodeProps
 | |
| 
 | |
| const BaseNode: FC<BaseNodeProps> = ({
 | |
|   id,
 | |
|   data,
 | |
|   children,
 | |
| }) => {
 | |
|   const { t } = useTranslation()
 | |
|   const nodeRef = useRef<HTMLDivElement>(null)
 | |
|   const { nodesReadOnly } = useNodesReadOnly()
 | |
|   const { handleNodeIterationChildSizeChange } = useNodeIterationInteractions()
 | |
|   const toolIcon = useToolIcon(data)
 | |
| 
 | |
|   useEffect(() => {
 | |
|     if (nodeRef.current && data.selected && data.isInIteration) {
 | |
|       const resizeObserver = new ResizeObserver(() => {
 | |
|         handleNodeIterationChildSizeChange(id)
 | |
|       })
 | |
| 
 | |
|       resizeObserver.observe(nodeRef.current)
 | |
| 
 | |
|       return () => {
 | |
|         resizeObserver.disconnect()
 | |
|       }
 | |
|     }
 | |
|   }, [data.isInIteration, data.selected, id, handleNodeIterationChildSizeChange])
 | |
| 
 | |
|   const showSelectedBorder = data.selected || data._isBundled || data._isEntering
 | |
|   const {
 | |
|     showRunningBorder,
 | |
|     showSuccessBorder,
 | |
|     showFailedBorder,
 | |
|   } = useMemo(() => {
 | |
|     return {
 | |
|       showRunningBorder: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder,
 | |
|       showSuccessBorder: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder,
 | |
|       showFailedBorder: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder,
 | |
|     }
 | |
|   }, [data._runningStatus, showSelectedBorder])
 | |
| 
 | |
|   return (
 | |
|     <div
 | |
|       className={cn(
 | |
|         'flex border-[2px] rounded-2xl',
 | |
|         showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent',
 | |
|         !showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight',
 | |
|       )}
 | |
|       ref={nodeRef}
 | |
|       style={{
 | |
|         width: data.type === BlockEnum.Iteration ? data.width : 'auto',
 | |
|         height: data.type === BlockEnum.Iteration ? data.height : 'auto',
 | |
|       }}
 | |
|     >
 | |
|       <div
 | |
|         className={cn(
 | |
|           'group relative pb-1 shadow-xs',
 | |
|           'border border-transparent rounded-[15px]',
 | |
|           data.type !== BlockEnum.Iteration && 'w-[240px] bg-workflow-block-bg',
 | |
|           data.type === BlockEnum.Iteration && 'flex flex-col w-full h-full bg-[#fcfdff]/80',
 | |
|           !data._runningStatus && 'hover:shadow-lg',
 | |
|           showRunningBorder && '!border-primary-500',
 | |
|           showSuccessBorder && '!border-[#12B76A]',
 | |
|           showFailedBorder && '!border-[#F04438]',
 | |
|           data._isBundled && '!shadow-lg',
 | |
|         )}
 | |
|       >
 | |
|         {
 | |
|           data._inParallelHovering && (
 | |
|             <div className='absolute left-2 -top-2.5 top system-2xs-medium-uppercase text-text-tertiary z-10'>
 | |
|               {t('workflow.common.parallelRun')}
 | |
|             </div>
 | |
|           )
 | |
|         }
 | |
|         {
 | |
|           data._showAddVariablePopup && (
 | |
|             <AddVariablePopupWithPosition
 | |
|               nodeId={id}
 | |
|               nodeData={data}
 | |
|             />
 | |
|           )
 | |
|         }
 | |
|         {
 | |
|           data.type === BlockEnum.Iteration && (
 | |
|             <NodeResizer
 | |
|               nodeId={id}
 | |
|               nodeData={data}
 | |
|             />
 | |
|           )
 | |
|         }
 | |
|         {
 | |
|           !data._isCandidate && (
 | |
|             <NodeTargetHandle
 | |
|               id={id}
 | |
|               data={data}
 | |
|               handleClassName='!top-4 !-left-[9px] !translate-y-0'
 | |
|               handleId='target'
 | |
|             />
 | |
|           )
 | |
|         }
 | |
|         {
 | |
|           data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._isCandidate && (
 | |
|             <NodeSourceHandle
 | |
|               id={id}
 | |
|               data={data}
 | |
|               handleClassName='!top-4 !-right-[9px] !translate-y-0'
 | |
|               handleId='source'
 | |
|             />
 | |
|           )
 | |
|         }
 | |
|         {
 | |
|           !data._runningStatus && !nodesReadOnly && !data._isCandidate && (
 | |
|             <NodeControl
 | |
|               id={id}
 | |
|               data={data}
 | |
|             />
 | |
|           )
 | |
|         }
 | |
|         <div className={cn(
 | |
|           'flex items-center px-3 pt-3 pb-2 rounded-t-2xl',
 | |
|           data.type === BlockEnum.Iteration && 'bg-[rgba(250,252,255,0.9)]',
 | |
|         )}>
 | |
|           <BlockIcon
 | |
|             className='shrink-0 mr-2'
 | |
|             type={data.type}
 | |
|             size='md'
 | |
|             toolIcon={toolIcon}
 | |
|           />
 | |
|           <div
 | |
|             title={data.title}
 | |
|             className='grow mr-1 system-sm-semibold-uppercase text-text-primary truncate flex items-center'
 | |
|           >
 | |
|             <div>
 | |
|               {data.title}
 | |
|             </div>
 | |
|             {
 | |
|               data.type === BlockEnum.Iteration && (data as IterationNodeType).is_parallel && (
 | |
|                 <Tooltip popupContent={
 | |
|                   <div className='w-[180px]'>
 | |
|                     <div className='font-extrabold'>
 | |
|                       {t('workflow.nodes.iteration.parallelModeEnableTitle')}
 | |
|                     </div>
 | |
|                     {t('workflow.nodes.iteration.parallelModeEnableDesc')}
 | |
|                   </div>}
 | |
|                 >
 | |
|                   <div className='flex justify-center items-center px-[5px] py-[3px] ml-1 border-[1px] border-text-warning rounded-[5px] text-text-warning system-2xs-medium-uppercase '>
 | |
|                     {t('workflow.nodes.iteration.parallelModeUpper')}
 | |
|                   </div>
 | |
|                 </Tooltip>
 | |
|               )
 | |
|             }
 | |
|           </div>
 | |
|           {
 | |
|             data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && (
 | |
|               <div className='mr-1.5 text-xs font-medium text-primary-600'>
 | |
|                 {data._iterationIndex > data._iterationLength ? data._iterationLength : data._iterationIndex}/{data._iterationLength}
 | |
|               </div>
 | |
|             )
 | |
|           }
 | |
|           {
 | |
|             (data._runningStatus === NodeRunningStatus.Running || data._singleRunningStatus === NodeRunningStatus.Running) && (
 | |
|               <RiLoader2Line className='w-3.5 h-3.5 text-primary-600 animate-spin' />
 | |
|             )
 | |
|           }
 | |
|           {
 | |
|             data._runningStatus === NodeRunningStatus.Succeeded && (
 | |
|               <RiCheckboxCircleLine className='w-3.5 h-3.5 text-[#12B76A]' />
 | |
|             )
 | |
|           }
 | |
|           {
 | |
|             data._runningStatus === NodeRunningStatus.Failed && (
 | |
|               <RiErrorWarningLine className='w-3.5 h-3.5 text-[#F04438]' />
 | |
|             )
 | |
|           }
 | |
|         </div>
 | |
|         {
 | |
|           data.type !== BlockEnum.Iteration && (
 | |
|             cloneElement(children, { id, data })
 | |
|           )
 | |
|         }
 | |
|         {
 | |
|           data.type === BlockEnum.Iteration && (
 | |
|             <div className='grow pl-1 pr-1 pb-1'>
 | |
|               {cloneElement(children, { id, data })}
 | |
|             </div>
 | |
|           )
 | |
|         }
 | |
|         {
 | |
|           data.desc && data.type !== BlockEnum.Iteration && (
 | |
|             <div className='px-3 pt-1 pb-2 system-xs-regular text-text-tertiary whitespace-pre-line break-words'>
 | |
|               {data.desc}
 | |
|             </div>
 | |
|           )
 | |
|         }
 | |
|       </div>
 | |
|     </div>
 | |
|   )
 | |
| }
 | |
| 
 | |
| export default memo(BaseNode)
 | 
