import type { NodeDefault, OnSelectBlock } from '../types' import type { BlockClassificationEnum } from './types' import { createPreviewCardHandle, PreviewCard, PreviewCardContent, PreviewCardTrigger, } from '@langgenius/dify-ui/preview-card' import { groupBy } from 'es-toolkit/compat' import { memo, useCallback, useMemo, } from 'react' import { useTranslation } from 'react-i18next' import { useStoreApi } from 'reactflow' import Badge from '@/app/components/base/badge' import BlockIcon from '../block-icon' import { BlockEnum } from '../types' import { AgentBlockItem } from './agent-selector' import { BLOCK_CLASSIFICATIONS } from './constants' import { useBlocks } from './hooks' type BlocksProps = { searchText: string onSelect: OnSelectBlock availableBlocksTypes?: BlockEnum[] blocks?: NodeDefault[] } type BlockPreviewPayload = { block: NodeDefault } const Blocks = ({ searchText, onSelect, availableBlocksTypes = [], blocks: blocksFromProps, }: BlocksProps) => { const { t } = useTranslation() const store = useStoreApi() const blocksFromHooks = useBlocks() const previewCardHandle = useMemo(() => createPreviewCardHandle(), []) // 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', // @ts-expect-error Fix this missing field later 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, availableBlocksTypes, searchText]) const isEmpty = Object.values(groups).every(list => !list.length) const renderGroup = useCallback((classification: BlockClassificationEnum) => { const list = [...groups[classification]!].sort((a, b) => { if (a.metaData.type === BlockEnum.AgentV2) return -1 if (b.metaData.type === BlockEnum.AgentV2) return 1 return (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(`tabs.${classification}`, { ns: 'workflow' })}
) } { filteredList.map((block) => { if (block.metaData.type === BlockEnum.AgentV2) { return ( onSelect(BlockEnum.AgentV2, { agent_binding: { binding_type: 'roster_agent', agent_id: agent.id, }, agent_node_kind: 'dify_agent', version: '2', })} onStartFromScratch={() => onSelect(BlockEnum.AgentV2, { agent_binding: { binding_type: 'inline_agent', }, agent_node_kind: 'dify_agent', version: '2', })} /> ) } return ( onSelect(block.metaData.type)} > {block.metaData.title} { block.metaData.type === BlockEnum.LoopEnd && ( ) } )} /> ) }) }
) }, [groups, onSelect, previewCardHandle, t, store]) return (
{ isEmpty && (
{t('tabs.noResult', { ns: 'workflow' })}
) } { !isEmpty && BLOCK_CLASSIFICATIONS.map(renderGroup) } {({ payload }) => ( )}
) } type BlockPreviewCardProps = { payload?: BlockPreviewPayload } function BlockPreviewCard({ payload, }: BlockPreviewCardProps) { if (!payload) return null const { block } = payload return (
{block.metaData.title}
{block.metaData.description}
) } export default memo(Blocks)