import type { BlockEnum, CommonNodeType } from '../types' import type { TriggerDefaultValue } from './types' import { cn } from '@langgenius/dify-ui/cn' import { createPreviewCardHandle, PreviewCard, PreviewCardTrigger, } from '@langgenius/dify-ui/preview-card' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { Fragment, memo, useCallback, useEffect, useId, useMemo } from 'react' import { useTranslation } from 'react-i18next' import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import BlockIcon from '../block-icon' import { BlockEnum as BlockEnumValues } from '../types' import { BlockSelectorRow } from './block-selector-row' import { START_BLOCKS } from './constants' import { BlockSelectorPreviewCardContent } from './preview-card' type StartBlocksProps = { searchText: string onSelect: (type: BlockEnum, triggerDefaultValue?: TriggerDefaultValue) => void availableBlocksTypes?: BlockEnum[] onContentStateChange?: (hasContent: boolean) => void hideUserInput?: boolean showMostCommonBadge?: boolean showUserInputAdded?: boolean showUserInputDisabled?: boolean disabled?: boolean } type StartBlockPreviewPayload = { block: (typeof START_BLOCKS)[number] label: string description: string } const StartBlocks = ({ searchText, onSelect, availableBlocksTypes = [], onContentStateChange, hideUserInput = false, // Allow parent to explicitly hide Start node option (e.g. when one already exists). showMostCommonBadge = false, showUserInputAdded = false, showUserInputDisabled = false, disabled = false, }: StartBlocksProps) => { const { t } = useTranslation() const nodes = useNodes() const previewCardHandle = useMemo(() => createPreviewCardHandle(), []) const previewDescriptionBaseId = useId() const filteredBlocks = useMemo(() => { // Check if Start node already exists in workflow const hasStartNode = nodes.some( (node) => (node.data as CommonNodeType)?.type === BlockEnumValues.Start, ) const normalizedSearch = searchText.toLowerCase() const getDisplayName = (blockType: BlockEnum) => { if (blockType === BlockEnumValues.TriggerWebhook) return t(($) => $.customWebhook, { ns: 'workflow' }) return t(($) => $[`blocks.${blockType}`], { ns: 'workflow' }) } return START_BLOCKS.filter((block) => { // Hide User Input (Start) if it already exists in workflow or if hideUserInput is true. // In read-only conflict modes, keep it visible so the row can show Added or disabled tooltip state. if ( block.type === BlockEnumValues.Start && (hasStartNode || hideUserInput) && !showUserInputAdded && !showUserInputDisabled ) return false // Filter by search text const displayName = getDisplayName(block.type).toLowerCase() if ( !displayName.includes(normalizedSearch) && !block.title.toLowerCase().includes(normalizedSearch) ) return false // availableBlocksTypes now contains properly filtered entry node types from parent return availableBlocksTypes.includes(block.type) }) }, [ searchText, availableBlocksTypes, nodes, t, hideUserInput, showUserInputAdded, showUserInputDisabled, ]) const isEmpty = filteredBlocks.length === 0 useEffect(() => { onContentStateChange?.(!isEmpty) }, [isEmpty, onContentStateChange]) const renderBlock = useCallback( (block: (typeof START_BLOCKS)[number]) => { const isUserInput = block.type === BlockEnumValues.Start const isUserInputDisabled = isUserInput && showUserInputDisabled const isRowDisabled = disabled || (isUserInput && showUserInputAdded) || isUserInputDisabled const label = t(($) => $[`blocks.${block.type}`], { ns: 'workflow' }) const description = block.type === BlockEnumValues.Start ? t(($) => $['nodes.start.userInputTipDescription'], { ns: 'workflow' }) : t(($) => $[`blocksAbout.${block.type}`], { ns: 'workflow' }) const previewDescriptionId = `${previewDescriptionBaseId}-${block.type}` const disabledReason = t(($) => $['nodes.startPlaceholder.userInputConflictTip'], { ns: 'workflow', }) const row = ( { if (isRowDisabled) return onSelect(block.type) }} >
{label} {isUserInput && showUserInputAdded && ( {t(($) => $['operation.added'], { ns: 'common' })} )} {isUserInput && showMostCommonBadge && !showUserInputAdded && ( {t(($) => $['blocks.mostCommon'], { ns: 'workflow' })} )} {isUserInput && !showMostCommonBadge && !showUserInputAdded && !showUserInputDisabled && ( {t(($) => $['blocks.originalStartNode'], { ns: 'workflow' })} )}
) if (isUserInputDisabled) { return ( {disabledReason} {description} ) } return ( {description} ) }, [ disabled, onSelect, previewCardHandle, previewDescriptionBaseId, showMostCommonBadge, showUserInputAdded, showUserInputDisabled, t, ], ) if (isEmpty) return null return (
{filteredBlocks.map((block, index) => (
{renderBlock(block)} {block.type === BlockEnumValues.Start && !showMostCommonBadge && index < filteredBlocks.length - 1 && (
)}
))}
{({ payload }) => ( )}
) } type StartBlockPreviewCardProps = { payload?: StartBlockPreviewPayload t: ReturnType['t'] } function StartBlockPreviewCard({ payload, t }: StartBlockPreviewCardProps) { if (!payload) return null const { block, label, description } = payload const showDifyTeamAuthor = [ BlockEnumValues.Start, BlockEnumValues.TriggerWebhook, BlockEnumValues.TriggerSchedule, ].includes(block.type) return (
{label}
{description}
{showDifyTeamAuthor && (
{t(($) => $.author, { ns: 'tools' })} {t(($) => $.difyTeam, { ns: 'workflow' })}
)}
) } export default memo(StartBlocks)