import type { Dispatch, RefObject, SetStateAction, } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import type { BlockEnum, ToolWithProvider, } from '../types' import type { ToolDefaultValue, ToolValue } from './types' import { ToolTypeEnum } from './types' import Tools from './tools' import { useToolTabs } from './hooks' import ViewTypeSelect, { ViewType } from './view-type-select' import cn from '@/utils/classnames' import Button from '@/app/components/base/button' import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general' import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list' import PluginList, { type ListProps } from '@/app/components/workflow/block-selector/market-place-plugin/list' import type { Plugin } from '../../plugins/types' import { PluginCategoryEnum } from '../../plugins/types' import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' import { useGlobalPublicStore } from '@/context/global-public-context' import RAGToolRecommendations from './rag-tool-recommendations' import FeaturedTools from './featured-tools' import Link from 'next/link' import Divider from '@/app/components/base/divider' import { RiArrowRightUpLine } from '@remixicon/react' import { getMarketplaceUrl } from '@/utils/var' import { useGetLanguage } from '@/context/i18n' import type { OnSelectBlock } from '@/app/components/workflow/types' const marketplaceFooterClassName = 'system-sm-medium z-10 flex h-8 flex-none cursor-pointer items-center rounded-b-lg border-[0.5px] border-t border-components-panel-border bg-components-panel-bg-blur px-4 py-1 text-text-accent-light-mode-only shadow-lg' type AllToolsProps = { className?: string toolContentClassName?: string searchText: string tags: ListProps['tags'] buildInTools: ToolWithProvider[] customTools: ToolWithProvider[] workflowTools: ToolWithProvider[] mcpTools: ToolWithProvider[] onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void canNotSelectMultiple?: boolean onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void selectedTools?: ToolValue[] canChooseMCPTool?: boolean onTagsChange?: Dispatch> isInRAGPipeline?: boolean featuredPlugins?: Plugin[] featuredLoading?: boolean showFeatured?: boolean onFeaturedInstallSuccess?: () => Promise | void } const DEFAULT_TAGS: AllToolsProps['tags'] = [] const AllTools = ({ className, toolContentClassName, searchText, tags = DEFAULT_TAGS, onSelect, canNotSelectMultiple, onSelectMultiple, buildInTools, workflowTools, customTools, mcpTools = [], selectedTools, canChooseMCPTool, onTagsChange, isInRAGPipeline = false, featuredPlugins = [], featuredLoading = false, showFeatured = false, onFeaturedInstallSuccess, }: AllToolsProps) => { const { t } = useTranslation() const language = useGetLanguage() const tabs = useToolTabs() const [activeTab, setActiveTab] = useState(ToolTypeEnum.All) const [activeView, setActiveView] = useState(ViewType.flat) const trimmedSearchText = searchText.trim() const hasSearchText = trimmedSearchText.length > 0 const hasTags = tags.length > 0 const hasFilter = hasSearchText || hasTags const isMatchingKeywords = (text: string, keywords: string) => { return text.toLowerCase().includes(keywords.toLowerCase()) } const allProviders = useMemo(() => [...buildInTools, ...customTools, ...workflowTools, ...mcpTools], [buildInTools, customTools, workflowTools, mcpTools]) const providerMap = useMemo(() => { const map = new Map() allProviders.forEach((provider) => { const key = provider.plugin_id || provider.id if (key) map.set(key, provider) }) return map }, [allProviders]) const tools = useMemo(() => { let mergedTools: ToolWithProvider[] = [] if (activeTab === ToolTypeEnum.All) mergedTools = [...buildInTools, ...customTools, ...workflowTools, ...mcpTools] if (activeTab === ToolTypeEnum.BuiltIn) mergedTools = buildInTools if (activeTab === ToolTypeEnum.Custom) mergedTools = customTools if (activeTab === ToolTypeEnum.Workflow) mergedTools = workflowTools if (activeTab === ToolTypeEnum.MCP) mergedTools = mcpTools const normalizedSearch = trimmedSearchText.toLowerCase() const getLocalizedText = (text?: Record | null) => { if (!text) return '' if (text[language]) return text[language] if (text['en-US']) return text['en-US'] const firstValue = Object.values(text).find(Boolean) return firstValue || '' } if (!hasFilter || !normalizedSearch) return mergedTools.filter(toolWithProvider => toolWithProvider.tools.length > 0) return mergedTools.reduce((acc, toolWithProvider) => { const providerLabel = getLocalizedText(toolWithProvider.label) const providerMatches = [ toolWithProvider.name, providerLabel, ].some(text => isMatchingKeywords(text || '', normalizedSearch)) if (providerMatches) { if (toolWithProvider.tools.length > 0) acc.push(toolWithProvider) return acc } const matchedTools = toolWithProvider.tools.filter((tool) => { const toolLabel = getLocalizedText(tool.label) return [ tool.name, toolLabel, ].some(text => isMatchingKeywords(text || '', normalizedSearch)) }) if (matchedTools.length > 0) { acc.push({ ...toolWithProvider, tools: matchedTools, }) } return acc }, []) }, [activeTab, buildInTools, customTools, workflowTools, mcpTools, trimmedSearchText, hasFilter, language]) const { queryPluginsWithDebounced: fetchPlugins, plugins: notInstalledPlugins = [], } = useMarketplacePlugins() const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) useEffect(() => { if (!enable_marketplace) return if (hasFilter) { fetchPlugins({ query: searchText, tags, category: PluginCategoryEnum.tool, }) } }, [searchText, tags, enable_marketplace, hasFilter, fetchPlugins]) const pluginRef = useRef(null) const wrapElemRef = useRef(null) const isSupportGroupView = [ToolTypeEnum.All, ToolTypeEnum.BuiltIn].includes(activeTab) const isShowRAGRecommendations = isInRAGPipeline && activeTab === ToolTypeEnum.All && !hasFilter const hasToolsListContent = tools.length > 0 || isShowRAGRecommendations const hasPluginContent = enable_marketplace && notInstalledPlugins.length > 0 const shouldShowEmptyState = hasFilter && !hasToolsListContent && !hasPluginContent const shouldShowFeatured = showFeatured && enable_marketplace && !isInRAGPipeline && activeTab === ToolTypeEnum.All && !hasFilter const shouldShowMarketplaceFooter = enable_marketplace && !hasFilter const handleRAGSelect = useCallback((type, pluginDefaultValue) => { if (!pluginDefaultValue) return onSelect(type, pluginDefaultValue as ToolDefaultValue) }, [onSelect]) return (
{ tabs.map(tab => (
setActiveTab(tab.key)} > {tab.name}
)) }
{isSupportGroupView && ( )}
{isShowRAGRecommendations && onTagsChange && ( )} {shouldShowFeatured && ( <> { await onFeaturedInstallSuccess?.() }} />
)} {hasToolsListContent && ( <>
{t('tools.allTools')}
)} {enable_marketplace && ( } list={notInstalledPlugins} searchText={searchText} toolContentClassName={toolContentClassName} tags={tags} hideFindMoreFooter /> )}
{shouldShowEmptyState && (
{t('workflow.tabs.noPluginsFound')}
)}
{shouldShowMarketplaceFooter && ( {t('plugin.findMoreInMarketplace')} )}
) } export default AllTools