feat: Introduce RAG tool recommendations and refactor related components for improved plugin management

This commit is contained in:
twwu 2025-10-22 15:01:29 +08:00
parent f909040567
commit 572abae37a
9 changed files with 191 additions and 34 deletions

View File

@ -2,7 +2,7 @@ import { useModelList } from '@/app/components/header/account-setting/model-prov
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useProviderContext } from '@/context/provider-context'
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
import { useInvalidateAllBuiltInTools, useInvalidateAllToolProviders } from '@/service/use-tools'
import { useInvalidateAllBuiltInTools, useInvalidateAllToolProviders, useInvalidateRAGRecommendedPlugins } from '@/service/use-tools'
import { useInvalidateStrategyProviders } from '@/service/use-strategy'
import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../../types'
import { PluginType } from '../../types'
@ -23,6 +23,8 @@ const useRefreshPluginList = () => {
const invalidateDataSourceListAuth = useInvalidDataSourceListAuth()
const invalidateStrategyProviders = useInvalidateStrategyProviders()
const invalidateRAGRecommendedPlugins = useInvalidateRAGRecommendedPlugins()
return {
refreshPluginList: (manifest?: PluginManifestInMarket | Plugin | PluginDeclaration | null, refreshAllType?: boolean) => {
// installed list
@ -32,6 +34,7 @@ const useRefreshPluginList = () => {
if ((manifest && PluginType.tool.includes(manifest.category)) || refreshAllType) {
invalidateAllToolProviders()
invalidateAllBuiltInTools()
invalidateRAGRecommendedPlugins()
// TODO: update suggested tools. It's a function in hook useMarketplacePlugins,handleUpdatePlugins
}

View File

@ -25,7 +25,7 @@ import PluginList, { type ListProps } from '@/app/components/workflow/block-sele
import { PluginType } from '../../plugins/types'
import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
import { useGlobalPublicStore } from '@/context/global-public-context'
import RAGToolSuggestions from './rag-tool-suggestions'
import RAGToolRecommendations from './rag-tool-recommendations'
type AllToolsProps = {
className?: string
@ -148,7 +148,7 @@ const AllTools = ({
onScroll={pluginRef.current?.handleScroll}
>
{isShowRAGRecommendations && (
<RAGToolSuggestions
<RAGToolRecommendations
viewType={isSupportGroupView ? activeView : ViewType.flat}
onSelect={onSelect}
onTagsChange={onTagsChange}

View File

@ -3,7 +3,7 @@ import React, { useEffect, useImperativeHandle, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import useStickyScroll, { ScrollPosition } from '../use-sticky-scroll'
import Item from './item'
import type { Plugin } from '@/app/components/plugins/types.ts'
import type { Plugin } from '@/app/components/plugins/types'
import cn from '@/utils/classnames'
import Link from 'next/link'
import { RiArrowRightUpLine, RiSearchLine } from '@remixicon/react'

View File

@ -1,27 +1,27 @@
import type { Dispatch, SetStateAction } from 'react'
import React, { useCallback, useMemo } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import type { OnSelectBlock } from '../types'
import Tools from './tools'
import { ToolTypeEnum } from './types'
import type { ViewType } from './view-type-select'
import type { OnSelectBlock } from '@/app/components/workflow/types'
import type { ViewType } from '@/app/components/workflow/block-selector/view-type-select'
import { RiMoreLine } from '@remixicon/react'
import Loading from '@/app/components/base/loading'
import Link from 'next/link'
import { getMarketplaceUrl } from '@/utils/var'
import { useRAGRecommendedPlugins } from '@/service/use-tools'
import List from './list'
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
type RAGToolSuggestionsProps = {
type RAGToolRecommendationsProps = {
viewType: ViewType
onSelect: OnSelectBlock
onTagsChange: Dispatch<SetStateAction<string[]>>
}
const RAGToolSuggestions: React.FC<RAGToolSuggestionsProps> = ({
const RAGToolRecommendations = ({
viewType,
onSelect,
onTagsChange,
}) => {
}: RAGToolRecommendationsProps) => {
const { t } = useTranslation()
const {
@ -31,7 +31,13 @@ const RAGToolSuggestions: React.FC<RAGToolSuggestionsProps> = ({
const recommendedPlugins = useMemo(() => {
if (ragRecommendedPlugins)
return [...ragRecommendedPlugins.installed_recommended_plugins]
return ragRecommendedPlugins.installed_recommended_plugins
return []
}, [ragRecommendedPlugins])
const unInstalledPlugins = useMemo(() => {
if (ragRecommendedPlugins)
return (ragRecommendedPlugins.uninstalled_recommended_plugins).map(getFormattedPlugin)
return []
}, [ragRecommendedPlugins])
@ -53,7 +59,7 @@ const RAGToolSuggestions: React.FC<RAGToolSuggestionsProps> = ({
<Loading type='app' />
</div>
)}
{!isFetchingRAGRecommendedPlugins && recommendedPlugins.length === 0 && (
{!isFetchingRAGRecommendedPlugins && recommendedPlugins.length === 0 && unInstalledPlugins.length === 0 && (
<p className='system-xs-regular px-3 py-1 text-text-tertiary'>
<Trans
i18nKey='pipeline.ragToolSuggestions.noRecommendationPluginsInstalled'
@ -70,16 +76,13 @@ const RAGToolSuggestions: React.FC<RAGToolSuggestionsProps> = ({
/>
</p>
)}
{!isFetchingRAGRecommendedPlugins && recommendedPlugins.length > 0 && (
{!isFetchingRAGRecommendedPlugins && (recommendedPlugins.length > 0 || unInstalledPlugins.length > 0) && (
<>
<Tools
className='p-0'
<List
tools={recommendedPlugins}
unInstalledPlugins={unInstalledPlugins}
onSelect={onSelect}
canNotSelectMultiple
toolType={ToolTypeEnum.All}
viewType={viewType}
hasSearchText={false}
/>
<div
className='flex cursor-pointer items-center gap-x-2 py-1 pl-3 pr-2'
@ -98,4 +101,4 @@ const RAGToolSuggestions: React.FC<RAGToolSuggestionsProps> = ({
)
}
export default React.memo(RAGToolSuggestions)
export default React.memo(RAGToolRecommendations)

View File

@ -0,0 +1,102 @@
import {
useMemo,
useRef,
} from 'react'
import type { BlockEnum, ToolWithProvider } from '../../types'
import type { ToolDefaultValue } from '../types'
import { ViewType } from '../view-type-select'
import { useGetLanguage } from '@/context/i18n'
import { groupItems } from '../index-bar'
import cn from '@/utils/classnames'
import ToolListTreeView from '../tool/tool-list-tree-view/list'
import ToolListFlatView from '../tool/tool-list-flat-view/list'
import UninstalledItem from './uninstalled-item'
import type { Plugin } from '@/app/components/plugins/types'
type ListProps = {
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
tools: ToolWithProvider[]
viewType: ViewType
unInstalledPlugins: Plugin[]
className?: string
}
const List = ({
onSelect,
tools,
viewType,
unInstalledPlugins,
className,
}: ListProps) => {
const language = useGetLanguage()
const isFlatView = viewType === ViewType.flat
const { letters, groups: withLetterAndGroupViewToolsData } = groupItems(tools, tool => tool.label[language][0])
const treeViewToolsData = useMemo(() => {
const result: Record<string, ToolWithProvider[]> = {}
Object.keys(withLetterAndGroupViewToolsData).forEach((letter) => {
Object.keys(withLetterAndGroupViewToolsData[letter]).forEach((groupName) => {
if (!result[groupName])
result[groupName] = []
result[groupName].push(...withLetterAndGroupViewToolsData[letter][groupName])
})
})
return result
}, [withLetterAndGroupViewToolsData])
const listViewToolData = useMemo(() => {
const result: ToolWithProvider[] = []
letters.forEach((letter) => {
Object.keys(withLetterAndGroupViewToolsData[letter]).forEach((groupName) => {
result.push(...withLetterAndGroupViewToolsData[letter][groupName].map((item) => {
return {
...item,
letter,
}
}))
})
})
return result
}, [withLetterAndGroupViewToolsData, letters])
const toolRefs = useRef({})
return (
<div className={cn('max-w-[100%] p-1', className)}>
{!!tools.length && (
isFlatView ? (
<ToolListFlatView
toolRefs={toolRefs}
letters={letters}
payload={listViewToolData}
isShowLetterIndex={false}
hasSearchText={false}
onSelect={onSelect}
canNotSelectMultiple
indexBar={null}
/>
) : (
<ToolListTreeView
payload={treeViewToolsData}
hasSearchText={false}
onSelect={onSelect}
canNotSelectMultiple
/>
)
)}
{
unInstalledPlugins.map((item) => {
return (
<UninstalledItem
key={item.plugin_id}
payload={item}
/>
)
})
}
</div>
)
}
export default List

View File

@ -0,0 +1,58 @@
'use client'
import React from 'react'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import type { Plugin } from '@/app/components/plugins/types'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import I18n from '@/context/i18n'
import { useBoolean } from 'ahooks'
import { BlockEnum } from '../../types'
import BlockIcon from '../../block-icon'
type UninstalledItemProps = {
payload: Plugin
}
const UninstalledItem = ({
payload,
}: UninstalledItemProps) => {
const { t } = useTranslation()
const { locale } = useContext(I18n)
const getLocalizedText = (obj: Record<string, string> | undefined) =>
obj?.[locale] || obj?.['en-US'] || obj?.en_US || ''
const [isShowInstallModal, {
setTrue: showInstallModal,
setFalse: hideInstallModal,
}] = useBoolean(false)
return (
<div className='flex h-8 items-center rounded-lg pl-3 pr-2 hover:bg-state-base-hover'>
<BlockIcon
className='shrink-0'
type={BlockEnum.Tool}
toolIcon={payload.icon}
/>
<div className='ml-2 flex w-0 grow items-center'>
<div className='system-sm-medium h-4 w-0 grow truncate leading-4 text-text-primary'>
{getLocalizedText(payload.label)}
</div>
<div
className='system-xs-medium cursor-pointer pl-1.5 text-components-button-secondary-accent-text'
onClick={showInstallModal}
>
{t('plugin.installAction')}
</div>
{isShowInstallModal && (
<InstallFromMarketplace
uniqueIdentifier={payload.latest_package_identifier}
manifest={payload}
onSuccess={hideInstallModal}
onClose={hideInstallModal}
/>
)}
</div>
</div>
)
}
export default React.memo(UninstalledItem)

View File

@ -30,7 +30,7 @@ type ToolsProps = {
canChooseMCPTool?: boolean
isShowRAGRecommendations?: boolean
}
const Blocks = ({
const Tools = ({
onSelect,
canNotSelectMultiple,
onSelectMultiple,
@ -146,4 +146,4 @@ const Blocks = ({
)
}
export default memo(Blocks)
export default memo(Tools)

View File

@ -19,7 +19,7 @@ import type {
} from '@/app/components/workflow/nodes/_base/components/error-handle/types'
import type { WorkflowRetryConfig } from '@/app/components/workflow/nodes/_base/components/retry/types'
import type { StructuredOutput } from '@/app/components/workflow/nodes/llm/types'
import type { PluginMeta } from '../plugins/types'
import type { Plugin, PluginMeta } from '@/app/components/plugins/types'
import type { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types'
import type { SchemaTypeDefinition } from '@/service/use-common'
@ -451,16 +451,9 @@ export type ToolWithProvider = Collection & {
meta: PluginMeta
}
export type UninstalledRecommendedPlugin = {
plugin_id: string
name: string
icon: string
plugin_unique_identifier: string
}
export type RAGRecommendedPlugins = {
installed_recommended_plugins: ToolWithProvider[]
uninstalled_recommended_plugins: UninstalledRecommendedPlugin[]
uninstalled_recommended_plugins: Plugin[]
}
export enum SupportUploadFileTypes {

View File

@ -39,7 +39,7 @@ import {
useQuery,
useQueryClient,
} from '@tanstack/react-query'
import { useInvalidateAllBuiltInTools, useInvalidateRAGRecommendedPlugins } from './use-tools'
import { useInvalidateAllBuiltInTools } from './use-tools'
import useReferenceSetting from '@/app/components/plugins/plugin-page/use-reference-setting'
import { uninstallPlugin } from '@/service/plugins'
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
@ -135,14 +135,12 @@ export const useInstalledLatestVersion = (pluginIds: string[]) => {
export const useInvalidateInstalledPluginList = () => {
const queryClient = useQueryClient()
const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools()
const invalidateRAGRecommendedPlugins = useInvalidateRAGRecommendedPlugins()
return () => {
queryClient.invalidateQueries(
{
queryKey: useInstalledPluginListKey,
})
invalidateAllBuiltInTools()
invalidateRAGRecommendedPlugins()
}
}