fix: marketplace list

This commit is contained in:
StyleZhang 2024-12-03 18:02:57 +08:00
parent 1e2ee61f6a
commit d6a4cbc6cc
10 changed files with 182 additions and 16 deletions

View File

@ -8,7 +8,7 @@ const PluginList = async () => {
return ( return (
<PluginPage <PluginPage
plugins={<PluginsPanel />} plugins={<PluginsPanel />}
marketplace={<Marketplace locale={locale} shouldExclude />} marketplace={<Marketplace locale={locale} shouldExclude pluginTypeSwitchClassName='top-[60px]' />}
/> />
) )
} }

View File

@ -285,3 +285,25 @@ export const useMarketplace = (providers: ModelProvider[], searchText: string) =
plugins: plugins?.filter(plugin => plugin.type !== 'bundle'), plugins: plugins?.filter(plugin => plugin.type !== 'bundle'),
} }
} }
export const useMarketplaceAllPlugins = () => {
const {
plugins,
queryPlugins,
isLoading,
} = useMarketplacePlugins()
useEffect(() => {
queryPlugins({
query: '',
category: PluginType.model,
type: 'plugin',
pageSize: 1000,
})
}, [queryPlugins])
return {
plugins: plugins?.filter(plugin => plugin.type !== 'bundle'),
isLoading,
}
}

View File

@ -22,6 +22,7 @@ import {
import { import {
useDefaultModel, useDefaultModel,
useMarketplace, useMarketplace,
useMarketplaceAllPlugins,
useUpdateModelList, useUpdateModelList,
useUpdateModelProviders, useUpdateModelProviders,
} from './hooks' } from './hooks'
@ -128,6 +129,10 @@ const ModelProviderPage = ({ searchText }: Props) => {
marketplaceCollectionPluginsMap, marketplaceCollectionPluginsMap,
isLoading: isPluginsLoading, isLoading: isPluginsLoading,
} = useMarketplace(providers, searchText) } = useMarketplace(providers, searchText)
const {
plugins: allPlugins,
isLoading: isAllPluginsLoading,
} = useMarketplaceAllPlugins()
const cardRender = useCallback((plugin: Plugin) => { const cardRender = useCallback((plugin: Plugin) => {
if (plugin.type === 'bundle') if (plugin.type === 'bundle')
@ -206,12 +211,12 @@ const ModelProviderPage = ({ searchText }: Props) => {
<div className='flex items-center mb-2 pt-2'> <div className='flex items-center mb-2 pt-2'>
<span className='pr-1 text-text-tertiary system-sm-regular'>{t('common.modelProvider.discoverMore')}</span> <span className='pr-1 text-text-tertiary system-sm-regular'>{t('common.modelProvider.discoverMore')}</span>
<Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}`} className='inline-flex items-center system-sm-medium text-text-accent'> <Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}`} className='inline-flex items-center system-sm-medium text-text-accent'>
Dify Marketplace {t('plugin.marketplace.difyMarketplace')}
<RiArrowRightUpLine className='w-4 h-4' /> <RiArrowRightUpLine className='w-4 h-4' />
</Link> </Link>
</div> </div>
</div> </div>
{!collapse && isPluginsLoading && <Loading type='area' />} {!collapse && (isPluginsLoading || isAllPluginsLoading) && <Loading type='area' />}
{ {
!isPluginsLoading && ( !isPluginsLoading && (
<List <List
@ -225,6 +230,19 @@ const ModelProviderPage = ({ searchText }: Props) => {
/> />
) )
} }
{
!isAllPluginsLoading && (
<List
marketplaceCollections={[]}
marketplaceCollectionPluginsMap={{}}
plugins={allPlugins}
showInstallButton
locale={locale}
cardContainerClassName='grid grid-cols-2 gap-2'
cardRender={cardRender}
/>
)
}
</div> </div>
</div> </div>
) )

View File

@ -45,15 +45,19 @@ export type MarketplaceContextValue = {
handleFilterPluginTagsChange: (tags: string[]) => void handleFilterPluginTagsChange: (tags: string[]) => void
activePluginType: string activePluginType: string
handleActivePluginTypeChange: (type: string) => void handleActivePluginTypeChange: (type: string) => void
page: number
handlePageChange: (page: number) => void
plugins?: Plugin[] plugins?: Plugin[]
resetPlugins: () => void resetPlugins: () => void
sort: PluginsSort sort: PluginsSort
handleSortChange: (sort: PluginsSort) => void handleSortChange: (sort: PluginsSort) => void
handleQueryPluginsWhenNoCollection: () => void
marketplaceCollectionsFromClient?: MarketplaceCollection[] marketplaceCollectionsFromClient?: MarketplaceCollection[]
setMarketplaceCollectionsFromClient: (collections: MarketplaceCollection[]) => void setMarketplaceCollectionsFromClient: (collections: MarketplaceCollection[]) => void
marketplaceCollectionPluginsMapFromClient?: Record<string, Plugin[]> marketplaceCollectionPluginsMapFromClient?: Record<string, Plugin[]>
setMarketplaceCollectionPluginsMapFromClient: (map: Record<string, Plugin[]>) => void setMarketplaceCollectionPluginsMapFromClient: (map: Record<string, Plugin[]>) => void
isLoading: boolean isLoading: boolean
isSuccessCollections: boolean
} }
export const MarketplaceContext = createContext<MarketplaceContextValue>({ export const MarketplaceContext = createContext<MarketplaceContextValue>({
@ -65,15 +69,19 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
handleFilterPluginTagsChange: () => {}, handleFilterPluginTagsChange: () => {},
activePluginType: 'all', activePluginType: 'all',
handleActivePluginTypeChange: () => {}, handleActivePluginTypeChange: () => {},
page: 1,
handlePageChange: () => {},
plugins: undefined, plugins: undefined,
resetPlugins: () => {}, resetPlugins: () => {},
sort: DEFAULT_SORT, sort: DEFAULT_SORT,
handleSortChange: () => {}, handleSortChange: () => {},
handleQueryPluginsWhenNoCollection: () => {},
marketplaceCollectionsFromClient: [], marketplaceCollectionsFromClient: [],
setMarketplaceCollectionsFromClient: () => {}, setMarketplaceCollectionsFromClient: () => {},
marketplaceCollectionPluginsMapFromClient: {}, marketplaceCollectionPluginsMapFromClient: {},
setMarketplaceCollectionPluginsMapFromClient: () => {}, setMarketplaceCollectionPluginsMapFromClient: () => {},
isLoading: false, isLoading: false,
isSuccessCollections: false,
}) })
type MarketplaceContextProviderProps = { type MarketplaceContextProviderProps = {
@ -108,6 +116,8 @@ export const MarketplaceContextProvider = ({
const filterPluginTagsRef = useRef(filterPluginTags) const filterPluginTagsRef = useRef(filterPluginTags)
const [activePluginType, setActivePluginType] = useState(categoryFromSearchParams) const [activePluginType, setActivePluginType] = useState(categoryFromSearchParams)
const activePluginTypeRef = useRef(activePluginType) const activePluginTypeRef = useRef(activePluginType)
const [page, setPage] = useState(1)
const pageRef = useRef(page)
const [sort, setSort] = useState(DEFAULT_SORT) const [sort, setSort] = useState(DEFAULT_SORT)
const sortRef = useRef(sort) const sortRef = useRef(sort)
const { const {
@ -117,6 +127,7 @@ export const MarketplaceContextProvider = ({
setMarketplaceCollectionPluginsMap: setMarketplaceCollectionPluginsMapFromClient, setMarketplaceCollectionPluginsMap: setMarketplaceCollectionPluginsMapFromClient,
queryMarketplaceCollectionsAndPlugins, queryMarketplaceCollectionsAndPlugins,
isLoading, isLoading,
isSuccess: isSuccessCollections,
} = useMarketplaceCollectionsAndPlugins() } = useMarketplaceCollectionsAndPlugins()
const { const {
plugins, plugins,
@ -135,6 +146,7 @@ export const MarketplaceContextProvider = ({
sortBy: sortRef.current.sortBy, sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder, sortOrder: sortRef.current.sortOrder,
type: getMarketplaceListFilterType(activePluginTypeRef.current), type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
}) })
history.pushState({}, '', `/${searchParams?.language ? `?language=${searchParams?.language}` : ''}`) history.pushState({}, '', `/${searchParams?.language ? `?language=${searchParams?.language}` : ''}`)
} }
@ -152,6 +164,8 @@ export const MarketplaceContextProvider = ({
const handleSearchPluginTextChange = useCallback((text: string) => { const handleSearchPluginTextChange = useCallback((text: string) => {
setSearchPluginText(text) setSearchPluginText(text)
searchPluginTextRef.current = text searchPluginTextRef.current = text
setPage(1)
pageRef.current = 1
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) { if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
queryMarketplaceCollectionsAndPlugins({ queryMarketplaceCollectionsAndPlugins({
@ -172,12 +186,15 @@ export const MarketplaceContextProvider = ({
sortBy: sortRef.current.sortBy, sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder, sortOrder: sortRef.current.sortOrder,
exclude, exclude,
page: pageRef.current,
}) })
}, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins, exclude]) }, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins, exclude])
const handleFilterPluginTagsChange = useCallback((tags: string[]) => { const handleFilterPluginTagsChange = useCallback((tags: string[]) => {
setFilterPluginTags(tags) setFilterPluginTags(tags)
filterPluginTagsRef.current = tags filterPluginTagsRef.current = tags
setPage(1)
pageRef.current = 1
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) { if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
queryMarketplaceCollectionsAndPlugins({ queryMarketplaceCollectionsAndPlugins({
@ -199,12 +216,15 @@ export const MarketplaceContextProvider = ({
sortOrder: sortRef.current.sortOrder, sortOrder: sortRef.current.sortOrder,
exclude, exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current), type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
}) })
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude]) }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude])
const handleActivePluginTypeChange = useCallback((type: string) => { const handleActivePluginTypeChange = useCallback((type: string) => {
setActivePluginType(type) setActivePluginType(type)
activePluginTypeRef.current = type activePluginTypeRef.current = type
setPage(1)
pageRef.current = 1
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) { if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
queryMarketplaceCollectionsAndPlugins({ queryMarketplaceCollectionsAndPlugins({
@ -226,12 +246,25 @@ export const MarketplaceContextProvider = ({
sortOrder: sortRef.current.sortOrder, sortOrder: sortRef.current.sortOrder,
exclude, exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current), type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
}) })
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude]) }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude])
const handleSortChange = useCallback((sort: PluginsSort) => { const handlePageChange = useCallback(() => {
setSort(sort) setPage(pageRef.current + 1)
sortRef.current = sort pageRef.current++
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
queryMarketplaceCollectionsAndPlugins({
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
condition: getMarketplaceListCondition(activePluginTypeRef.current),
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
})
resetPlugins()
return
}
queryPlugins({ queryPlugins({
query: searchPluginTextRef.current, query: searchPluginTextRef.current,
@ -241,9 +274,43 @@ export const MarketplaceContextProvider = ({
sortOrder: sortRef.current.sortOrder, sortOrder: sortRef.current.sortOrder,
exclude, exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current), type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
})
}, [exclude, queryPlugins, queryMarketplaceCollectionsAndPlugins, resetPlugins])
const handleSortChange = useCallback((sort: PluginsSort) => {
setSort(sort)
sortRef.current = sort
setPage(1)
pageRef.current = 1
queryPlugins({
query: searchPluginTextRef.current,
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
}) })
}, [queryPlugins, exclude]) }, [queryPlugins, exclude])
const handleQueryPluginsWhenNoCollection = useCallback(() => {
queryPlugins({
query: searchPluginTextRef.current,
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
})
}, [exclude, queryPlugins])
// useMarketplaceContainerScroll(handlePageChange)
return ( return (
<MarketplaceContext.Provider <MarketplaceContext.Provider
value={{ value={{
@ -255,15 +322,19 @@ export const MarketplaceContextProvider = ({
handleFilterPluginTagsChange, handleFilterPluginTagsChange,
activePluginType, activePluginType,
handleActivePluginTypeChange, handleActivePluginTypeChange,
page,
handlePageChange,
plugins, plugins,
resetPlugins, resetPlugins,
sort, sort,
handleSortChange, handleSortChange,
handleQueryPluginsWhenNoCollection,
marketplaceCollectionsFromClient, marketplaceCollectionsFromClient,
setMarketplaceCollectionsFromClient, setMarketplaceCollectionsFromClient,
marketplaceCollectionPluginsMapFromClient, marketplaceCollectionPluginsMapFromClient,
setMarketplaceCollectionPluginsMapFromClient, setMarketplaceCollectionPluginsMapFromClient,
isLoading: isLoading || isPluginsLoading, isLoading: isLoading || isPluginsLoading,
isSuccessCollections,
}} }}
> >
{children} {children}

View File

@ -1,5 +1,6 @@
import { import {
useCallback, useCallback,
useEffect,
useState, useState,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -23,16 +24,25 @@ import {
export const useMarketplaceCollectionsAndPlugins = () => { export const useMarketplaceCollectionsAndPlugins = () => {
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [isSuccess, setIsSuccess] = useState(false)
const [marketplaceCollections, setMarketplaceCollections] = useState<MarketplaceCollection[]>() const [marketplaceCollections, setMarketplaceCollections] = useState<MarketplaceCollection[]>()
const [marketplaceCollectionPluginsMap, setMarketplaceCollectionPluginsMap] = useState<Record<string, Plugin[]>>() const [marketplaceCollectionPluginsMap, setMarketplaceCollectionPluginsMap] = useState<Record<string, Plugin[]>>()
const queryMarketplaceCollectionsAndPlugins = useCallback(async (query?: CollectionsAndPluginsSearchParams) => { const queryMarketplaceCollectionsAndPlugins = useCallback(async (query?: CollectionsAndPluginsSearchParams) => {
try {
setIsLoading(true) setIsLoading(true)
setIsSuccess(false)
const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins(query) const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins(query)
setIsLoading(false) setIsLoading(false)
setIsSuccess(true)
setMarketplaceCollections(marketplaceCollections) setMarketplaceCollections(marketplaceCollections)
setMarketplaceCollectionPluginsMap(marketplaceCollectionPluginsMap) setMarketplaceCollectionPluginsMap(marketplaceCollectionPluginsMap)
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (e) {
setIsLoading(false)
setIsSuccess(false)
}
}, []) }, [])
return { return {
@ -42,6 +52,7 @@ export const useMarketplaceCollectionsAndPlugins = () => {
setMarketplaceCollectionPluginsMap, setMarketplaceCollectionPluginsMap,
queryMarketplaceCollectionsAndPlugins, queryMarketplaceCollectionsAndPlugins,
isLoading, isLoading,
isSuccess,
} }
} }
@ -67,6 +78,7 @@ export const useMarketplacePlugins = () => {
plugins: data?.data?.plugins.map((plugin) => { plugins: data?.data?.plugins.map((plugin) => {
return getFormattedPlugin(plugin) return getFormattedPlugin(plugin)
}), }),
total: data?.data?.total,
resetPlugins: reset, resetPlugins: reset,
queryPlugins, queryPlugins,
queryPluginsWithDebounced, queryPluginsWithDebounced,
@ -84,3 +96,28 @@ export const useMixedTranslation = (localeFromOuter?: string) => {
t, t,
} }
} }
export const useMarketplaceContainerScroll = (callback: () => void) => {
const container = document.getElementById('marketplace-container')
const handleScroll = useCallback((e: Event) => {
const target = e.target as HTMLDivElement
const {
scrollTop,
scrollHeight,
clientHeight,
} = target
if (scrollTop + clientHeight >= scrollHeight - 5 && scrollTop > 0)
callback()
}, [callback])
useEffect(() => {
if (container)
container.addEventListener('scroll', handleScroll)
return () => {
if (container)
container.removeEventListener('scroll', handleScroll)
}
}, [container, handleScroll])
}

View File

@ -13,12 +13,14 @@ type MarketplaceProps = {
showInstallButton?: boolean showInstallButton?: boolean
shouldExclude?: boolean shouldExclude?: boolean
searchParams?: SearchParams searchParams?: SearchParams
pluginTypeSwitchClassName?: string
} }
const Marketplace = async ({ const Marketplace = async ({
locale, locale,
showInstallButton = true, showInstallButton = true,
shouldExclude, shouldExclude,
searchParams, searchParams,
pluginTypeSwitchClassName,
}: MarketplaceProps) => { }: MarketplaceProps) => {
let marketplaceCollections: any = [] let marketplaceCollections: any = []
let marketplaceCollectionPluginsMap = {} let marketplaceCollectionPluginsMap = {}
@ -34,7 +36,10 @@ const Marketplace = async ({
<Description locale={locale} /> <Description locale={locale} />
<IntersectionLine /> <IntersectionLine />
<SearchBoxWrapper locale={locale} /> <SearchBoxWrapper locale={locale} />
<PluginTypeSwitch locale={locale} /> <PluginTypeSwitch
locale={locale}
className={pluginTypeSwitchClassName}
/>
<ListWrapper <ListWrapper
locale={locale} locale={locale}
marketplaceCollections={marketplaceCollections} marketplaceCollections={marketplaceCollections}

View File

@ -1,4 +1,5 @@
'use client' 'use client'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { Plugin } from '../../types' import type { Plugin } from '../../types'
import type { MarketplaceCollection } from '../types' import type { MarketplaceCollection } from '../types'
@ -24,6 +25,13 @@ const ListWrapper = ({
const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient) const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient)
const marketplaceCollectionPluginsMapFromClient = useMarketplaceContext(v => v.marketplaceCollectionPluginsMapFromClient) const marketplaceCollectionPluginsMapFromClient = useMarketplaceContext(v => v.marketplaceCollectionPluginsMapFromClient)
const isLoading = useMarketplaceContext(v => v.isLoading) const isLoading = useMarketplaceContext(v => v.isLoading)
const isSuccessCollections = useMarketplaceContext(v => v.isSuccessCollections)
const handleQueryPluginsWhenNoCollection = useMarketplaceContext(v => v.handleQueryPluginsWhenNoCollection)
useEffect(() => {
if (!marketplaceCollectionsFromClient?.length && isSuccessCollections)
handleQueryPluginsWhenNoCollection()
}, [handleQueryPluginsWhenNoCollection, marketplaceCollections, marketplaceCollectionsFromClient, isSuccessCollections])
return ( return (
<div className='relative flex flex-col grow px-12 py-2 bg-background-default-subtle'> <div className='relative flex flex-col grow px-12 py-2 bg-background-default-subtle'>

View File

@ -19,9 +19,11 @@ export const PLUGIN_TYPE_SEARCH_MAP = {
} }
type PluginTypeSwitchProps = { type PluginTypeSwitchProps = {
locale?: string locale?: string
className?: string
} }
const PluginTypeSwitch = ({ const PluginTypeSwitch = ({
locale, locale,
className,
}: PluginTypeSwitchProps) => { }: PluginTypeSwitchProps) => {
const { t } = useMixedTranslation(locale) const { t } = useMixedTranslation(locale)
const activePluginType = useMarketplaceContext(s => s.activePluginType) const activePluginType = useMarketplaceContext(s => s.activePluginType)
@ -57,7 +59,8 @@ const PluginTypeSwitch = ({
return ( return (
<div className={cn( <div className={cn(
'sticky top-[60px] shrink-0 flex items-center justify-center py-3 bg-background-body space-x-2 z-10', 'sticky top-[56px] shrink-0 flex items-center justify-center py-3 bg-background-body space-x-2 z-10',
className,
)}> )}>
{ {
options.map(option => ( options.map(option => (

View File

@ -312,6 +312,7 @@ export type UninstallPluginResponse = {
export type PluginsFromMarketplaceResponse = { export type PluginsFromMarketplaceResponse = {
plugins: Plugin[] plugins: Plugin[]
total: number
} }
export type PluginsFromMarketplaceByInfoResponse = { export type PluginsFromMarketplaceByInfoResponse = {
list: { list: {

View File

@ -297,11 +297,12 @@ export const useMutationPluginsFromMarketplace = () => {
tags, tags,
exclude, exclude,
type, type,
page = 1,
} = pluginsSearchParams } = pluginsSearchParams
return postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/search/basic', { return postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/search/basic', {
body: { body: {
page: 1, page,
page_size: 10, page_size: 100,
query, query,
sort_by: sortBy, sort_by: sortBy,
sort_order: sortOrder, sort_order: sortOrder,