mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 03:36:36 +08:00
feat(workflow): polish featured tools recommendations
This commit is contained in:
parent
d99644237b
commit
77e9bae3ff
@ -25,13 +25,12 @@ import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general'
|
|||||||
import { useGetLanguage } from '@/context/i18n'
|
import { useGetLanguage } from '@/context/i18n'
|
||||||
import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list'
|
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 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 { PluginCategoryEnum } from '../../plugins/types'
|
||||||
import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
|
import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
|
||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
import RAGToolSuggestions from './rag-tool-suggestions'
|
import RAGToolSuggestions from './rag-tool-suggestions'
|
||||||
import FeaturedTools from './featured-tools'
|
import FeaturedTools from './featured-tools'
|
||||||
import { useCheckInstalled, useRecommendedMarketplacePlugins } from '@/service/use-plugins'
|
|
||||||
import { useInvalidateAllBuiltInTools } from '@/service/use-tools'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
|
||||||
type AllToolsProps = {
|
type AllToolsProps = {
|
||||||
@ -50,6 +49,12 @@ type AllToolsProps = {
|
|||||||
canChooseMCPTool?: boolean
|
canChooseMCPTool?: boolean
|
||||||
onTagsChange?: Dispatch<SetStateAction<string[]>>
|
onTagsChange?: Dispatch<SetStateAction<string[]>>
|
||||||
isInRAGPipeline?: boolean
|
isInRAGPipeline?: boolean
|
||||||
|
featuredPlugins?: Plugin[]
|
||||||
|
featuredLoading?: boolean
|
||||||
|
featuredInstalledPluginIds?: Set<string>
|
||||||
|
featuredInstallLoading?: boolean
|
||||||
|
showFeatured?: boolean
|
||||||
|
onFeaturedInstallSuccess?: () => Promise<void> | void
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TAGS: AllToolsProps['tags'] = []
|
const DEFAULT_TAGS: AllToolsProps['tags'] = []
|
||||||
@ -70,6 +75,12 @@ const AllTools = ({
|
|||||||
canChooseMCPTool,
|
canChooseMCPTool,
|
||||||
onTagsChange,
|
onTagsChange,
|
||||||
isInRAGPipeline = false,
|
isInRAGPipeline = false,
|
||||||
|
featuredPlugins = [],
|
||||||
|
featuredLoading = false,
|
||||||
|
featuredInstalledPluginIds = new Set<string>(),
|
||||||
|
featuredInstallLoading = false,
|
||||||
|
showFeatured = false,
|
||||||
|
onFeaturedInstallSuccess,
|
||||||
}: AllToolsProps) => {
|
}: AllToolsProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const language = useGetLanguage()
|
const language = useGetLanguage()
|
||||||
@ -149,26 +160,6 @@ const AllTools = ({
|
|||||||
} = useMarketplacePlugins()
|
} = useMarketplacePlugins()
|
||||||
|
|
||||||
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
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(() => {
|
useEffect(() => {
|
||||||
if (!enable_marketplace) return
|
if (!enable_marketplace) return
|
||||||
@ -189,12 +180,12 @@ const AllTools = ({
|
|||||||
const hasToolsContent = tools.length > 0
|
const hasToolsContent = tools.length > 0
|
||||||
const hasPluginContent = enable_marketplace && notInstalledPlugins.length > 0
|
const hasPluginContent = enable_marketplace && notInstalledPlugins.length > 0
|
||||||
const shouldShowEmptyState = hasFilter && !hasToolsContent && !hasPluginContent
|
const shouldShowEmptyState = hasFilter && !hasToolsContent && !hasPluginContent
|
||||||
const shouldShowFeatured = enable_marketplace
|
const shouldShowFeatured = showFeatured
|
||||||
|
&& enable_marketplace
|
||||||
|
&& !isInRAGPipeline
|
||||||
&& activeTab === ToolTypeEnum.All
|
&& activeTab === ToolTypeEnum.All
|
||||||
&& !hasFilter
|
&& !hasFilter
|
||||||
&& !isLoadingRecommended
|
&& (featuredLoading || featuredPlugins.length > 0)
|
||||||
&& !isInRAGPipeline
|
|
||||||
&& recommendedPlugins.length > 0
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('min-w-[400px] max-w-[500px]', className)}>
|
<div className={cn('min-w-[400px] max-w-[500px]', className)}>
|
||||||
@ -235,17 +226,16 @@ const AllTools = ({
|
|||||||
)}
|
)}
|
||||||
{shouldShowFeatured && (
|
{shouldShowFeatured && (
|
||||||
<FeaturedTools
|
<FeaturedTools
|
||||||
plugins={recommendedPlugins}
|
plugins={featuredPlugins}
|
||||||
providerMap={providerMap}
|
providerMap={providerMap}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
selectedTools={selectedTools}
|
selectedTools={selectedTools}
|
||||||
canChooseMCPTool={canChooseMCPTool}
|
canChooseMCPTool={canChooseMCPTool}
|
||||||
installedPluginIds={installedPluginIds}
|
installedPluginIds={featuredInstalledPluginIds}
|
||||||
loadingInstalledStatus={loadingRecommendedInstallStatus}
|
loadingInstalledStatus={featuredInstallLoading}
|
||||||
isLoading={isLoadingRecommended}
|
isLoading={featuredLoading}
|
||||||
onInstallSuccess={async () => {
|
onInstallSuccess={async () => {
|
||||||
invalidateBuiltInTools()
|
await onFeaturedInstallSuccess?.()
|
||||||
await installedCheck.refetch()
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Dispatch, FC, SetStateAction } from 'react'
|
import type { Dispatch, FC, SetStateAction } from 'react'
|
||||||
import { memo } 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 {
|
import type {
|
||||||
BlockEnum,
|
BlockEnum,
|
||||||
NodeDefault,
|
NodeDefault,
|
||||||
@ -13,6 +13,8 @@ import AllStartBlocks from './all-start-blocks'
|
|||||||
import AllTools from './all-tools'
|
import AllTools from './all-tools'
|
||||||
import DataSources from './data-sources'
|
import DataSources from './data-sources'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { useFeaturedToolsRecommendations } from '@/service/use-plugins'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
export type TabsProps = {
|
export type TabsProps = {
|
||||||
activeTab: TabsEnum
|
activeTab: TabsEnum
|
||||||
@ -53,6 +55,16 @@ const Tabs: FC<TabsProps> = ({
|
|||||||
const { data: customTools } = useAllCustomTools()
|
const { data: customTools } = useAllCustomTools()
|
||||||
const { data: workflowTools } = useAllWorkflowTools()
|
const { data: workflowTools } = useAllWorkflowTools()
|
||||||
const { data: mcpTools } = useAllMCPTools()
|
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 (
|
return (
|
||||||
<div onClick={e => e.stopPropagation()}>
|
<div onClick={e => e.stopPropagation()}>
|
||||||
@ -127,7 +139,16 @@ const Tabs: FC<TabsProps> = ({
|
|||||||
mcpTools={mcpTools || []}
|
mcpTools={mcpTools || []}
|
||||||
canChooseMCPTool
|
canChooseMCPTool
|
||||||
onTagsChange={onTagsChange}
|
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()
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,9 @@ import {
|
|||||||
} from '@/service/tools'
|
} from '@/service/tools'
|
||||||
import type { CustomCollectionBackend } from '@/app/components/tools/types'
|
import type { CustomCollectionBackend } from '@/app/components/tools/types'
|
||||||
import Toast from '@/app/components/base/toast'
|
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'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -61,11 +63,21 @@ const ToolPicker: FC<Props> = ({
|
|||||||
const [searchText, setSearchText] = useState('')
|
const [searchText, setSearchText] = useState('')
|
||||||
const [tags, setTags] = useState<string[]>([])
|
const [tags, setTags] = useState<string[]>([])
|
||||||
|
|
||||||
|
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||||
const { data: buildInTools } = useAllBuiltInTools()
|
const { data: buildInTools } = useAllBuiltInTools()
|
||||||
const { data: customTools } = useAllCustomTools()
|
const { data: customTools } = useAllCustomTools()
|
||||||
const invalidateCustomTools = useInvalidateAllCustomTools()
|
const invalidateCustomTools = useInvalidateAllCustomTools()
|
||||||
const { data: workflowTools } = useAllWorkflowTools()
|
const { data: workflowTools } = useAllWorkflowTools()
|
||||||
const { data: mcpTools } = useAllMCPTools()
|
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(() => {
|
const { builtinToolList, customToolList, workflowToolList } = useMemo(() => {
|
||||||
if (scope === 'plugins') {
|
if (scope === 'plugins') {
|
||||||
@ -179,6 +191,15 @@ const ToolPicker: FC<Props> = ({
|
|||||||
selectedTools={selectedTools}
|
selectedTools={selectedTools}
|
||||||
canChooseMCPTool={canChooseMCPTool}
|
canChooseMCPTool={canChooseMCPTool}
|
||||||
onTagsChange={setTags}
|
onTagsChange={setTags}
|
||||||
|
featuredPlugins={featuredPlugins}
|
||||||
|
featuredLoading={isFeaturedLoading}
|
||||||
|
featuredInstalledPluginIds={featuredInstalledIds}
|
||||||
|
featuredInstallLoading={featuredInstallLoading}
|
||||||
|
showFeatured={scope === 'all' && enable_marketplace}
|
||||||
|
onFeaturedInstallSuccess={async () => {
|
||||||
|
invalidateBuiltInTools()
|
||||||
|
await refetchFeaturedInstallStatus()
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</PortalToFollowElemContent>
|
</PortalToFollowElemContent>
|
||||||
|
|||||||
@ -283,6 +283,7 @@ const translation = {
|
|||||||
'pluginByAuthor': 'By {{author}}',
|
'pluginByAuthor': 'By {{author}}',
|
||||||
'usePlugin': 'Select tool',
|
'usePlugin': 'Select tool',
|
||||||
'hideActions': 'Hide tools',
|
'hideActions': 'Hide tools',
|
||||||
|
'noFeaturedPlugins': 'Discover more tools in Marketplace',
|
||||||
},
|
},
|
||||||
blocks: {
|
blocks: {
|
||||||
'start': 'User Input',
|
'start': 'User Input',
|
||||||
|
|||||||
@ -269,6 +269,7 @@ const translation = {
|
|||||||
'pluginByAuthor': '来自 {{author}}',
|
'pluginByAuthor': '来自 {{author}}',
|
||||||
'usePlugin': '选择工具',
|
'usePlugin': '选择工具',
|
||||||
'hideActions': '收起工具',
|
'hideActions': '收起工具',
|
||||||
|
'noFeaturedPlugins': '前往插件市场查看更多工具',
|
||||||
},
|
},
|
||||||
blocks: {
|
blocks: {
|
||||||
'start': '用户输入',
|
'start': '用户输入',
|
||||||
@ -358,7 +359,7 @@ const translation = {
|
|||||||
panel: {
|
panel: {
|
||||||
userInputField: '用户输入字段',
|
userInputField: '用户输入字段',
|
||||||
changeBlock: '更改节点',
|
changeBlock: '更改节点',
|
||||||
helpLink: '帮助文档',
|
helpLink: '帮助链接',
|
||||||
about: '关于',
|
about: '关于',
|
||||||
createdBy: '作者',
|
createdBy: '作者',
|
||||||
nextStep: '下一步',
|
nextStep: '下一步',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect } from 'react'
|
import { useCallback, useEffect, useMemo } from 'react'
|
||||||
import type {
|
import type {
|
||||||
FormOption,
|
FormOption,
|
||||||
ModelProvider,
|
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) => {
|
export const useInstalledPluginList = (disable?: boolean, pageSize = 100) => {
|
||||||
const fetchPlugins = async ({ pageParam = 1 }) => {
|
const fetchPlugins = async ({ pageParam = 1 }) => {
|
||||||
const response = await get<InstalledPluginListWithTotalResponse>(
|
const response = await get<InstalledPluginListWithTotalResponse>(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user