import type { NodeDefault, OnSelectBlock } from '../types' import type { BlockClassification } from './types' import { createPreviewCardHandle, PreviewCard, PreviewCardTrigger, } from '@langgenius/dify-ui/preview-card' import { groupBy } from 'es-toolkit/compat' import { Fragment, memo, useCallback, useId, 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' import { BlockSelectorPreviewCardContent } from './preview-card' 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(), []) const previewDescriptionBaseId = useId() // 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: BlockClassification) => { 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', }) } /> ) } const previewDescriptionId = block.metaData.description ? `${previewDescriptionBaseId}-${block.metaData.type}` : undefined return ( onSelect(block.metaData.type)} > {block.metaData.title} {block.metaData.type === BlockEnum.LoopEnd && ( $['nodes.loop.loopNode'], { ns: 'workflow' })} className="ml-2 shrink-0" /> )} } /> {previewDescriptionId && ( {block.metaData.description} )} ) })}
) }, [groups, onSelect, previewCardHandle, previewDescriptionBaseId, 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)