import type { Placement } from '@langgenius/dify-ui/popover' import type { CSSProperties, KeyboardEvent, MouseEventHandler, ReactElement } from 'react' import type { CommonNodeType, NodeDefault, OnNodeAdd, OnSelectBlock, ToolWithProvider, } from '../types' import type { TabType } from './types' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { Popover, PopoverClose, PopoverContent, PopoverTitle, PopoverTrigger, } from '@langgenius/dify-ui/popover' import { memo, useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useHooksStore } from '@/app/components/workflow/hooks-store' import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { FlowType } from '@/types/common' import TipPopup from '../operator/tip-popup' import { useStore } from '../store' import { BlockEnum, isTriggerNode } from '../types' import { useTabs } from './hooks' import { BlockSelectorPanels } from './tabs' export type BlockSelectorProps = { open?: boolean onOpenChange?: (open: boolean) => void onSelect: OnSelectBlock trigger?: (open: boolean) => ReactElement triggerTooltip?: string placement?: Placement sideOffset?: number alignOffset?: number triggerStyle?: CSSProperties triggerClassName?: (open: boolean) => string triggerAriaLabel?: string popupClassName?: string availableBlocksTypes?: BlockEnum[] disabled?: boolean blocks?: NodeDefault[] dataSources?: ToolWithProvider[] noBlocks?: boolean noTools?: boolean standalonePanel?: TabType showStartTab?: boolean defaultActiveTab?: TabType ignoreNodeIds?: string[] forceEnableStartTab?: boolean // Force enabling Start tab regardless of existing trigger/user input nodes (e.g., when changing Start node type). allowUserInputSelection?: boolean // Override user-input availability; default logic blocks it when triggers exist. snippetInsertPayload?: Parameters[1] isolateKeyboardEvents?: boolean } function BlockSelector({ open: openFromProps, onOpenChange, onSelect, trigger, triggerTooltip, placement = 'right', sideOffset, alignOffset, triggerClassName, triggerAriaLabel, triggerStyle, popupClassName, availableBlocksTypes, disabled, blocks, dataSources, noBlocks = false, noTools = false, standalonePanel, showStartTab = false, defaultActiveTab, ignoreNodeIds = [], forceEnableStartTab = false, allowUserInputSelection, snippetInsertPayload, isolateKeyboardEvents = false, }: BlockSelectorProps) { const { t } = useTranslation() const [localOpen, setLocalOpen] = useState(false) const searchInputRef = useRef(null) const open = openFromProps === undefined ? localOpen : openFromProps const handleOpenChange = useCallback( (newOpen: boolean) => { if (newOpen && disabled) return setLocalOpen(newOpen) if (onOpenChange) onOpenChange(newOpen) }, [disabled, onOpenChange], ) const handleTrigger = useCallback>((e) => { e.stopPropagation() }, []) const handlePopupClick = useCallback>((event) => { event.stopPropagation() }, []) const handleSelect = useCallback( (type, pluginDefaultValue) => { if (disabled) return handleOpenChange(false) onSelect(type, pluginDefaultValue) }, [disabled, handleOpenChange, onSelect], ) const handlePopupKeyDown = useCallback( (event: KeyboardEvent) => { if (isolateKeyboardEvents) event.stopPropagation() }, [isolateKeyboardEvents], ) const triggerControl = trigger ? ( ) : ( $['common.addBlock'], { ns: 'workflow' })} disabled={disabled} render={