'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