dify/web/app/components/plugins/marketplace/utils.ts
Stephen Zhou d4432ed80f
refactor: marketplace state management (#30702)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 14:31:24 +08:00

227 lines
6.4 KiB
TypeScript

import type { ActivePluginType } from './constants'
import type {
CollectionsAndPluginsSearchParams,
MarketplaceCollection,
PluginsSearchParams,
} from '@/app/components/plugins/marketplace/types'
import type { Plugin, PluginsFromMarketplaceResponse } from '@/app/components/plugins/types'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import {
APP_VERSION,
IS_MARKETPLACE,
MARKETPLACE_API_PREFIX,
} from '@/config'
import { postMarketplace } from '@/service/base'
import { getMarketplaceUrl } from '@/utils/var'
import { PLUGIN_TYPE_SEARCH_MAP } from './constants'
type MarketplaceFetchOptions = {
signal?: AbortSignal
}
const getMarketplaceHeaders = () => new Headers({
'X-Dify-Version': !IS_MARKETPLACE ? APP_VERSION : '999.0.0',
})
export const getPluginIconInMarketplace = (plugin: Plugin) => {
if (plugin.type === 'bundle')
return `${MARKETPLACE_API_PREFIX}/bundles/${plugin.org}/${plugin.name}/icon`
return `${MARKETPLACE_API_PREFIX}/plugins/${plugin.org}/${plugin.name}/icon`
}
export const getFormattedPlugin = (bundle: Plugin): Plugin => {
if (bundle.type === 'bundle') {
return {
...bundle,
icon: getPluginIconInMarketplace(bundle),
brief: bundle.description,
// @ts-expect-error I do not have enough information
label: bundle.labels,
}
}
return {
...bundle,
icon: getPluginIconInMarketplace(bundle),
}
}
export const getPluginLinkInMarketplace = (plugin: Plugin, params?: Record<string, string | undefined>) => {
if (plugin.type === 'bundle')
return getMarketplaceUrl(`/bundles/${plugin.org}/${plugin.name}`, params)
return getMarketplaceUrl(`/plugins/${plugin.org}/${plugin.name}`, params)
}
export const getPluginDetailLinkInMarketplace = (plugin: Plugin) => {
if (plugin.type === 'bundle')
return `/bundles/${plugin.org}/${plugin.name}`
return `/plugins/${plugin.org}/${plugin.name}`
}
export const getMarketplacePluginsByCollectionId = async (
collectionId: string,
query?: CollectionsAndPluginsSearchParams,
options?: MarketplaceFetchOptions,
) => {
let plugins: Plugin[] = []
try {
const url = `${MARKETPLACE_API_PREFIX}/collections/${collectionId}/plugins`
const headers = getMarketplaceHeaders()
const marketplaceCollectionPluginsData = await globalThis.fetch(
url,
{
cache: 'no-store',
method: 'POST',
headers,
signal: options?.signal,
body: JSON.stringify({
category: query?.category,
exclude: query?.exclude,
type: query?.type,
}),
},
)
const marketplaceCollectionPluginsDataJson = await marketplaceCollectionPluginsData.json()
plugins = (marketplaceCollectionPluginsDataJson.data.plugins || []).map((plugin: Plugin) => getFormattedPlugin(plugin))
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (e) {
plugins = []
}
return plugins
}
export const getMarketplaceCollectionsAndPlugins = async (
query?: CollectionsAndPluginsSearchParams,
options?: MarketplaceFetchOptions,
) => {
let marketplaceCollections: MarketplaceCollection[] = []
let marketplaceCollectionPluginsMap: Record<string, Plugin[]> = {}
try {
let marketplaceUrl = `${MARKETPLACE_API_PREFIX}/collections?page=1&page_size=100`
if (query?.condition)
marketplaceUrl += `&condition=${query.condition}`
if (query?.type)
marketplaceUrl += `&type=${query.type}`
const headers = getMarketplaceHeaders()
const marketplaceCollectionsData = await globalThis.fetch(
marketplaceUrl,
{
headers,
cache: 'no-store',
signal: options?.signal,
},
)
const marketplaceCollectionsDataJson = await marketplaceCollectionsData.json()
marketplaceCollections = marketplaceCollectionsDataJson.data.collections || []
await Promise.all(marketplaceCollections.map(async (collection: MarketplaceCollection) => {
const plugins = await getMarketplacePluginsByCollectionId(collection.name, query, options)
marketplaceCollectionPluginsMap[collection.name] = plugins
}))
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (e) {
marketplaceCollections = []
marketplaceCollectionPluginsMap = {}
}
return {
marketplaceCollections,
marketplaceCollectionPluginsMap,
}
}
export const getMarketplacePlugins = async (
queryParams: PluginsSearchParams | undefined,
pageParam: number,
signal?: AbortSignal,
) => {
if (!queryParams) {
return {
plugins: [] as Plugin[],
total: 0,
page: 1,
pageSize: 40,
}
}
const {
query,
sortBy,
sortOrder,
category,
tags,
type,
pageSize = 40,
} = queryParams
const pluginOrBundle = type === 'bundle' ? 'bundles' : 'plugins'
try {
const res = await postMarketplace<{ data: PluginsFromMarketplaceResponse }>(`/${pluginOrBundle}/search/advanced`, {
body: {
page: pageParam,
page_size: pageSize,
query,
sort_by: sortBy,
sort_order: sortOrder,
category: category !== 'all' ? category : '',
tags,
type,
},
signal,
})
const resPlugins = res.data.bundles || res.data.plugins || []
return {
plugins: resPlugins.map(plugin => getFormattedPlugin(plugin)),
total: res.data.total,
page: pageParam,
pageSize,
}
}
catch {
return {
plugins: [],
total: 0,
page: pageParam,
pageSize,
}
}
}
export const getMarketplaceListCondition = (pluginType: string) => {
if ([PluginCategoryEnum.tool, PluginCategoryEnum.agent, PluginCategoryEnum.model, PluginCategoryEnum.datasource, PluginCategoryEnum.trigger].includes(pluginType as PluginCategoryEnum))
return `category=${pluginType}`
if (pluginType === PluginCategoryEnum.extension)
return 'category=endpoint'
if (pluginType === 'bundle')
return 'type=bundle'
return ''
}
export const getMarketplaceListFilterType = (category: ActivePluginType) => {
if (category === PLUGIN_TYPE_SEARCH_MAP.all)
return undefined
if (category === PLUGIN_TYPE_SEARCH_MAP.bundle)
return 'bundle'
return 'plugin'
}
export function getCollectionsParams(category: ActivePluginType): CollectionsAndPluginsSearchParams {
if (category === PLUGIN_TYPE_SEARCH_MAP.all) {
return {}
}
return {
category,
condition: getMarketplaceListCondition(category),
type: getMarketplaceListFilterType(category),
}
}