diff --git a/web/app/components/plugins/marketplace/__tests__/utils.spec.ts b/web/app/components/plugins/marketplace/__tests__/utils.spec.ts
index 478a86ffa39..91c83e21194 100644
--- a/web/app/components/plugins/marketplace/__tests__/utils.spec.ts
+++ b/web/app/components/plugins/marketplace/__tests__/utils.spec.ts
@@ -122,6 +122,22 @@ describe('getPluginLinkInMarketplace', () => {
})
})
+describe('getPluginDetailLinkInMarketplace', () => {
+ it('should return the local detail link for a regular plugin', async () => {
+ const { getPluginDetailLinkInMarketplace } = await import('../utils')
+ const plugin = createMockPlugin({ org: 'test-org', name: 'test-plugin', type: 'plugin' })
+
+ expect(getPluginDetailLinkInMarketplace(plugin)).toBe('/plugin/test-org/test-plugin')
+ })
+
+ it('should return the local detail link for a bundle', async () => {
+ const { getPluginDetailLinkInMarketplace } = await import('../utils')
+ const bundle = createMockPlugin({ org: 'test-org', name: 'test-bundle', type: 'bundle' })
+
+ expect(getPluginDetailLinkInMarketplace(bundle)).toBe('/bundles/test-org/test-bundle')
+ })
+})
+
describe('getMarketplaceListCondition', () => {
it('should return category condition for tool', async () => {
const { getMarketplaceListCondition } = await import('../utils')
diff --git a/web/app/components/plugins/marketplace/index.tsx b/web/app/components/plugins/marketplace/index.tsx
index 9c140b9d73a..5714dff3588 100644
--- a/web/app/components/plugins/marketplace/index.tsx
+++ b/web/app/components/plugins/marketplace/index.tsx
@@ -8,6 +8,7 @@ import StickySearchAndSwitchWrapper from './sticky-search-and-switch-wrapper'
type MarketplaceProps = {
showInstallButton?: boolean
+ linkToMarketplaceDetail?: boolean
pluginTypeSwitchClassName?: string
isMarketplacePlatform?: boolean
marketplaceNav?: React.ReactNode
@@ -19,6 +20,7 @@ type MarketplaceProps = {
const Marketplace = async ({
showInstallButton = false,
+ linkToMarketplaceDetail = false,
pluginTypeSwitchClassName,
isMarketplacePlatform = false,
marketplaceNav,
@@ -35,7 +37,10 @@ const Marketplace = async ({
{!isMarketplacePlatform && (
)}
-
+
diff --git a/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx b/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx
index 0e8d561f864..049b6d51157 100644
--- a/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx
+++ b/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx
@@ -87,13 +87,19 @@ describe('CardWrapper', () => {
,
)
- it('renders a non-navigating card when install button is hidden', () => {
+ it('renders a non-navigating card by default when install button is hidden', () => {
renderCardWrapper()
expect(screen.queryByRole('link')).not.toBeInTheDocument()
expect(screen.getByTestId('card-more-info')).toHaveTextContent('42:tag:search|tag:agent')
})
+ it('links the card to its marketplace detail when explicitly enabled', () => {
+ renderCardWrapper({ linkToMarketplaceDetail: true })
+
+ expect(screen.getByRole('link')).toHaveAttribute('href', '/detail/dify/plugin-a')
+ })
+
it('renders install and marketplace detail actions when install button is shown', () => {
renderCardWrapper({ showInstallButton: true })
diff --git a/web/app/components/plugins/marketplace/list/card-wrapper.tsx b/web/app/components/plugins/marketplace/list/card-wrapper.tsx
index d9c4b8a7570..081e6d9c0c8 100644
--- a/web/app/components/plugins/marketplace/list/card-wrapper.tsx
+++ b/web/app/components/plugins/marketplace/list/card-wrapper.tsx
@@ -11,17 +11,20 @@ import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
import { useTags } from '@/app/components/plugins/hooks'
import { useOptionalPluginInstallPermission } from '@/app/components/plugins/install-plugin/hooks/use-plugin-install-permission'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
-import { getPluginLinkInMarketplace } from '../utils'
+import Link from '@/next/link'
+import { getPluginDetailLinkInMarketplace, getPluginLinkInMarketplace } from '../utils'
type CardWrapperProps = {
plugin: Plugin
showInstallButton?: boolean
isInstalled?: boolean
+ linkToMarketplaceDetail?: boolean
}
const CardWrapperComponent = ({
plugin,
showInstallButton,
isInstalled = false,
+ linkToMarketplaceDetail = false,
}: CardWrapperProps) => {
const { t } = useTranslation()
const { theme } = useTheme()
@@ -102,7 +105,7 @@ const CardWrapperComponent = ({
)
}
- return (
+ const card = (
)
+
+ if (!linkToMarketplaceDetail) return card
+
+ return (
+
+ {card}
+
+ )
}
// Memoize the component to prevent unnecessary re-renders when props haven't changed
diff --git a/web/app/components/plugins/marketplace/list/index.tsx b/web/app/components/plugins/marketplace/list/index.tsx
index 94206e589f9..6d6c227b56e 100644
--- a/web/app/components/plugins/marketplace/list/index.tsx
+++ b/web/app/components/plugins/marketplace/list/index.tsx
@@ -15,6 +15,7 @@ type ListProps = {
marketplaceCollectionPluginsMap: Record
plugins?: Plugin[]
showInstallButton?: boolean
+ linkToMarketplaceDetail?: boolean
cardContainerClassName?: string
cardRender?: (plugin: Plugin) => React.JSX.Element | null
emptyClassName?: string
@@ -25,6 +26,7 @@ const List = ({
marketplaceCollectionPluginsMap,
plugins,
showInstallButton,
+ linkToMarketplaceDetail,
cardContainerClassName,
cardRender,
emptyClassName,
@@ -62,6 +64,7 @@ const List = ({
marketplaceCollections={marketplaceCollections}
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
showInstallButton={showInstallButton}
+ linkToMarketplaceDetail={linkToMarketplaceDetail}
cardContainerClassName={cardContainerClassName}
cardRender={cardRender}
onCollectionMoreClick={onCollectionMoreClick}
@@ -79,6 +82,7 @@ const List = ({
plugin={plugin}
showInstallButton={showInstallButton}
isInstalled={installedPluginIds.has(plugin.plugin_id)}
+ linkToMarketplaceDetail={linkToMarketplaceDetail}
/>
)
})}
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 4fd44f00ece..42c03c9f6c5 100644
--- a/web/app/components/plugins/marketplace/list/list-with-collection.tsx
+++ b/web/app/components/plugins/marketplace/list/list-with-collection.tsx
@@ -35,6 +35,7 @@ type ListWithCollectionProps = {
marketplaceCollections: MarketplaceCollection[]
marketplaceCollectionPluginsMap: Record
showInstallButton?: boolean
+ linkToMarketplaceDetail?: boolean
cardContainerClassName?: string
cardRender?: (plugin: Plugin) => React.JSX.Element | null
onCollectionMoreClick?: (searchParams?: SearchParamsFromCollection) => void
@@ -46,13 +47,25 @@ type PluginCardProps = {
showInstallButton?: boolean
cardRender?: (plugin: Plugin) => React.JSX.Element | null
isInstalled?: boolean
+ linkToMarketplaceDetail?: boolean
}
-const PluginCard = ({ plugin, showInstallButton, cardRender, isInstalled }: PluginCardProps) => {
+const PluginCard = ({
+ plugin,
+ showInstallButton,
+ cardRender,
+ isInstalled,
+ linkToMarketplaceDetail,
+}: PluginCardProps) => {
if (cardRender) return cardRender(plugin)
return (
-
+
)
}
@@ -60,6 +73,7 @@ const ListWithCollection = ({
marketplaceCollections,
marketplaceCollectionPluginsMap,
showInstallButton,
+ linkToMarketplaceDetail,
cardContainerClassName,
cardRender,
onCollectionMoreClick,
@@ -147,6 +161,7 @@ const ListWithCollection = ({
@@ -163,6 +178,7 @@ const ListWithCollection = ({
key={plugin.plugin_id}
plugin={plugin}
showInstallButton={showInstallButton}
+ linkToMarketplaceDetail={linkToMarketplaceDetail}
cardRender={cardRender}
isInstalled={installedPluginIds?.has(plugin.plugin_id)}
/>
diff --git a/web/app/components/plugins/marketplace/list/list-wrapper.tsx b/web/app/components/plugins/marketplace/list/list-wrapper.tsx
index 385bc9be9f4..63a1debe9db 100644
--- a/web/app/components/plugins/marketplace/list/list-wrapper.tsx
+++ b/web/app/components/plugins/marketplace/list/list-wrapper.tsx
@@ -7,8 +7,9 @@ import List from './index'
type ListWrapperProps = {
showInstallButton?: boolean
+ linkToMarketplaceDetail?: boolean
}
-const ListWrapper = ({ showInstallButton }: ListWrapperProps) => {
+const ListWrapper = ({ showInstallButton, linkToMarketplaceDetail }: ListWrapperProps) => {
const { t } = useTranslation()
const {
@@ -45,6 +46,7 @@ const ListWrapper = ({ showInstallButton }: ListWrapperProps) => {
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap || {}}
plugins={plugins}
showInstallButton={showInstallButton}
+ linkToMarketplaceDetail={linkToMarketplaceDetail}
/>
)}
diff --git a/web/app/components/plugins/marketplace/utils.ts b/web/app/components/plugins/marketplace/utils.ts
index affae9efa72..acc77a84ebf 100644
--- a/web/app/components/plugins/marketplace/utils.ts
+++ b/web/app/components/plugins/marketplace/utils.ts
@@ -60,6 +60,16 @@ export const getPluginLinkInMarketplace = (
return getMarketplaceUrl(`/plugins/${plugin.org}/${plugin.name}`, params)
}
+export const getPluginDetailLinkInMarketplace = (
+ plugin: Pick,
+) => {
+ const org = encodeURIComponent(plugin.org)
+ const name = encodeURIComponent(plugin.name)
+
+ if (plugin.type === 'bundle') return `/bundles/${org}/${name}`
+ return `/plugin/${org}/${name}`
+}
+
export const getMarketplaceCategoryUrl = (
category?: string,
params?: Record,