diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index e071c665ae..e7643dce55 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -25,13 +25,12 @@ import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general' import { useGetLanguage } from '@/context/i18n' 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 RAGToolSuggestions from './rag-tool-suggestions' import FeaturedTools from './featured-tools' -import { useCheckInstalled, useRecommendedMarketplacePlugins } from '@/service/use-plugins' -import { useInvalidateAllBuiltInTools } from '@/service/use-tools' import Link from 'next/link' type AllToolsProps = { @@ -50,6 +49,12 @@ type AllToolsProps = { canChooseMCPTool?: boolean onTagsChange?: Dispatch> isInRAGPipeline?: boolean + featuredPlugins?: Plugin[] + featuredLoading?: boolean + featuredInstalledPluginIds?: Set + featuredInstallLoading?: boolean + showFeatured?: boolean + onFeaturedInstallSuccess?: () => Promise | void } const DEFAULT_TAGS: AllToolsProps['tags'] = [] @@ -70,6 +75,12 @@ const AllTools = ({ canChooseMCPTool, onTagsChange, isInRAGPipeline = false, + featuredPlugins = [], + featuredLoading = false, + featuredInstalledPluginIds = new Set(), + featuredInstallLoading = false, + showFeatured = false, + onFeaturedInstallSuccess, }: AllToolsProps) => { const { t } = useTranslation() const language = useGetLanguage() @@ -149,26 +160,6 @@ const AllTools = ({ } = useMarketplacePlugins() const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) - const { - data: recommendedPlugins = [], - isLoading: isLoadingRecommended, - } = useRecommendedMarketplacePlugins({ - enabled: enable_marketplace && !isInRAGPipeline, - }) - const recommendedPluginIds = useMemo( - () => recommendedPlugins.map(plugin => plugin.plugin_id), - [recommendedPlugins], - ) - const installedCheck = useCheckInstalled({ - pluginIds: recommendedPluginIds, - enabled: recommendedPluginIds.length > 0, - }) - const installedPluginIds = useMemo( - () => new Set(installedCheck.data?.plugins.map(plugin => plugin.plugin_id) ?? []), - [installedCheck.data], - ) - const loadingRecommendedInstallStatus = installedCheck.isLoading || installedCheck.isRefetching - const invalidateBuiltInTools = useInvalidateAllBuiltInTools() useEffect(() => { if (!enable_marketplace) return @@ -189,12 +180,12 @@ const AllTools = ({ const hasToolsContent = tools.length > 0 const hasPluginContent = enable_marketplace && notInstalledPlugins.length > 0 const shouldShowEmptyState = hasFilter && !hasToolsContent && !hasPluginContent - const shouldShowFeatured = enable_marketplace + const shouldShowFeatured = showFeatured + && enable_marketplace + && !isInRAGPipeline && activeTab === ToolTypeEnum.All && !hasFilter - && !isLoadingRecommended - && !isInRAGPipeline - && recommendedPlugins.length > 0 + && (featuredLoading || featuredPlugins.length > 0) return (
@@ -235,17 +226,16 @@ const AllTools = ({ )} {shouldShowFeatured && ( { - invalidateBuiltInTools() - await installedCheck.refetch() + await onFeaturedInstallSuccess?.() }} /> )} diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index 871ab3405a..444ddcd419 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -1,6 +1,6 @@ import type { Dispatch, FC, SetStateAction } from 'react' import { memo } from 'react' -import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools } from '@/service/use-tools' +import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools } from '@/service/use-tools' import type { BlockEnum, NodeDefault, @@ -13,6 +13,8 @@ import AllStartBlocks from './all-start-blocks' import AllTools from './all-tools' import DataSources from './data-sources' import cn from '@/utils/classnames' +import { useFeaturedToolsRecommendations } from '@/service/use-plugins' +import { useGlobalPublicStore } from '@/context/global-public-context' export type TabsProps = { activeTab: TabsEnum @@ -53,6 +55,16 @@ const Tabs: FC = ({ const { data: customTools } = useAllCustomTools() const { data: workflowTools } = useAllWorkflowTools() const { data: mcpTools } = useAllMCPTools() + const invalidateBuiltInTools = useInvalidateAllBuiltInTools() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) + const inRAGPipeline = dataSources.length > 0 + const { + plugins: featuredPlugins = [], + isLoading: isFeaturedLoading, + installedIds: featuredInstalledIds, + installStatusLoading: featuredInstallLoading, + refetchInstallStatus: refetchFeaturedInstallStatus, + } = useFeaturedToolsRecommendations(enable_marketplace && !inRAGPipeline) return (
e.stopPropagation()}> @@ -127,7 +139,16 @@ const Tabs: FC = ({ mcpTools={mcpTools || []} canChooseMCPTool onTagsChange={onTagsChange} - isInRAGPipeline={dataSources.length > 0} + isInRAGPipeline={inRAGPipeline} + featuredPlugins={featuredPlugins} + featuredLoading={isFeaturedLoading} + featuredInstalledPluginIds={featuredInstalledIds} + featuredInstallLoading={featuredInstallLoading} + showFeatured={enable_marketplace && !inRAGPipeline} + onFeaturedInstallSuccess={async () => { + invalidateBuiltInTools() + await refetchFeaturedInstallStatus() + }} /> ) } diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index ae4b0d4f02..809a7b1cc9 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -23,7 +23,9 @@ import { } from '@/service/tools' import type { CustomCollectionBackend } from '@/app/components/tools/types' import Toast from '@/app/components/base/toast' -import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllCustomTools } from '@/service/use-tools' +import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools, useInvalidateAllCustomTools } from '@/service/use-tools' +import { useFeaturedToolsRecommendations } from '@/service/use-plugins' +import { useGlobalPublicStore } from '@/context/global-public-context' import cn from '@/utils/classnames' type Props = { @@ -61,11 +63,21 @@ const ToolPicker: FC = ({ const [searchText, setSearchText] = useState('') const [tags, setTags] = useState([]) + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const { data: buildInTools } = useAllBuiltInTools() const { data: customTools } = useAllCustomTools() const invalidateCustomTools = useInvalidateAllCustomTools() const { data: workflowTools } = useAllWorkflowTools() const { data: mcpTools } = useAllMCPTools() + const invalidateBuiltInTools = useInvalidateAllBuiltInTools() + + const { + plugins: featuredPlugins = [], + isLoading: isFeaturedLoading, + installedIds: featuredInstalledIds, + installStatusLoading: featuredInstallLoading, + refetchInstallStatus: refetchFeaturedInstallStatus, + } = useFeaturedToolsRecommendations(enable_marketplace) const { builtinToolList, customToolList, workflowToolList } = useMemo(() => { if (scope === 'plugins') { @@ -179,6 +191,15 @@ const ToolPicker: FC = ({ selectedTools={selectedTools} canChooseMCPTool={canChooseMCPTool} onTagsChange={setTags} + featuredPlugins={featuredPlugins} + featuredLoading={isFeaturedLoading} + featuredInstalledPluginIds={featuredInstalledIds} + featuredInstallLoading={featuredInstallLoading} + showFeatured={scope === 'all' && enable_marketplace} + onFeaturedInstallSuccess={async () => { + invalidateBuiltInTools() + await refetchFeaturedInstallStatus() + }} />
diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 30ea04d6ae..608e8dc420 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -283,6 +283,7 @@ const translation = { 'pluginByAuthor': 'By {{author}}', 'usePlugin': 'Select tool', 'hideActions': 'Hide tools', + 'noFeaturedPlugins': 'Discover more tools in Marketplace', }, blocks: { 'start': 'User Input', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 1d2507f969..6970c2ee16 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -269,6 +269,7 @@ const translation = { 'pluginByAuthor': '来自 {{author}}', 'usePlugin': '选择工具', 'hideActions': '收起工具', + 'noFeaturedPlugins': '前往插件市场查看更多工具', }, blocks: { 'start': '用户输入', @@ -358,7 +359,7 @@ const translation = { panel: { userInputField: '用户输入字段', changeBlock: '更改节点', - helpLink: '帮助文档', + helpLink: '帮助链接', about: '关于', createdBy: '作者', nextStep: '下一步', diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index fcd626b58e..8eb7cbb0da 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect } from 'react' +import { useCallback, useEffect, useMemo } from 'react' import type { FormOption, ModelProvider, @@ -96,6 +96,36 @@ export const useRecommendedMarketplacePlugins = ({ }) } +export const useFeaturedToolsRecommendations = (enabled: boolean, limit = 15) => { + const { + data: plugins = [], + isLoading, + } = useRecommendedMarketplacePlugins({ + enabled, + limit, + }) + const pluginIds = useMemo( + () => plugins.map(plugin => plugin.plugin_id), + [plugins], + ) + const installedCheck = useCheckInstalled({ + pluginIds, + enabled: enabled && pluginIds.length > 0, + }) + const installedIds = useMemo( + () => new Set(installedCheck.data?.plugins.map(plugin => plugin.plugin_id) ?? []), + [installedCheck.data], + ) + const installStatusLoading = installedCheck.isLoading || installedCheck.isRefetching + return { + plugins, + isLoading, + installedIds, + installStatusLoading, + refetchInstallStatus: installedCheck.refetch, + } +} + export const useInstalledPluginList = (disable?: boolean, pageSize = 100) => { const fetchPlugins = async ({ pageParam = 1 }) => { const response = await get(