+ {/* Navigation arrows */}
+ {showNavigation && (
+
+ {/* Pagination dots */}
+ {showPagination && scrollState.totalPages > 1 && (
+
+ {Array.from({ length: scrollState.totalPages }).map((_, index) => (
+
+ )}
+
+
+ scroll('left')}
+ Icon={RiArrowLeftSLine}
+ />
+ scroll('right')}
+ Icon={RiArrowRightSLine}
+ />
+
+
+ )}
+
+ {/* Scrollable container */}
+
+ {children}
+
+
+ )
+}
+
+export default Carousel
diff --git a/web/app/components/plugins/marketplace/list/list-with-collection.tsx b/web/app/components/plugins/marketplace/list/list-with-collection.tsx
index 264227b666..2eaa96127f 100644
--- a/web/app/components/plugins/marketplace/list/list-with-collection.tsx
+++ b/web/app/components/plugins/marketplace/list/list-with-collection.tsx
@@ -5,9 +5,9 @@ import type { Plugin } from '@/app/components/plugins/types'
import { useLocale, useTranslation } from '#i18n'
import { RiArrowRightSLine } from '@remixicon/react'
import { getLanguage } from '@/i18n-config/language'
-import { cn } from '@/utils/classnames'
import { useMarketplaceMoreClick } from '../atoms'
import CardWrapper from './card-wrapper'
+import Carousel from './carousel'
type ListWithCollectionProps = {
marketplaceCollections: MarketplaceCollection[]
@@ -16,6 +16,10 @@ type ListWithCollectionProps = {
cardContainerClassName?: string
cardRender?: (plugin: Plugin) => React.JSX.Element | null
}
+
+const PARTNERS_COLLECTION_NAME = 'partners'
+const GRID_DISPLAY_LIMIT = 8 // 2 rows × 4 columns
+
const ListWithCollection = ({
marketplaceCollections,
marketplaceCollectionPluginsMap,
@@ -27,55 +31,102 @@ const ListWithCollection = ({
const locale = useLocale()
const onMoreClick = useMarketplaceMoreClick()
+ const renderPluginCard = (plugin: Plugin) => {
+ if (cardRender)
+ return cardRender(plugin)
+
+ return (
+
-
-
-
{collection.label[getLanguage(locale)]}
-
{collection.description[getLanguage(locale)]}
-
- {
- collection.searchable && (
+ }).map((collection) => {
+ const plugins = marketplaceCollectionPluginsMap[collection.name]
+ const isPartnersCollection = collection.name === PARTNERS_COLLECTION_NAME
+ const showViewMore = collection.searchable && (isPartnersCollection || plugins.length > GRID_DISPLAY_LIMIT)
+
+ return (
+
+
+
+
{collection.label[getLanguage(locale)]}
+
{collection.description[getLanguage(locale)]}
+
+ {showViewMore && (
onMoreClick(collection.search_params)}
>
{t('marketplace.viewMore', { ns: 'plugin' })}
- )
- }
+ )}
+
+ {isPartnersCollection
+ ? renderPartnersCarousel(collection, plugins)
+ : renderGridCollection(collection, plugins)}
-
- {
- marketplaceCollectionPluginsMap[collection.name].map((plugin) => {
- if (cardRender)
- return cardRender(plugin)
-
- return (
-
- )
- })
- }
-
-
- ))
+ )
+ })
}
>
)
diff --git a/web/app/components/plugins/marketplace/plugin-type-switch.tsx b/web/app/components/plugins/marketplace/plugin-type-switch.tsx
index 6e56a288d8..916d290638 100644
--- a/web/app/components/plugins/marketplace/plugin-type-switch.tsx
+++ b/web/app/components/plugins/marketplace/plugin-type-switch.tsx
@@ -2,6 +2,7 @@
import type { ActivePluginType } from './constants'
import { useTranslation } from '#i18n'
import {
+ RiApps2Line,
RiArchive2Line,
RiBrain2Line,
RiDatabase2Line,
@@ -17,14 +18,18 @@ import { PLUGIN_CATEGORY_WITH_COLLECTIONS, PLUGIN_TYPE_SEARCH_MAP } from './cons
type PluginTypeSwitchProps = {
className?: string
+ variant?: 'default' | 'hero'
}
const PluginTypeSwitch = ({
className,
+ variant = 'default',
}: PluginTypeSwitchProps) => {
const { t } = useTranslation()
const [activePluginType, handleActivePluginTypeChange] = useActivePluginType()
const setSearchMode = useSetAtom(searchModeAtom)
+ const isHeroVariant = variant === 'hero'
+
const options: Array<{
value: ActivePluginType
text: string
@@ -32,8 +37,8 @@ const PluginTypeSwitch = ({
}> = [
{
value: PLUGIN_TYPE_SEARCH_MAP.all,
- text: t('category.all', { ns: 'plugin' }),
- icon: null,
+ text: isHeroVariant ? t('category.allTypes', { ns: 'plugin' }) : t('category.all', { ns: 'plugin' }),
+ icon: isHeroVariant ?
: null,
},
{
value: PLUGIN_TYPE_SEARCH_MAP.model,
@@ -72,9 +77,25 @@ const PluginTypeSwitch = ({
},
]
+ const getItemClassName = (isActive: boolean) => {
+ if (isHeroVariant) {
+ return cn(
+ 'system-md-medium flex h-8 cursor-pointer items-center rounded-lg px-3 text-text-primary-on-surface transition-all',
+ isActive
+ ? 'bg-components-button-secondary-bg text-saas-dify-blue-inverted'
+ : 'hover:bg-state-base-hover',
+ )
+ }
+ return cn(
+ 'system-md-medium flex h-8 cursor-pointer items-center rounded-xl border border-transparent px-3 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
+ isActive && 'border-components-main-nav-nav-button-border !bg-components-main-nav-nav-button-bg-active !text-components-main-nav-nav-button-text-active shadow-xs',
+ )
+ }
+
return (
@@ -82,10 +103,7 @@ const PluginTypeSwitch = ({
options.map(option => (
{
handleActivePluginTypeChange(option.value)
if (PLUGIN_CATEGORY_WITH_COLLECTIONS.has(option.value)) {
diff --git a/web/app/components/plugins/marketplace/search-box/search-box-wrapper.tsx b/web/app/components/plugins/marketplace/search-box/search-box-wrapper.tsx
index 9957e9bc42..39f2f1bdc6 100644
--- a/web/app/components/plugins/marketplace/search-box/search-box-wrapper.tsx
+++ b/web/app/components/plugins/marketplace/search-box/search-box-wrapper.tsx
@@ -1,18 +1,26 @@
'use client'
import { useTranslation } from '#i18n'
+import { cn } from '@/utils/classnames'
import { useFilterPluginTags, useSearchPluginText } from '../atoms'
import SearchBox from './index'
-const SearchBoxWrapper = () => {
+type SearchBoxWrapperProps = {
+ wrapperClassName?: string
+ inputClassName?: string
+}
+const SearchBoxWrapper = ({
+ wrapperClassName,
+ inputClassName,
+}: SearchBoxWrapperProps) => {
const { t } = useTranslation()
const [searchPluginText, handleSearchPluginTextChange] = useSearchPluginText()
const [filterPluginTags, handleFilterPluginTagsChange] = useFilterPluginTags()
return (
-
)
}
diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx
index efb665197a..30ece18ac3 100644
--- a/web/app/components/plugins/plugin-page/index.tsx
+++ b/web/app/components/plugins/plugin-page/index.tsx
@@ -27,6 +27,7 @@ import { PLUGIN_PAGE_TABS_MAP } from '../hooks'
import InstallFromLocalPackage from '../install-plugin/install-from-local-package'
import InstallFromMarketplace from '../install-plugin/install-from-marketplace'
import { PLUGIN_TYPE_SEARCH_MAP } from '../marketplace/constants'
+import SearchBoxWrapper from '../marketplace/search-box/search-box-wrapper'
import {
PluginPageContextProvider,
usePluginPageContext,
@@ -140,23 +141,17 @@ const PluginPage = ({
id="marketplace-container"
ref={containerRef}
style={{ scrollbarGutter: 'stable' }}
- className={cn('relative flex grow flex-col overflow-y-auto border-t border-divider-subtle', isPluginsTab
- ? 'rounded-t-xl bg-components-panel-bg'
- : 'bg-background-body')}
+ className="relative flex grow flex-col overflow-y-auto rounded-t-xl border-t border-divider-subtle bg-components-panel-bg"
>
-
+
-
+
+
{
diff --git a/web/i18n/en-US/plugin.json b/web/i18n/en-US/plugin.json
index c7f091a442..c7e4eb4a05 100644
--- a/web/i18n/en-US/plugin.json
+++ b/web/i18n/en-US/plugin.json
@@ -65,6 +65,7 @@
"autoUpdate.upgradeModePlaceholder.partial": "Only selected plugins will auto-update. No plugins are currently selected, so no plugins will auto-update.",
"category.agents": "Agent Strategies",
"category.all": "All",
+ "category.allTypes": "All types",
"category.bundles": "Bundles",
"category.datasources": "Data Sources",
"category.extensions": "Extensions",
@@ -194,7 +195,12 @@
"marketplace.difyMarketplace": "Dify Marketplace",
"marketplace.discover": "Discover",
"marketplace.empower": "Empower your AI development",
+ "marketplace.featured": "Featured",
+ "marketplace.heroSubtitle": "Use community-built plugins to power your AI development.",
+ "marketplace.heroTitle": "Discover. Extend. Build.",
+ "marketplace.installs": "installs",
"marketplace.moreFrom": "More from Marketplace",
+ "marketplace.ourTopPicks": "Our top picks to get you started",
"marketplace.noPluginFound": "No plugin found",
"marketplace.partnerTip": "Verified by a Dify partner",
"marketplace.pluginsResult": "{{num}} results",
diff --git a/web/i18n/zh-Hans/plugin.json b/web/i18n/zh-Hans/plugin.json
index 703bd4e6ea..e2b625dc8a 100644
--- a/web/i18n/zh-Hans/plugin.json
+++ b/web/i18n/zh-Hans/plugin.json
@@ -65,6 +65,7 @@
"autoUpdate.upgradeModePlaceholder.partial": "仅选定的插件将自动更新。目前未选择任何插件,因此不会自动更新任何插件。",
"category.agents": "Agent 策略",
"category.all": "全部",
+ "category.allTypes": "所有类型",
"category.bundles": "插件集",
"category.datasources": "数据源",
"category.extensions": "扩展",
@@ -194,7 +195,12 @@
"marketplace.difyMarketplace": "Dify 市场",
"marketplace.discover": "探索",
"marketplace.empower": "助力您的 AI 开发",
+ "marketplace.featured": "精选",
+ "marketplace.heroSubtitle": "使用社区构建的插件为您的 AI 开发提供动力。",
+ "marketplace.heroTitle": "探索。扩展。构建。",
+ "marketplace.installs": "次安装",
"marketplace.moreFrom": "更多来自市场",
+ "marketplace.ourTopPicks": "我们精选推荐",
"marketplace.noPluginFound": "未找到插件",
"marketplace.partnerTip": "此插件由 Dify 合作伙伴认证",
"marketplace.pluginsResult": "{{num}} 个插件结果",