This commit is contained in:
2025-12-01 17:21:38 +08:00
parent 32fee2b8ab
commit fab8c13cb3
7511 changed files with 996300 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import {
useMarketplaceCollectionsAndPlugins,
useMarketplacePlugins,
} from '@/app/components/plugins/marketplace/hooks'
import { PluginCategoryEnum } 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,
marketplaceCollectionPluginsMap,
queryMarketplaceCollectionsAndPlugins,
} = useMarketplaceCollectionsAndPlugins()
const {
plugins,
resetPlugins,
queryPlugins,
queryPluginsWithDebounced,
isLoading: isPluginsLoading,
total: pluginsTotal,
} = useMarketplacePlugins()
const [page, setPage] = useState(1)
const pageRef = useRef(page)
const searchPluginTextRef = useRef(searchPluginText)
const filterPluginTagsRef = useRef(filterPluginTags)
useEffect(() => {
searchPluginTextRef.current = searchPluginText
filterPluginTagsRef.current = filterPluginTags
}, [searchPluginText, filterPluginTags])
useEffect(() => {
if ((searchPluginText || filterPluginTags.length) && isSuccess) {
setPage(1)
pageRef.current = 1
if (searchPluginText) {
queryPluginsWithDebounced({
category: PluginCategoryEnum.tool,
query: searchPluginText,
tags: filterPluginTags,
exclude,
type: 'plugin',
page: pageRef.current,
})
return
}
queryPlugins({
category: PluginCategoryEnum.tool,
query: searchPluginText,
tags: filterPluginTags,
exclude,
type: 'plugin',
page: pageRef.current,
})
}
else {
if (isSuccess) {
queryMarketplaceCollectionsAndPlugins({
category: PluginCategoryEnum.tool,
condition: getMarketplaceListCondition(PluginCategoryEnum.tool),
exclude,
type: 'plugin',
})
resetPlugins()
}
}
}, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins, exclude, isSuccess])
const handleScroll = useCallback((e: Event) => {
const target = e.target as HTMLDivElement
const {
scrollTop,
scrollHeight,
clientHeight,
} = target
if (scrollTop + clientHeight >= scrollHeight - 5 && scrollTop > 0) {
const searchPluginText = searchPluginTextRef.current
const filterPluginTags = filterPluginTagsRef.current
if (pluginsTotal && plugins && pluginsTotal > plugins.length && (!!searchPluginText || !!filterPluginTags.length)) {
setPage(pageRef.current + 1)
pageRef.current++
queryPlugins({
category: PluginCategoryEnum.tool,
query: searchPluginText,
tags: filterPluginTags,
exclude,
type: 'plugin',
page: pageRef.current,
})
}
}
}, [exclude, plugins, pluginsTotal, queryPlugins])
return {
isLoading: isLoading || isPluginsLoading,
marketplaceCollections,
marketplaceCollectionPluginsMap,
plugins,
handleScroll,
page,
}
}

View File

@@ -0,0 +1,116 @@
import { useTheme } from 'next-themes'
import {
RiArrowRightUpLine,
RiArrowUpDoubleLine,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import type { useMarketplace } from './hooks'
import List from '@/app/components/plugins/marketplace/list'
import Loading from '@/app/components/base/loading'
import { getLocaleOnClient } from '@/i18n-config'
import { getMarketplaceUrl } from '@/utils/var'
type MarketplaceProps = {
searchPluginText: string
filterPluginTags: string[]
isMarketplaceArrowVisible: boolean
showMarketplacePanel: () => void
marketplaceContext: ReturnType<typeof useMarketplace>
}
const Marketplace = ({
searchPluginText,
filterPluginTags,
isMarketplaceArrowVisible,
showMarketplacePanel,
marketplaceContext,
}: MarketplaceProps) => {
const locale = getLocaleOnClient()
const { t } = useTranslation()
const { theme } = useTheme()
const {
isLoading,
marketplaceCollections,
marketplaceCollectionPluginsMap,
plugins,
page,
} = marketplaceContext
return (
<>
<div className='sticky bottom-0 flex shrink-0 flex-col bg-background-default-subtle px-12 pb-[14px] pt-2'>
{isMarketplaceArrowVisible && (
<RiArrowUpDoubleLine
className='absolute left-1/2 top-2 z-10 h-4 w-4 -translate-x-1/2 cursor-pointer text-text-quaternary'
onClick={showMarketplacePanel}
/>
)}
<div className='pb-3 pt-4'>
<div className='title-2xl-semi-bold bg-gradient-to-r from-[rgba(11,165,236,0.95)] to-[rgba(21,90,239,0.95)] bg-clip-text text-transparent'>
{t('plugin.marketplace.moreFrom')}
</div>
<div className='body-md-regular flex items-center text-center text-text-tertiary'>
{t('plugin.marketplace.discover')}
<span className="body-md-medium relative ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
{t('plugin.category.models')}
</span>
,
<span className="body-md-medium relative ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
{t('plugin.category.tools')}
</span>
,
<span className="body-md-medium relative ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
{t('plugin.category.datasources')}
</span>
,
<span className="body-md-medium relative ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
{t('plugin.category.triggers')}
</span>
,
<span className="body-md-medium relative ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
{t('plugin.category.agents')}
</span>
,
<span className="body-md-medium relative ml-1 mr-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
{t('plugin.category.extensions')}
</span>
{t('plugin.marketplace.and')}
<span className="body-md-medium relative ml-1 mr-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
{t('plugin.category.bundles')}
</span>
{t('common.operation.in')}
<a
href={getMarketplaceUrl('', { language: locale, q: searchPluginText, tags: filterPluginTags.join(','), theme })}
className='system-sm-medium ml-1 flex items-center text-text-accent'
target='_blank'
>
{t('plugin.marketplace.difyMarketplace')}
<RiArrowRightUpLine className='h-4 w-4' />
</a>
</div>
</div>
</div>
<div className='mt-[-14px] shrink-0 grow bg-background-default-subtle px-12 pb-2'>
{
isLoading && page === 1 && (
<div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'>
<Loading />
</div>
)
}
{
(!isLoading || page > 1) && (
<List
marketplaceCollections={marketplaceCollections || []}
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap || {}}
plugins={plugins}
showInstallButton
locale={locale}
/>
)
}
</div>
</>
)
}
export default Marketplace