diff --git a/web/app/(commonLayout)/plugins/page.tsx b/web/app/(commonLayout)/plugins/page.tsx index 516cc138a2..f44ff6522a 100644 --- a/web/app/(commonLayout)/plugins/page.tsx +++ b/web/app/(commonLayout)/plugins/page.tsx @@ -8,7 +8,7 @@ const PluginList = async () => { return ( } - marketplace={} + marketplace={} /> ) } diff --git a/web/app/components/plugins/marketplace/context.tsx b/web/app/components/plugins/marketplace/context.tsx index 4b692cac28..57e3dae420 100644 --- a/web/app/components/plugins/marketplace/context.tsx +++ b/web/app/components/plugins/marketplace/context.tsx @@ -6,6 +6,7 @@ import type { import { useCallback, useEffect, + useMemo, useRef, useState, } from 'react' @@ -30,6 +31,7 @@ import { useMarketplacePlugins, } from './hooks' import { getMarketplaceListCondition } from './utils' +import { useInstalledPluginList } from '@/service/use-plugins' export type MarketplaceContextValue = { intersected: boolean @@ -74,6 +76,7 @@ export const MarketplaceContext = createContext({ type MarketplaceContextProviderProps = { children: ReactNode searchParams?: SearchParams + shouldExclude?: boolean } export function useMarketplaceContext(selector: (value: MarketplaceContextValue) => any) { @@ -83,7 +86,13 @@ export function useMarketplaceContext(selector: (value: MarketplaceContextValue) export const MarketplaceContextProvider = ({ children, searchParams, + shouldExclude, }: MarketplaceContextProviderProps) => { + const { data, isSuccess } = useInstalledPluginList(!shouldExclude) + const exclude = useMemo(() => { + if (shouldExclude) + return data?.plugins.map(plugin => plugin.plugin_id) + }, [data?.plugins, shouldExclude]) const queryFromSearchParams = searchParams?.q || '' const tagsFromSearchParams = searchParams?.tags ? getValidTagKeys(searchParams.tags.split(',')) : [] const hasValidTags = !!tagsFromSearchParams.length @@ -125,8 +134,15 @@ export const MarketplaceContextProvider = ({ }) history.pushState({}, '', `/${searchParams?.language ? `?language=${searchParams?.language}` : ''}`) } + else { + if (shouldExclude && isSuccess) { + queryMarketplaceCollectionsAndPlugins({ + exclude, + }) + } + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [queryPlugins]) + }, [queryPlugins, queryMarketplaceCollectionsAndPlugins, isSuccess, exclude]) const handleSearchPluginTextChange = useCallback((text: string) => { setSearchPluginText(text) @@ -136,6 +152,7 @@ export const MarketplaceContextProvider = ({ queryMarketplaceCollectionsAndPlugins({ category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current, condition: getMarketplaceListCondition(activePluginTypeRef.current), + exclude, }) resetPlugins() @@ -148,8 +165,9 @@ export const MarketplaceContextProvider = ({ tags: filterPluginTagsRef.current, sortBy: sortRef.current.sortBy, sortOrder: sortRef.current.sortOrder, + exclude, }) - }, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins]) + }, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins, exclude]) const handleFilterPluginTagsChange = useCallback((tags: string[]) => { setFilterPluginTags(tags) @@ -159,6 +177,7 @@ export const MarketplaceContextProvider = ({ queryMarketplaceCollectionsAndPlugins({ category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current, condition: getMarketplaceListCondition(activePluginTypeRef.current), + exclude, }) resetPlugins() @@ -171,8 +190,9 @@ export const MarketplaceContextProvider = ({ tags, sortBy: sortRef.current.sortBy, sortOrder: sortRef.current.sortOrder, + exclude, }) - }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins]) + }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude]) const handleActivePluginTypeChange = useCallback((type: string) => { setActivePluginType(type) @@ -182,6 +202,7 @@ export const MarketplaceContextProvider = ({ queryMarketplaceCollectionsAndPlugins({ category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type, condition: getMarketplaceListCondition(type), + exclude, }) resetPlugins() @@ -194,8 +215,9 @@ export const MarketplaceContextProvider = ({ tags: filterPluginTagsRef.current, sortBy: sortRef.current.sortBy, sortOrder: sortRef.current.sortOrder, + exclude, }) - }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins]) + }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude]) const handleSortChange = useCallback((sort: PluginsSort) => { setSort(sort) @@ -207,8 +229,9 @@ export const MarketplaceContextProvider = ({ tags: filterPluginTagsRef.current, sortBy: sortRef.current.sortBy, sortOrder: sortRef.current.sortOrder, + exclude, }) - }, [queryPlugins]) + }, [queryPlugins, exclude]) return ( { const [isLoading, setIsLoading] = useState(false) @@ -55,7 +57,7 @@ export const useMarketplacePlugins = () => { mutate(pluginsSearchParams) }, [mutate]) - const { run: queryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams) => { + const { run: queryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams: PluginsSearchParams) => { mutate(pluginsSearchParams) }, { wait: 500, diff --git a/web/app/components/plugins/marketplace/index.tsx b/web/app/components/plugins/marketplace/index.tsx index 5afb8c31ae..9f8bbb2e76 100644 --- a/web/app/components/plugins/marketplace/index.tsx +++ b/web/app/components/plugins/marketplace/index.tsx @@ -11,18 +11,26 @@ import { TanstackQueryIniter } from '@/context/query-client' type MarketplaceProps = { locale: string showInstallButton?: boolean + shouldExclude?: boolean searchParams?: SearchParams } const Marketplace = async ({ locale, showInstallButton = true, + shouldExclude, searchParams, }: MarketplaceProps) => { - const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins() + let marketplaceCollections: any = [] + let marketplaceCollectionPluginsMap = {} + if (!shouldExclude) { + const marketplaceCollectionsAndPluginsData = await getMarketplaceCollectionsAndPlugins() + marketplaceCollections = marketplaceCollectionsAndPluginsData.marketplaceCollections + marketplaceCollectionPluginsMap = marketplaceCollectionsAndPluginsData.marketplaceCollectionPluginsMap + } return ( - + diff --git a/web/app/components/plugins/marketplace/types.ts b/web/app/components/plugins/marketplace/types.ts index 58424d6c68..844ced0e00 100644 --- a/web/app/components/plugins/marketplace/types.ts +++ b/web/app/components/plugins/marketplace/types.ts @@ -27,6 +27,7 @@ export type PluginsSearchParams = { sortOrder?: string category?: string tags?: string[] + exclude?: string[] } export type PluginsSort = { @@ -37,6 +38,7 @@ export type PluginsSort = { export type CollectionsAndPluginsSearchParams = { category?: string condition?: string + exclude?: string[] } export type SearchParams = { diff --git a/web/app/components/plugins/marketplace/utils.ts b/web/app/components/plugins/marketplace/utils.ts index c9f77318f7..7252b9d315 100644 --- a/web/app/components/plugins/marketplace/utils.ts +++ b/web/app/components/plugins/marketplace/utils.ts @@ -3,7 +3,6 @@ import { PluginType } from '@/app/components/plugins/types' import type { CollectionsAndPluginsSearchParams, MarketplaceCollection, - PluginsSearchParams, } from '@/app/components/plugins/marketplace/types' import { MARKETPLACE_API_PREFIX } from '@/config' @@ -22,10 +21,18 @@ export const getMarketplaceCollectionsAndPlugins = async (query?: CollectionsAnd const marketplaceCollectionsDataJson = await marketplaceCollectionsData.json() marketplaceCollections = marketplaceCollectionsDataJson.data.collections await Promise.all(marketplaceCollections.map(async (collection: MarketplaceCollection) => { - let url = `${MARKETPLACE_API_PREFIX}/collections/${collection.name}/plugins?page=1&page_size=100` - if (query?.category) - url += `&category=${query.category}` - const marketplaceCollectionPluginsData = await globalThis.fetch(url, { cache: 'no-store' }) + const url = `${MARKETPLACE_API_PREFIX}/collections/${collection.name}/plugins` + const marketplaceCollectionPluginsData = await globalThis.fetch( + url, + { + cache: 'no-store', + method: 'POST', + body: JSON.stringify({ + category: query?.category, + exclude: query?.exclude, + }), + }, + ) const marketplaceCollectionPluginsDataJson = await marketplaceCollectionPluginsData.json() const plugins = marketplaceCollectionPluginsDataJson.data.plugins.map((plugin: Plugin) => { return { @@ -49,45 +56,6 @@ export const getMarketplaceCollectionsAndPlugins = async (query?: CollectionsAnd } } -export const getMarketplacePlugins = async (query: PluginsSearchParams) => { - let marketplacePlugins = [] as Plugin[] - try { - const marketplacePluginsData = await globalThis.fetch( - `${MARKETPLACE_API_PREFIX}/plugins/search/basic`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - page: 1, - page_size: 10, - query: query.query, - sort_by: query.sortBy, - sort_order: query.sortOrder, - category: query.category, - tags: query.tags, - }), - }, - ) - const marketplacePluginsDataJson = await marketplacePluginsData.json() - marketplacePlugins = marketplacePluginsDataJson.data.plugins.map((plugin: Plugin) => { - return { - ...plugin, - icon: getPluginIconInMarketplace(plugin), - } - }) - } - // eslint-disable-next-line unused-imports/no-unused-vars - catch (e) { - marketplacePlugins = [] - } - - return { - marketplacePlugins, - } -} - export const getMarketplaceListCondition = (pluginType: string) => { if (pluginType === PluginType.tool) return 'category=tool' diff --git a/web/app/components/tools/marketplace/hooks.ts b/web/app/components/tools/marketplace/hooks.ts index 3aec42be75..e29920ab29 100644 --- a/web/app/components/tools/marketplace/hooks.ts +++ b/web/app/components/tools/marketplace/hooks.ts @@ -1,5 +1,6 @@ import { useEffect, + useMemo, } from 'react' import { useMarketplaceCollectionsAndPlugins, @@ -7,8 +8,14 @@ import { } from '@/app/components/plugins/marketplace/hooks' import { PluginType } from '@/app/components/plugins/types' import { getMarketplaceListCondition } from '@/app/components/plugins/marketplace/utils' +import { useAllToolProviders } from '@/service/use-tools' export const useMarketplace = (searchPluginText: string, filterPluginTags: string[]) => { + const { data: toolProvidersData, isSuccess } = useAllToolProviders() + const exclude = useMemo(() => { + if (isSuccess) + return toolProvidersData?.filter(toolProvider => !!toolProvider.plugin_id).map(toolProvider => toolProvider.plugin_id!) + }, [isSuccess, toolProvidersData]) const { isLoading, marketplaceCollections, @@ -24,12 +31,13 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin } = useMarketplacePlugins() useEffect(() => { - if (searchPluginText || filterPluginTags.length) { + if ((searchPluginText || filterPluginTags.length) && isSuccess) { if (searchPluginText) { queryPluginsWithDebounced({ category: PluginType.tool, query: searchPluginText, tags: filterPluginTags, + exclude, }) return } @@ -37,16 +45,20 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin category: PluginType.tool, query: searchPluginText, tags: filterPluginTags, + exclude, }) } else { - queryMarketplaceCollectionsAndPlugins({ - category: PluginType.tool, - condition: getMarketplaceListCondition(PluginType.tool), - }) - resetPlugins() + if (isSuccess) { + queryMarketplaceCollectionsAndPlugins({ + category: PluginType.tool, + condition: getMarketplaceListCondition(PluginType.tool), + exclude, + }) + resetPlugins() + } } - }, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins]) + }, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins, exclude, isSuccess]) return { isLoading: isLoading || isPluginsLoading, diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index 9072810426..9bdf63c5c5 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -29,10 +29,12 @@ import { useInvalidateAllBuiltInTools } from './use-tools' const NAME_SPACE = 'plugins' const useInstalledPluginListKey = [NAME_SPACE, 'installedPluginList'] -export const useInstalledPluginList = () => { +export const useInstalledPluginList = (disable?: boolean) => { return useQuery({ queryKey: useInstalledPluginListKey, queryFn: () => get('/workspaces/current/plugin/list'), + enabled: !disable, + initialData: !disable ? undefined : { plugins: [] }, }) } @@ -225,6 +227,7 @@ export const useMutationPluginsFromMarketplace = () => { sortOrder, category, tags, + exclude, } = pluginsSearchParams return postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/search/basic', { body: { @@ -235,6 +238,7 @@ export const useMutationPluginsFromMarketplace = () => { sort_order: sortOrder, category: category !== 'all' ? category : '', tags, + exclude, }, }) },