| 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} | |
| </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) | |