mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 19:59:50 +08:00
110 lines
3.0 KiB
TypeScript
110 lines
3.0 KiB
TypeScript
import {
|
|
memo,
|
|
useCallback,
|
|
useState,
|
|
} from 'react'
|
|
import { RiAddCircleFill } from '@remixicon/react'
|
|
import { useStoreApi } from 'reactflow'
|
|
import { useTranslation } from 'react-i18next'
|
|
import type { OffsetOptions } from '@floating-ui/react'
|
|
import {
|
|
generateNewNode,
|
|
} from '../utils'
|
|
import {
|
|
useAvailableBlocks,
|
|
useNodesReadOnly,
|
|
usePanelInteractions,
|
|
} from '../hooks'
|
|
import { NODES_INITIAL_DATA } from '../constants'
|
|
import { useWorkflowStore } from '../store'
|
|
import TipPopup from './tip-popup'
|
|
import cn from '@/utils/classnames'
|
|
import BlockSelector from '@/app/components/workflow/block-selector'
|
|
import type {
|
|
OnSelectBlock,
|
|
} from '@/app/components/workflow/types'
|
|
import {
|
|
BlockEnum,
|
|
} from '@/app/components/workflow/types'
|
|
|
|
type AddBlockProps = {
|
|
renderTrigger?: (open: boolean) => React.ReactNode
|
|
offset?: OffsetOptions
|
|
}
|
|
const AddBlock = ({
|
|
renderTrigger,
|
|
offset,
|
|
}: AddBlockProps) => {
|
|
const { t } = useTranslation()
|
|
const store = useStoreApi()
|
|
const workflowStore = useWorkflowStore()
|
|
const { nodesReadOnly } = useNodesReadOnly()
|
|
const { handlePaneContextmenuCancel } = usePanelInteractions()
|
|
const [open, setOpen] = useState(false)
|
|
const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, false)
|
|
|
|
const handleOpenChange = useCallback((open: boolean) => {
|
|
setOpen(open)
|
|
if (!open)
|
|
handlePaneContextmenuCancel()
|
|
}, [handlePaneContextmenuCancel])
|
|
|
|
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
|
const {
|
|
getNodes,
|
|
} = store.getState()
|
|
const nodes = getNodes()
|
|
const nodesWithSameType = nodes.filter(node => node.data.type === type)
|
|
const newNode = generateNewNode({
|
|
data: {
|
|
...NODES_INITIAL_DATA[type],
|
|
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${type}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${type}`),
|
|
...(toolDefaultValue || {}),
|
|
_isCandidate: true,
|
|
},
|
|
position: {
|
|
x: 0,
|
|
y: 0,
|
|
},
|
|
})
|
|
workflowStore.setState({
|
|
candidateNode: newNode,
|
|
})
|
|
}, [store, workflowStore, t])
|
|
|
|
const renderTriggerElement = useCallback((open: boolean) => {
|
|
return (
|
|
<TipPopup
|
|
title={t('workflow.common.addBlock')}
|
|
>
|
|
<div className={cn(
|
|
'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
|
|
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
|
open && '!bg-black/5',
|
|
)}>
|
|
<RiAddCircleFill className='w-4 h-4' />
|
|
</div>
|
|
</TipPopup>
|
|
)
|
|
}, [nodesReadOnly, t])
|
|
|
|
return (
|
|
<BlockSelector
|
|
open={open}
|
|
onOpenChange={handleOpenChange}
|
|
disabled={nodesReadOnly}
|
|
onSelect={handleSelect}
|
|
placement='top-start'
|
|
offset={offset ?? {
|
|
mainAxis: 4,
|
|
crossAxis: -8,
|
|
}}
|
|
trigger={renderTrigger || renderTriggerElement}
|
|
popupClassName='!min-w-[256px]'
|
|
availableBlocksTypes={availableNextBlocks}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export default memo(AddBlock)
|