'use client' import type { ReactNode, RefObject } from 'react' import type { ToolCategory } from '@/app/components/integrations/routes' import type { ToolsContentInset } from '@/app/components/tools/content-inset' import type { Collection } from '@/app/components/tools/types' import { cn } from '@langgenius/dify-ui/cn' import { ScrollAreaContent, ScrollAreaRoot, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport, } from '@langgenius/dify-ui/scroll-area' import { useSuspenseQuery } from '@tanstack/react-query' import { useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { isSearchResultEmpty } from '@/app/components/base/search-input/search-state' import { useTags } from '@/app/components/plugins/hooks' import Empty from '@/app/components/plugins/marketplace/empty' import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' import { usePluginSettingsAccess } from '@/app/components/plugins/plugin-page/use-reference-setting' import { toolsContentInsetClassNames, toolsUnifiedContentFrameClassName, } from '@/app/components/tools/content-inset' import { useCanManageMCP, useCanManageTools, } from '@/app/components/tools/hooks/use-tool-permissions' import Marketplace from '@/app/components/tools/marketplace' import MCPList from '@/app/components/tools/mcp' import ProviderDetail from '@/app/components/tools/provider/detail' import { ToolProviderGrid } from '@/app/components/tools/tool-provider-grid' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useCheckInstalled, useInvalidateInstalledPluginList } from '@/service/use-plugins' import { useAllToolProviders } from '@/service/use-tools' import { useToolMarketplacePanel } from './hooks/use-tool-marketplace-panel' import { useToolProviderCategory } from './hooks/use-tool-provider-category' import ToolProviderCreateAction from './tool-provider-create-action' import { ToolProviderToolbar } from './tool-provider-toolbar' type ProviderListProps = { category?: ToolCategory contentInset?: ToolsContentInset layout?: (parts: { body: ReactNode; toolbar: ReactNode }) => ReactNode } type BuiltinMarketplacePanelProps = { containerRef: RefObject contentInset: ToolsContentInset keywords: string tagFilterValue: string[] } const BuiltinMarketplacePanel = ({ containerRef, contentInset, keywords, tagFilterValue, }: BuiltinMarketplacePanelProps) => { const { isMarketplaceArrowVisible, marketplaceContext, showMarketplacePanel, toolListTailRef } = useToolMarketplacePanel({ containerRef, keywords, tagFilterValue, }) return ( <>
) } const ProviderList = ({ category, contentInset = 'default', layout }: ProviderListProps) => { // const searchParams = useSearchParams() // searchParams.get('category') === 'workflow' const { t } = useTranslation() const { getTagLabel } = useTags() const { canDeletePlugin, canSetPluginPreferences, canUpdatePlugin } = usePluginSettingsAccess() const canManageTools = useCanManageTools() const canManageMCP = useCanManageMCP() const { data: enable_marketplace } = useSuspenseQuery({ ...systemFeaturesQueryOptions(), select: (s) => s.enable_marketplace, }) const { activeTab, handleCategoryChange, isRouteCategory } = useToolProviderCategory(category) const contentPaddingClassName = toolsContentInsetClassNames[contentInset] const toolListFrameClassName = cn(contentPaddingClassName, toolsUnifiedContentFrameClassName) const showToolsUpdateSetting = activeTab === 'builtin' && canSetPluginPreferences const showLabelFilter = activeTab === 'builtin' const options = [ { value: 'builtin', text: t(($) => $['type.builtIn'], { ns: 'tools' }) }, { value: 'api', text: t(($) => $['type.custom'], { ns: 'tools' }) }, { value: 'workflow', text: t(($) => $['type.workflow'], { ns: 'tools' }) }, { value: 'mcp', text: 'MCP' }, ] const [tagFilterValue, setTagFilterValue] = useState([]) const handleTagsChange = (value: string[]) => { setTagFilterValue(value) } const [keywords, setKeywords] = useState('') const handleKeywordsChange = (value: string) => { setKeywords(value) } const [createdMCPProviderId, setCreatedMCPProviderId] = useState() const handleMCPProviderCreated = useCallback((providerId: string) => { setCreatedMCPProviderId(providerId) }, []) const handleCreatedMCPProviderHandled = useCallback(() => { setCreatedMCPProviderId(undefined) }, []) const { data: collectionList = [], isLoading: isCollectionListLoading, refetch, } = useAllToolProviders() const activeTabCollectionList = useMemo(() => { return collectionList.filter((collection) => collection.type === activeTab) }, [activeTab, collectionList]) const hasCategoryCollections = activeTabCollectionList.length > 0 const shouldShowCustomToolCreateCard = canManageTools && !(activeTab === 'api' && !isCollectionListLoading && hasCategoryCollections) const shouldShowMCPCreateCard = canManageMCP && !(activeTab === 'mcp' && hasCategoryCollections) const shouldShowToolbarCreateAction = (activeTab === 'mcp' && canManageMCP && (hasCategoryCollections || isRouteCategory)) || (activeTab === 'api' && canManageTools && !isCollectionListLoading && hasCategoryCollections) const filteredCollectionList = useMemo(() => { return activeTabCollectionList.filter((collection) => { if ( showLabelFilter && tagFilterValue.length > 0 && (!collection.labels || collection.labels.every((label) => !tagFilterValue.includes(label))) ) return false if (keywords) return Object.values(collection.label).some((value) => value.toLowerCase().includes(keywords.toLowerCase()), ) return true }) }, [activeTabCollectionList, showLabelFilter, tagFilterValue, keywords]) const isFilteringCollections = !!keywords.trim() || (showLabelFilter && tagFilterValue.length > 0) const isCollectionSearchEmpty = isSearchResultEmpty({ hasActiveFilter: isFilteringCollections, isLoading: isCollectionListLoading, resultCount: filteredCollectionList.length, sourceCount: activeTabCollectionList.length, }) const [currentProviderId, setCurrentProviderId] = useState() const currentProvider = useMemo(() => { return filteredCollectionList.find((collection) => collection.id === currentProviderId) }, [currentProviderId, filteredCollectionList]) const { data: checkedInstalledData } = useCheckInstalled({ pluginIds: currentProvider?.plugin_id ? [currentProvider.plugin_id] : [], enabled: !!currentProvider?.plugin_id, }) const invalidateInstalledPluginList = useInvalidateInstalledPluginList() const currentPluginDetail = useMemo(() => { return checkedInstalledData?.plugins?.[0] }, [checkedInstalledData]) const containerRef = useRef(null) const toolbar = ( ) : undefined } onCategoryChange={(state) => handleCategoryChange(state, () => setCurrentProviderId(undefined)) } onKeywordsChange={handleKeywordsChange} onTagsChange={handleTagsChange} /> ) const body = ( <>
$['menus.tools'], { ns: 'common' })} className="overscroll-contain" role="region" > {activeTab !== 'mcp' && ( 0} isLoading={isCollectionListLoading} useIntegrationsCard={contentInset === 'compact'} isSearchResultEmpty={isCollectionSearchEmpty} showCreateCard={shouldShowCustomToolCreateCard} onRefreshData={refetch} onSelectProvider={setCurrentProviderId} /> )} {!isCollectionListLoading && !activeTabCollectionList.length && activeTab === 'builtin' && ( $.noTools, { ns: 'tools' })} className={cn('h-[224px] shrink-0', toolListFrameClassName)} /> )} {isCollectionSearchEmpty && activeTab === 'builtin' && (
)} {enable_marketplace && activeTab === 'builtin' && ( )} {activeTab === 'mcp' && ( )}
{currentProvider && !currentProvider.plugin_id && ( setCurrentProviderId(undefined)} onRefreshData={refetch} /> )} invalidateInstalledPluginList()} onHide={() => setCurrentProviderId(undefined)} canDeletePlugin={canDeletePlugin} canUpdatePlugin={canUpdatePlugin} /> ) if (layout) return layout({ body, toolbar }) return ( <> {toolbar} {body} ) } ProviderList.displayName = 'ToolProviderList' export default ProviderList