import { memo, useCallback, useMemo, } from 'react' import { useStoreApi } from 'reactflow' import { useTranslation } from 'react-i18next' import { groupBy } from 'lodash-es' import BlockIcon from '../block-icon' import { BlockEnum } from '../types' import type { NodeDefault } from '../types' import { BLOCK_CLASSIFICATIONS } from './constants' import { useBlocks } from './hooks' import Tooltip from '@/app/components/base/tooltip' import Badge from '@/app/components/base/badge' type BlocksProps = { searchText: string onSelect: (type: BlockEnum) => void availableBlocksTypes?: BlockEnum[] blocks?: NodeDefault[] } const Blocks = ({ searchText, onSelect, availableBlocksTypes = [], blocks: blocksFromProps, }: BlocksProps) => { const { t } = useTranslation() const store = useStoreApi() const blocksFromHooks = useBlocks() // Use external blocks if provided, otherwise fallback to hook-based blocks const blocks = blocksFromProps || blocksFromHooks.map(block => ({ metaData: { classification: block.classification, sort: 0, // Default sort order type: block.type, title: block.title, author: 'Dify', description: block.description, }, defaultValue: {}, checkValid: () => ({ isValid: true }), }) as NodeDefault) const groups = useMemo(() => { return BLOCK_CLASSIFICATIONS.reduce((acc, classification) => { const grouped = groupBy(blocks, 'metaData.classification') const list = (grouped[classification] || []).filter((block) => { // Filter out trigger types from Blocks tab if (block.metaData.type === BlockEnum.TriggerWebhook || block.metaData.type === BlockEnum.TriggerSchedule || block.metaData.type === BlockEnum.TriggerPlugin) return false return block.metaData.title.toLowerCase().includes(searchText.toLowerCase()) && availableBlocksTypes.includes(block.metaData.type) }) return { ...acc, [classification]: list, } }, {} as Record) }, [blocks, searchText, availableBlocksTypes]) const isEmpty = Object.values(groups).every(list => !list.length) const renderGroup = useCallback((classification: string) => { const list = groups[classification].sort((a, b) => (a.metaData.sort || 0) - (b.metaData.sort || 0)) const { getNodes } = store.getState() const nodes = getNodes() const hasKnowledgeBaseNode = nodes.some(node => node.data.type === BlockEnum.KnowledgeBase) const filteredList = list.filter((block) => { if (hasKnowledgeBaseNode) return block.metaData.type !== BlockEnum.KnowledgeBase return true }) return (
{ classification !== '-' && !!filteredList.length && (
{t(`workflow.tabs.${classification}`)}
) } { filteredList.map(block => (
{block.metaData.title}
{block.metaData.description}
)} >
onSelect(block.metaData.type)} >
{block.metaData.title}
{ block.metaData.type === BlockEnum.LoopEnd && ( ) }
)) } ) }, [groups, onSelect, t, store]) return (
{ isEmpty && (
{t('workflow.tabs.noResult')}
) } { !isEmpty && BLOCK_CLASSIFICATIONS.map(renderGroup) }
) } export default memo(Blocks)