'use client' import type { RefObject } from 'react' import type { BlockEnum, OnSelectBlock } from '../types' import type { ListRef } from './marketplace-plugin/list' import type { TriggerDefaultValue, TriggerWithProvider } from './types' import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' import { useDebounce } from 'ahooks' import { useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/query' import { getMarketplaceCategoryUrl } from '@/app/components/plugins/marketplace/utils' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' import { useFeaturedTriggersRecommendations } from '@/service/use-plugins' import { useAllTriggerPlugins, useInvalidateAllTriggerPlugins } from '@/service/use-triggers' import { PluginCategoryEnum } from '../../plugins/types' import { BlockEnum as BlockEnumValue } from '../types' import { ENTRY_NODE_TYPES } from './constants' import FeaturedTriggers from './featured-triggers' import PluginList from './marketplace-plugin/list' import StartBlocks from './start-blocks' import TriggerPluginList from './trigger-plugin/list' const popoverMarketplaceFooterClassName = 'system-sm-medium z-10 flex h-8 flex-none items-center border-t border-divider-subtle bg-components-panel-bg-blur px-4 py-1' const panelMarketplaceFooterClassName = 'system-xs-regular z-10 flex flex-none flex-col items-start gap-2 rounded-lg px-4 pt-2 pb-4' const DEFAULT_TAGS: string[] = [] const SectionDivider = () => (
) const MarketplaceFooterDivider = () => (
) type AllStartBlocksProps = { className?: string searchText: string onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void availableBlocksTypes?: BlockEnum[] tags?: string[] allowUserInputSelection?: boolean // Allow user input option even when trigger node already exists (e.g. when no Start node yet or changing node type). hasUserInputNode?: boolean hasTriggerNode?: boolean variant?: 'popover' | 'panel' } function AllStartBlocks({ className, searchText, onSelect, availableBlocksTypes, tags = DEFAULT_TAGS, allowUserInputSelection = false, hasUserInputNode = false, hasTriggerNode = false, variant = 'popover', }: AllStartBlocksProps) { const { t } = useTranslation() const [hasStartBlocksContent, setHasStartBlocksContent] = useState(false) const [hasPluginContent, setHasPluginContent] = useState(false) const { data: enable_marketplace } = useSuspenseQuery({ ...systemFeaturesQueryOptions(), select: (s) => s.enable_marketplace, }) const pluginRef = useRef(null) const wrapElemRef = useRef(null) const entryNodeTypes = useMemo(() => { return availableBlocksTypes?.length ? availableBlocksTypes : [...ENTRY_NODE_TYPES] }, [availableBlocksTypes]) const enableTriggerPlugin = entryNodeTypes.includes(BlockEnumValue.TriggerPlugin) const { data: triggerProviders = [] } = useAllTriggerPlugins(enableTriggerPlugin) const providerMap = useMemo(() => { const map = new Map() triggerProviders.forEach((provider) => { const keys = [provider.plugin_id, provider.plugin_unique_identifier, provider.id].filter( Boolean, ) as string[] keys.forEach((key) => { if (!map.has(key)) map.set(key, provider) }) }) return map }, [triggerProviders]) const invalidateTriggers = useInvalidateAllTriggerPlugins() const trimmedSearchText = searchText.trim() const hasSearchText = trimmedSearchText.length > 0 const hasFilter = hasSearchText || tags.length > 0 const marketplaceFilters = useMemo( () => ({ query: trimmedSearchText, tags }), [tags, trimmedSearchText], ) const debouncedMarketplaceFilters = useDebounce(marketplaceFilters, { wait: 500 }) const isMarketplaceFilterSettled = debouncedMarketplaceFilters === marketplaceFilters const isMarketplaceSearchEnabled = enableTriggerPlugin && enable_marketplace && hasFilter const { plugins: featuredPlugins = [], isLoading: featuredLoading } = useFeaturedTriggersRecommendations(enableTriggerPlugin && enable_marketplace && !hasFilter) const marketplaceSearchParams = useMemo( () => isMarketplaceSearchEnabled && isMarketplaceFilterSettled ? { query: debouncedMarketplaceFilters.query, tags: debouncedMarketplaceFilters.tags, category: PluginCategoryEnum.trigger, } : undefined, [debouncedMarketplaceFilters, isMarketplaceSearchEnabled, isMarketplaceFilterSettled], ) const { data: marketplacePluginsData, isFetching: isMarketplaceFetching } = useMarketplacePlugins(marketplaceSearchParams) const marketplacePlugins = useMemo( () => marketplacePluginsData?.pages.flatMap((page) => page.plugins) ?? [], [marketplacePluginsData?.pages], ) const shouldShowFeatured = enableTriggerPlugin && enable_marketplace && !hasFilter const shouldShowMarketplaceFooter = enable_marketplace const isPanelVariant = variant === 'panel' const handleStartBlocksContentChange = useCallback((hasContent: boolean) => { setHasStartBlocksContent(hasContent) }, []) const handlePluginContentChange = useCallback((hasContent: boolean) => { setHasPluginContent(hasContent) }, []) const hasInstalledPluginContent = enableTriggerPlugin && hasPluginContent const hasMarketplaceContent = enableTriggerPlugin && enable_marketplace && marketplacePlugins.length > 0 const hasAnyContent = hasStartBlocksContent || hasInstalledPluginContent || shouldShowFeatured || hasMarketplaceContent const isMarketplaceSearchPending = isMarketplaceSearchEnabled && (!isMarketplaceFilterSettled || isMarketplaceFetching) const shouldShowEmptyState = hasFilter && !isMarketplaceSearchPending && !hasAnyContent const shouldShowInstalledTriggersDivider = isPanelVariant && hasStartBlocksContent && hasInstalledPluginContent const shouldShowMarketplaceSectionDivider = enableTriggerPlugin && enable_marketplace && (hasStartBlocksContent || hasInstalledPluginContent) && (shouldShowFeatured || hasMarketplaceContent) return (
pluginRef.current?.handleScroll()} >
{hasUserInputNode && (
{t(($) => $['nodes.startPlaceholder.userInputConflictTip'], { ns: 'workflow' })}
)}
{shouldShowInstalledTriggersDivider && } {enableTriggerPlugin && ( )} {shouldShowMarketplaceSectionDivider && } {shouldShowFeatured && ( { invalidateTriggers() }} /> )} {enableTriggerPlugin && enable_marketplace && ( } list={marketplacePlugins} searchText={trimmedSearchText} category={PluginCategoryEnum.trigger} tags={tags} hideFindMoreFooter /> )}
{shouldShowEmptyState && (
{t(($) => $['nodes.startPlaceholder.noTriggersFound'], { ns: 'workflow' })}
{t(($) => $['tabs.requestToCommunity'], { ns: 'workflow' })}
)}
{shouldShowMarketplaceFooter && ( )}
) } export default AllStartBlocks