feat: redesign marketplace homepage
@ -156,6 +156,21 @@ export type TemplateDetailResponse = {
|
||||
|
||||
export type DownloadPluginResponse = Blob
|
||||
|
||||
const bannerListContract = base
|
||||
.route({
|
||||
path: '/banners',
|
||||
method: 'GET',
|
||||
})
|
||||
.input(
|
||||
type<{
|
||||
query: {
|
||||
page: 'plugins'
|
||||
language: string
|
||||
}
|
||||
}>(),
|
||||
)
|
||||
.output(type<unknown>())
|
||||
|
||||
const collectionsContract = base
|
||||
.route({
|
||||
path: '/collections',
|
||||
@ -229,6 +244,9 @@ const downloadPluginContract = base
|
||||
.output(type<DownloadPluginResponse>())
|
||||
|
||||
export const marketplaceRouterContract = {
|
||||
banners: {
|
||||
list: bannerListContract,
|
||||
},
|
||||
collections: collectionsContract,
|
||||
collectionPlugins: collectionPluginsContract,
|
||||
searchAdvanced: searchAdvancedContract,
|
||||
|
||||
@ -15,6 +15,7 @@ type CarouselProps = Readonly<{
|
||||
opts?: CarouselOptions
|
||||
plugins?: CarouselPlugin
|
||||
orientation?: 'horizontal' | 'vertical'
|
||||
overlay?: React.ReactNode
|
||||
}>
|
||||
|
||||
type CarouselContextValue = {
|
||||
@ -49,7 +50,7 @@ type TCarousel = {
|
||||
>
|
||||
|
||||
const Carousel: TCarousel = React.forwardRef(
|
||||
({ orientation = 'horizontal', opts, plugins, className, children, ...props }, ref) => {
|
||||
({ orientation = 'horizontal', opts, plugins, overlay, className, children, ...props }, ref) => {
|
||||
const [carouselRef, api] = useEmblaCarousel(
|
||||
{ ...opts, axis: orientation === 'horizontal' ? 'x' : 'y' },
|
||||
plugins,
|
||||
@ -98,6 +99,35 @@ const Carousel: TCarousel = React.forwardRef(
|
||||
canScrollNext,
|
||||
}))
|
||||
|
||||
const carousel = overlay
|
||||
? (
|
||||
<div
|
||||
className={cn('relative', className)}
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
{...props}
|
||||
>
|
||||
{overlay}
|
||||
<div
|
||||
ref={carouselRef}
|
||||
className="overflow-hidden [border-radius:inherit]"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div
|
||||
ref={carouselRef}
|
||||
className={cn('relative overflow-hidden', className)}
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<CarouselContext.Provider
|
||||
value={{
|
||||
@ -112,16 +142,7 @@ const Carousel: TCarousel = React.forwardRef(
|
||||
canScrollNext,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={carouselRef}
|
||||
// onKeyDownCapture={handleKeyDown}
|
||||
className={cn('relative overflow-hidden', className)}
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{carousel}
|
||||
</CarouselContext.Provider>
|
||||
)
|
||||
},
|
||||
|
||||
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 427 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
BIN
web/app/components/plugins/marketplace/home/assets/red-brick.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
web/app/components/plugins/marketplace/home/assets/star.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
web/app/components/plugins/marketplace/home/assets/telescope.png
Normal file
|
After Width: | Height: | Size: 328 KiB |
121
web/app/components/plugins/marketplace/home/banners.spec.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { marketplaceClient } from '@/service/client'
|
||||
import { fetchPluginRecommendBanners } from './banners'
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
marketplaceClient: {
|
||||
banners: {
|
||||
list: vi.fn(),
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const mockedListBanners = vi.mocked(marketplaceClient.banners.list)
|
||||
|
||||
describe('fetchPluginRecommendBanners', () => {
|
||||
beforeEach(() => {
|
||||
mockedListBanners.mockReset()
|
||||
})
|
||||
|
||||
it('normalizes, sorts, and limits recommend banners from the public contract', async () => {
|
||||
mockedListBanners.mockResolvedValue({
|
||||
code: 0,
|
||||
msg: 'success',
|
||||
data: {
|
||||
banners: [
|
||||
{
|
||||
id: 'event',
|
||||
style_type: 'event',
|
||||
title: 'Event',
|
||||
sort: 0,
|
||||
language: 'en',
|
||||
content: {},
|
||||
},
|
||||
{
|
||||
id: 'recommend-2',
|
||||
style_type: 'recommend',
|
||||
title: 'Second',
|
||||
sort: 2,
|
||||
language: 'en',
|
||||
content: {
|
||||
cards: [
|
||||
{
|
||||
item_type: 'plugin',
|
||||
item_id: 'langgenius/fifth',
|
||||
display_name: 'Fifth',
|
||||
link: '/plugins/langgenius/fifth',
|
||||
card_position: 4,
|
||||
},
|
||||
{
|
||||
item_type: 'plugin',
|
||||
item_id: 'langgenius/first',
|
||||
display_name: 'First',
|
||||
icon_url: '/api/v1/plugins/langgenius/first/icon',
|
||||
link: '/plugins/langgenius/first',
|
||||
card_position: 0,
|
||||
},
|
||||
{
|
||||
item_type: 'plugin',
|
||||
item_id: 'langgenius/third',
|
||||
display_name: 'Third',
|
||||
link: '/plugins/langgenius/third',
|
||||
card_position: 2,
|
||||
},
|
||||
{
|
||||
item_type: 'plugin',
|
||||
item_id: 'langgenius/second',
|
||||
display_name: 'Second',
|
||||
link: '/plugins/langgenius/second',
|
||||
card_position: 1,
|
||||
},
|
||||
{
|
||||
item_type: 'plugin',
|
||||
item_id: 'langgenius/fourth',
|
||||
display_name: 'Fourth',
|
||||
link: '/plugins/langgenius/fourth',
|
||||
card_position: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'recommend-1',
|
||||
style_type: 'recommend',
|
||||
title: 'First',
|
||||
sort: 1,
|
||||
language: 'en',
|
||||
content: {
|
||||
cards: [
|
||||
{
|
||||
item_type: 'plugin',
|
||||
item_id: 'langgenius/agent',
|
||||
display_name: 'Agent',
|
||||
link: '/plugins/langgenius/agent',
|
||||
card_position: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const banners = await fetchPluginRecommendBanners('en-US')
|
||||
|
||||
expect(mockedListBanners).toHaveBeenCalledWith({
|
||||
query: {
|
||||
page: 'plugins',
|
||||
language: 'en-US',
|
||||
},
|
||||
})
|
||||
expect(banners.map(banner => banner.id)).toEqual(['recommend-1', 'recommend-2'])
|
||||
expect(banners[1]!.content.cards.map(card => card.display_name))
|
||||
.toEqual(['First', 'Second', 'Third', 'Fourth'])
|
||||
})
|
||||
|
||||
it('returns no banners for an empty response', async () => {
|
||||
mockedListBanners.mockResolvedValue('')
|
||||
|
||||
await expect(fetchPluginRecommendBanners('en-US')).resolves.toEqual([])
|
||||
})
|
||||
})
|
||||
127
web/app/components/plugins/marketplace/home/banners.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { marketplaceClient } from '@/service/client'
|
||||
|
||||
const MAX_TRENDING_PAGES = 3
|
||||
const MAX_CARDS_PER_PAGE = 4
|
||||
|
||||
export type BannerRecommendCard = {
|
||||
item_type: 'plugin' | 'template'
|
||||
item_id: string
|
||||
display_name: string
|
||||
icon_url?: string
|
||||
icon?: string
|
||||
icon_background?: string
|
||||
link: string
|
||||
card_position: number
|
||||
}
|
||||
|
||||
export type BannerRecommend = {
|
||||
id: string
|
||||
style_type: 'recommend'
|
||||
title: string
|
||||
sort: number
|
||||
language: string
|
||||
content: {
|
||||
theme_type?: string
|
||||
heading?: string
|
||||
subheadings?: string[]
|
||||
description?: string
|
||||
cards: BannerRecommendCard[]
|
||||
}
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> => {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
const parseRecommendCard = (value: unknown): BannerRecommendCard | null => {
|
||||
if (!isRecord(value))
|
||||
return null
|
||||
|
||||
const itemType = value.item_type
|
||||
const itemId = value.item_id
|
||||
const displayName = value.display_name
|
||||
if (
|
||||
(itemType !== 'plugin' && itemType !== 'template')
|
||||
|| typeof itemId !== 'string'
|
||||
|| !itemId
|
||||
|| typeof displayName !== 'string'
|
||||
|| !displayName
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
item_type: itemType,
|
||||
item_id: itemId,
|
||||
display_name: displayName,
|
||||
icon_url: typeof value.icon_url === 'string' ? value.icon_url : undefined,
|
||||
icon: typeof value.icon === 'string' ? value.icon : undefined,
|
||||
icon_background: typeof value.icon_background === 'string' ? value.icon_background : undefined,
|
||||
link: typeof value.link === 'string' ? value.link : '',
|
||||
card_position: typeof value.card_position === 'number' ? value.card_position : 0,
|
||||
}
|
||||
}
|
||||
|
||||
const parseRecommendBanner = (value: unknown): BannerRecommend | null => {
|
||||
if (!isRecord(value) || value.style_type !== 'recommend' || !isRecord(value.content))
|
||||
return null
|
||||
|
||||
const cards = Array.isArray(value.content.cards)
|
||||
? value.content.cards
|
||||
.map(parseRecommendCard)
|
||||
.filter((card): card is BannerRecommendCard => Boolean(card))
|
||||
.sort((a, b) => a.card_position - b.card_position)
|
||||
.slice(0, MAX_CARDS_PER_PAGE)
|
||||
: []
|
||||
|
||||
if (
|
||||
typeof value.id !== 'string'
|
||||
|| typeof value.title !== 'string'
|
||||
|| typeof value.sort !== 'number'
|
||||
|| typeof value.language !== 'string'
|
||||
|| cards.length === 0
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const subheadings = Array.isArray(value.content.subheadings)
|
||||
? value.content.subheadings.filter((item): item is string => typeof item === 'string')
|
||||
: undefined
|
||||
|
||||
return {
|
||||
id: value.id,
|
||||
style_type: 'recommend',
|
||||
title: value.title,
|
||||
sort: value.sort,
|
||||
language: value.language,
|
||||
content: {
|
||||
theme_type: typeof value.content.theme_type === 'string' ? value.content.theme_type : undefined,
|
||||
heading: typeof value.content.heading === 'string' ? value.content.heading : undefined,
|
||||
subheadings,
|
||||
description: typeof value.content.description === 'string' ? value.content.description : undefined,
|
||||
cards,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const normalizePluginRecommendBanners = (response: unknown): BannerRecommend[] => {
|
||||
if (!isRecord(response) || !isRecord(response.data) || !Array.isArray(response.data.banners))
|
||||
return []
|
||||
|
||||
return response.data.banners
|
||||
.map(parseRecommendBanner)
|
||||
.filter((banner): banner is BannerRecommend => Boolean(banner))
|
||||
.sort((a, b) => a.sort - b.sort)
|
||||
.slice(0, MAX_TRENDING_PAGES)
|
||||
}
|
||||
|
||||
export const fetchPluginRecommendBanners = async (language: string): Promise<BannerRecommend[]> => {
|
||||
const response = await marketplaceClient.banners.list({
|
||||
query: {
|
||||
page: 'plugins',
|
||||
language,
|
||||
},
|
||||
})
|
||||
|
||||
return normalizePluginRecommendBanners(response)
|
||||
}
|
||||
54
web/app/components/plugins/marketplace/home/home-header.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
'use client'
|
||||
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useTranslation } from '#i18n'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import Link from '@/next/link'
|
||||
|
||||
type HomeHeaderProps = {
|
||||
actions?: React.ReactNode
|
||||
isMarketplacePlatform: boolean
|
||||
}
|
||||
|
||||
const CreatorCenter = () => (
|
||||
<Link href="https://creators.dify.ai/" target="_blank" rel="noopener noreferrer">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="flex items-center gap-1 px-3 py-2 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary"
|
||||
>
|
||||
<span className="i-ri-user-star-line size-4" />
|
||||
<span className="hidden system-sm-medium lg:inline">Creator Center</span>
|
||||
</Button>
|
||||
</Link>
|
||||
)
|
||||
|
||||
const HomeHeader = ({
|
||||
actions,
|
||||
isMarketplacePlatform,
|
||||
}: HomeHeaderProps) => {
|
||||
const { t } = useTranslation('plugin')
|
||||
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
'sticky top-0 z-50 flex w-full shrink-0 items-center justify-between bg-background-default px-4 backdrop-blur-sm md:px-9',
|
||||
isMarketplacePlatform ? 'h-[46px]' : 'h-11',
|
||||
)}
|
||||
>
|
||||
<Link href="/" className="flex h-full w-[142px] items-center">
|
||||
<DifyLogo size="small" className="h-[18px] w-[39px] shrink-0" />
|
||||
<span className="ml-1 whitespace-nowrap text-[13px] leading-[15px] font-semibold text-text-primary">
|
||||
{t(($) => $['marketplace.difyMarketplace'])}
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<div className="flex h-full items-center gap-0.5">
|
||||
<CreatorCenter />
|
||||
{actions}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeHeader
|
||||
89
web/app/components/plugins/marketplace/home/home-hero.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
'use client'
|
||||
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useTranslation } from '#i18n'
|
||||
import blueBrick from './assets/blue-brick.png'
|
||||
import greenFlag from './assets/green-flag.png'
|
||||
import magnifyingGlass from './assets/magnifying-glass.png'
|
||||
import redBrick from './assets/red-brick.png'
|
||||
import star from './assets/star.png'
|
||||
import telescope from './assets/telescope.png'
|
||||
|
||||
type HomeHeroProps = {
|
||||
isMarketplacePlatform: boolean
|
||||
}
|
||||
|
||||
const HomeHero = ({ isMarketplacePlatform }: HomeHeroProps) => {
|
||||
const { t } = useTranslation('plugin')
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
'relative flex shrink-0 justify-center bg-background-default px-4',
|
||||
!isMarketplacePlatform && 'pt-6',
|
||||
)}
|
||||
>
|
||||
<div className="relative flex h-[162px] w-full max-w-[726px] flex-col items-center pt-[41px]">
|
||||
<div className="relative z-10 flex flex-col items-center gap-2 text-center">
|
||||
<h1 className="text-[28px] leading-[34px] font-semibold tracking-[-0.56px] text-text-primary">
|
||||
{t(($) => $['marketplace.home.heroTitle'])}
|
||||
</h1>
|
||||
<p className="text-[13px] leading-4 font-light tracking-[-0.065px] text-text-tertiary">
|
||||
{t(($) => $['marketplace.home.heroSubtitle'])}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src={blueBrick.src}
|
||||
width={93}
|
||||
height={91}
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute top-1/2 left-0 h-[91px] w-[93px] -translate-y-1/2 -rotate-[44deg] select-none"
|
||||
/>
|
||||
<img
|
||||
src={magnifyingGlass.src}
|
||||
width={26}
|
||||
height={22}
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute top-[28px] left-[169px] hidden h-[22px] w-[26px] select-none opacity-[0.88] sm:block"
|
||||
/>
|
||||
<img
|
||||
src={star.src}
|
||||
width={16}
|
||||
height={16}
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute top-3 left-[294px] hidden size-4 -scale-y-100 select-none opacity-[0.88] sm:block"
|
||||
/>
|
||||
<img
|
||||
src={greenFlag.src}
|
||||
width={24}
|
||||
height={24}
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute top-0 left-[409px] hidden size-6 rotate-45 select-none opacity-[0.88] sm:block"
|
||||
/>
|
||||
<img
|
||||
src={redBrick.src}
|
||||
width={21}
|
||||
height={20}
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute top-[23px] left-[541px] hidden h-5 w-[21px] -rotate-[60deg] select-none opacity-[0.88] sm:block"
|
||||
/>
|
||||
<img
|
||||
src={telescope.src}
|
||||
width={93}
|
||||
height={93}
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute top-1/2 right-0 size-[93px] -translate-y-1/2 rotate-[15deg] select-none"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeHero
|
||||
53
web/app/components/plugins/marketplace/home/home-search.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
'use client'
|
||||
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from '#i18n'
|
||||
import SearchBoxWrapper from '@/app/components/plugins/marketplace/search-box/search-box-wrapper'
|
||||
|
||||
type HomeSearchProps = {
|
||||
isMarketplacePlatform: boolean
|
||||
}
|
||||
|
||||
const HomeSearch = ({ isMarketplacePlatform }: HomeSearchProps) => {
|
||||
const searchRef = useRef<HTMLDivElement>(null)
|
||||
const { t } = useTranslation('plugin')
|
||||
|
||||
useEffect(() => {
|
||||
const handleGlobalSearchShortcut = (event: KeyboardEvent) => {
|
||||
if (event.key.toLowerCase() !== 'k' || (!event.metaKey && !event.ctrlKey))
|
||||
return
|
||||
|
||||
event.preventDefault()
|
||||
searchRef.current?.querySelector('input')?.focus()
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleGlobalSearchShortcut)
|
||||
return () => document.removeEventListener('keydown', handleGlobalSearchShortcut)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'sticky z-[60] -mt-9 flex h-9 shrink-0 justify-center px-4',
|
||||
isMarketplacePlatform ? 'top-[5px]' : 'top-1',
|
||||
)}
|
||||
>
|
||||
<div ref={searchRef} className="relative w-full max-w-[480px]">
|
||||
<SearchBoxWrapper
|
||||
wrapperClassName="w-full max-w-none"
|
||||
inputClassName="h-9 w-full rounded-[10px] border-divider-subtle bg-components-input-bg-normal shadow-xs"
|
||||
inputElementClassName="pr-12"
|
||||
placeholder={t(($) => $['marketplace.home.searchPlaceholder'])}
|
||||
showTags={false}
|
||||
usedInMarketplace={false}
|
||||
/>
|
||||
<kbd className="pointer-events-none absolute top-1/2 right-2 flex h-5 -translate-y-1/2 items-center rounded-md border border-divider-subtle bg-components-kbd-bg-gray px-1.5 font-sans text-[10px] leading-3 font-medium text-text-tertiary shadow-xs">
|
||||
⌘ K
|
||||
</kbd>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeSearch
|
||||
@ -0,0 +1,33 @@
|
||||
@property --trending-progress-angle {
|
||||
syntax: '<angle>';
|
||||
inherits: false;
|
||||
initial-value: 0deg;
|
||||
}
|
||||
|
||||
.progress {
|
||||
--trending-progress-angle: 0deg;
|
||||
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 7px;
|
||||
background: conic-gradient(
|
||||
from 0deg,
|
||||
var(--color-text-primary) var(--trending-progress-angle),
|
||||
transparent var(--trending-progress-angle)
|
||||
);
|
||||
animation-name: progress;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes progress {
|
||||
to {
|
||||
--trending-progress-angle: 360deg;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.progress {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
377
web/app/components/plugins/marketplace/home/home-trending.tsx
Normal file
@ -0,0 +1,377 @@
|
||||
'use client'
|
||||
|
||||
import type { FocusEvent } from 'react'
|
||||
import type { BannerRecommend, BannerRecommendCard } from './banners'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { Carousel, useCarousel } from '@/app/components/base/carousel'
|
||||
import { MARKETPLACE_API_PREFIX } from '@/config'
|
||||
import Link from '@/next/link'
|
||||
import background from './assets/background.jpg'
|
||||
import styles from './home-trending-indicator.module.css'
|
||||
|
||||
const AUTOPLAY_DELAY = 5000
|
||||
|
||||
type TrendingIndicatorProps = {
|
||||
index: number
|
||||
label: string
|
||||
isCurrent: boolean
|
||||
isNextSlide: boolean
|
||||
isPaused: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
const TrendingIndicator = ({
|
||||
index,
|
||||
label,
|
||||
isCurrent,
|
||||
isNextSlide,
|
||||
isPaused,
|
||||
onClick,
|
||||
}: TrendingIndicatorProps) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={label}
|
||||
aria-current={isCurrent ? 'true' : undefined}
|
||||
onClick={onClick}
|
||||
className="group relative flex size-6 shrink-0 items-center justify-center rounded-lg p-0 hover:bg-transparent"
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'relative flex h-5 w-[22px] items-center justify-center overflow-hidden rounded-[7px] p-px ring-1 ring-divider-subtle ring-inset',
|
||||
isCurrent && 'bg-text-primary ring-text-primary',
|
||||
)}
|
||||
>
|
||||
{isNextSlide && !isCurrent && !isPaused
|
||||
? (
|
||||
<span
|
||||
data-progress-ring
|
||||
className={styles.progress}
|
||||
aria-hidden="true"
|
||||
style={{ animationDuration: `${AUTOPLAY_DELAY}ms` }}
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
<span className="relative z-10 flex h-[18px] w-5 items-center justify-center rounded-md bg-components-panel-on-panel-item-bg p-0.5 text-center text-[10px] leading-3 font-semibold text-text-tertiary transition-colors group-hover:text-text-secondary group-aria-[current=true]:bg-text-primary group-aria-[current=true]:text-components-panel-on-panel-item-bg">
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
type TrendingCopyProps = {
|
||||
banners: BannerRecommend[]
|
||||
isMarketplacePlatform: boolean
|
||||
}
|
||||
|
||||
const TrendingCopy = ({
|
||||
banners,
|
||||
isMarketplacePlatform,
|
||||
}: TrendingCopyProps) => {
|
||||
const { t } = useTranslation('plugin')
|
||||
const { api, selectedIndex } = useCarousel()
|
||||
const [isPlaying, setIsPlaying] = useState(false)
|
||||
const shouldResumeAfterFocusRef = useRef(false)
|
||||
const nextIndex = (selectedIndex + 1) % banners.length
|
||||
|
||||
const pauseRotationForFocus = () => {
|
||||
const autoplay = api?.plugins().autoplay
|
||||
if (!autoplay?.isPlaying())
|
||||
return
|
||||
|
||||
shouldResumeAfterFocusRef.current = true
|
||||
autoplay.stop()
|
||||
}
|
||||
|
||||
const resumeRotationAfterFocus = (event: FocusEvent<HTMLDivElement>) => {
|
||||
if (event.currentTarget.contains(event.relatedTarget))
|
||||
return
|
||||
if (!shouldResumeAfterFocusRef.current)
|
||||
return
|
||||
|
||||
shouldResumeAfterFocusRef.current = false
|
||||
api?.plugins().autoplay?.play()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!api)
|
||||
return
|
||||
|
||||
const handleAutoplayPlay = () => setIsPlaying(true)
|
||||
const handleAutoplayStop = () => setIsPlaying(false)
|
||||
|
||||
// oxlint-disable-next-line eslint-react/set-state-in-effect -- Embla owns this external playback state.
|
||||
setIsPlaying(api.plugins().autoplay?.isPlaying() ?? false)
|
||||
api.on('autoplay:play', handleAutoplayPlay)
|
||||
api.on('autoplay:stop', handleAutoplayStop)
|
||||
|
||||
return () => {
|
||||
api.off('autoplay:play', handleAutoplayPlay)
|
||||
api.off('autoplay:stop', handleAutoplayStop)
|
||||
}
|
||||
}, [api])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex h-[200px] w-full flex-col gap-2.5 overflow-hidden bg-background-body p-4',
|
||||
isMarketplacePlatform
|
||||
? 'min-[1232px]:absolute min-[1232px]:top-0 min-[1232px]:right-full min-[1232px]:w-[443px]'
|
||||
: 'min-[1260px]:absolute min-[1260px]:top-0 min-[1260px]:right-full min-[1260px]:w-[431px]',
|
||||
)}
|
||||
>
|
||||
<div className="flex min-h-0 flex-1 flex-col items-start gap-2 overflow-hidden">
|
||||
<p className="shrink-0 text-[10px] leading-3 font-semibold tracking-[-0.2px] text-text-accent">
|
||||
{t(($) => $['marketplace.home.trendingEyebrow'])}
|
||||
</p>
|
||||
<h2
|
||||
id="home-trending-title"
|
||||
className="shrink-0 text-xl leading-6 font-semibold tracking-[-0.4px] text-text-primary"
|
||||
>
|
||||
{t(($) => $['marketplace.home.trendingTitle'])}
|
||||
</h2>
|
||||
<p className="text-[13px] leading-5 font-normal tracking-[-0.065px] text-text-tertiary">
|
||||
{t(($) => $['marketplace.home.trendingDescription'])}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
role="group"
|
||||
aria-label={t(($) => $['marketplace.home.trendingPaginationLabel'])}
|
||||
className="flex shrink-0 items-center py-1 pr-10"
|
||||
onFocusCapture={pauseRotationForFocus}
|
||||
onBlurCapture={resumeRotationAfterFocus}
|
||||
>
|
||||
<div className="flex items-center gap-0.5">
|
||||
{banners.map((banner, index) => (
|
||||
<TrendingIndicator
|
||||
key={banner.id}
|
||||
index={index}
|
||||
label={`${String(index + 1).padStart(2, '0')} ${banner.title}`}
|
||||
isCurrent={index === selectedIndex}
|
||||
isNextSlide={index === nextIndex}
|
||||
isPaused={!isPlaying}
|
||||
onClick={() => api?.scrollTo(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const getMarketplaceAssetURL = (path?: string) => {
|
||||
if (!path)
|
||||
return ''
|
||||
if (/^https?:\/\//.test(path))
|
||||
return path
|
||||
|
||||
try {
|
||||
const apiURL = new URL(MARKETPLACE_API_PREFIX)
|
||||
if (path.startsWith('/api/'))
|
||||
return `${apiURL.origin}${path}`
|
||||
return `${MARKETPLACE_API_PREFIX.replace(/\/$/, '')}/${path.replace(/^\//, '')}`
|
||||
}
|
||||
catch {
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalCardHref = (card: BannerRecommendCard) => {
|
||||
if (card.item_type === 'plugin') {
|
||||
const [organization, pluginName] = card.item_id.split('/')
|
||||
if (organization && pluginName)
|
||||
return `/plugin/${encodeURIComponent(organization)}/${encodeURIComponent(pluginName)}`
|
||||
}
|
||||
|
||||
if (card.item_type === 'template')
|
||||
return `/templates?tid=${encodeURIComponent(card.item_id)}`
|
||||
|
||||
return '/'
|
||||
}
|
||||
|
||||
const getCardHref = (
|
||||
card: BannerRecommendCard,
|
||||
isMarketplacePlatform: boolean,
|
||||
) => {
|
||||
if (!isMarketplacePlatform && card.link)
|
||||
return card.link
|
||||
return getLocalCardHref(card)
|
||||
}
|
||||
|
||||
const getCardCreator = (card: BannerRecommendCard) => {
|
||||
if (card.item_type !== 'plugin')
|
||||
return ''
|
||||
|
||||
return card.item_id.split('/')[0] || ''
|
||||
}
|
||||
|
||||
type TrendingCardProps = {
|
||||
card: BannerRecommendCard
|
||||
isMarketplacePlatform: boolean
|
||||
}
|
||||
|
||||
const TrendingCard = ({
|
||||
card,
|
||||
isMarketplacePlatform,
|
||||
}: TrendingCardProps) => {
|
||||
const { t } = useTranslation('plugin')
|
||||
const iconURL = getMarketplaceAssetURL(card.icon_url)
|
||||
const creator = getCardCreator(card)
|
||||
const href = getCardHref(card, isMarketplacePlatform)
|
||||
const opensInNewTab = !isMarketplacePlatform && /^https?:\/\//.test(href)
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
target={opensInNewTab ? '_blank' : undefined}
|
||||
rel={opensInNewTab ? 'noopener noreferrer' : undefined}
|
||||
aria-label={card.display_name}
|
||||
className="relative flex h-[116px] w-[161px] shrink-0 flex-col items-start overflow-hidden rounded-lg bg-components-panel-on-panel-item-bg-transparent p-3.5 shadow-md backdrop-blur-md"
|
||||
>
|
||||
<div
|
||||
className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-[10px] border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge bg-cover bg-center bg-no-repeat"
|
||||
style={{
|
||||
backgroundColor: !iconURL ? card.icon_background : undefined,
|
||||
backgroundImage: iconURL ? `url(${iconURL})` : undefined,
|
||||
}}
|
||||
>
|
||||
{!iconURL && card.icon
|
||||
? <span className="text-xl leading-none">{card.icon}</span>
|
||||
: null}
|
||||
{!iconURL && !card.icon
|
||||
? <span aria-hidden="true" className="i-ri-image-line size-5 text-text-quaternary" />
|
||||
: null}
|
||||
</div>
|
||||
|
||||
<h3 className="mt-3 w-full truncate text-sm leading-[normal] font-medium text-text-primary">
|
||||
{card.display_name}
|
||||
</h3>
|
||||
{creator
|
||||
? (
|
||||
<p className="mt-[3px] w-full truncate text-xs leading-[normal] font-normal text-text-tertiary">
|
||||
{t(($) => $['marketplace.home.trendingByCreator'], { creator })}
|
||||
</p>
|
||||
)
|
||||
: null}
|
||||
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="i-ri-arrow-right-up-line absolute top-1.5 right-2 size-4 text-text-quaternary"
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
type TrendingSlideProps = {
|
||||
banner: BannerRecommend
|
||||
isMarketplacePlatform: boolean
|
||||
}
|
||||
|
||||
const TrendingSlide = ({
|
||||
banner,
|
||||
isMarketplacePlatform,
|
||||
}: TrendingSlideProps) => {
|
||||
return (
|
||||
<div className="relative h-[200px] w-full overflow-hidden rounded-xl bg-text-accent">
|
||||
<img
|
||||
src={background.src}
|
||||
width={3840}
|
||||
height={2160}
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="absolute top-[-173px] left-[-990px] h-[1201px] w-[2135px] max-w-none opacity-80"
|
||||
/>
|
||||
<div aria-hidden className="absolute inset-0 bg-text-accent mix-blend-color" />
|
||||
|
||||
<div className="relative z-10 flex h-full items-center justify-between px-9 py-[42px]">
|
||||
{banner.content.cards.map(card => (
|
||||
<TrendingCard
|
||||
key={`${card.item_type}:${card.item_id}`}
|
||||
card={card}
|
||||
isMarketplacePlatform={isMarketplacePlatform}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type HomeTrendingProps = {
|
||||
banners: BannerRecommend[]
|
||||
isMarketplacePlatform: boolean
|
||||
}
|
||||
|
||||
const HomeTrending = ({
|
||||
banners,
|
||||
isMarketplacePlatform,
|
||||
}: HomeTrendingProps) => {
|
||||
const { t } = useTranslation('plugin')
|
||||
const [carouselPlugins] = useState(() => [
|
||||
Carousel.Plugin.Autoplay({
|
||||
delay: AUTOPLAY_DELAY,
|
||||
stopOnFocusIn: true,
|
||||
stopOnInteraction: false,
|
||||
stopOnMouseEnter: true,
|
||||
breakpoints: {
|
||||
'(prefers-reduced-motion: reduce)': { active: false },
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
if (banners.length === 0)
|
||||
return null
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-labelledby="home-trending-title"
|
||||
className={cn(
|
||||
'shrink-0 bg-background-default pb-6',
|
||||
isMarketplacePlatform
|
||||
? 'px-4 min-[1232px]:px-0'
|
||||
: 'px-4 md:px-9',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'mx-auto w-full overflow-hidden rounded-xl bg-background-body',
|
||||
isMarketplacePlatform ? 'max-w-[1200px]' : 'max-w-[1188px]',
|
||||
)}
|
||||
>
|
||||
<Carousel
|
||||
opts={{ loop: true }}
|
||||
plugins={carouselPlugins}
|
||||
overlay={(
|
||||
<TrendingCopy
|
||||
banners={banners}
|
||||
isMarketplacePlatform={isMarketplacePlatform}
|
||||
/>
|
||||
)}
|
||||
aria-label={t(($) => $['marketplace.home.trendingTitle'])}
|
||||
className={cn(
|
||||
'ml-auto w-full rounded-xl',
|
||||
isMarketplacePlatform
|
||||
? 'min-[1232px]:w-[757px]'
|
||||
: 'min-[1260px]:w-[757px]',
|
||||
)}
|
||||
>
|
||||
<Carousel.Content aria-live="polite" className="rounded-xl">
|
||||
{banners.map(banner => (
|
||||
<Carousel.Item key={banner.id}>
|
||||
<TrendingSlide
|
||||
banner={banner}
|
||||
isMarketplacePlatform={isMarketplacePlatform}
|
||||
/>
|
||||
</Carousel.Item>
|
||||
))}
|
||||
</Carousel.Content>
|
||||
</Carousel>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeTrending
|
||||
52
web/app/components/plugins/marketplace/home/index.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import type { BannerRecommend } from './banners'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import ListWrapper from '../list/list-wrapper'
|
||||
import HomeHeader from './home-header'
|
||||
import HomeHero from './home-hero'
|
||||
import HomeSearch from './home-search'
|
||||
import HomeTrending from './home-trending'
|
||||
|
||||
type MarketplaceHomeProps = {
|
||||
actions?: React.ReactNode
|
||||
banners: BannerRecommend[]
|
||||
isMarketplacePlatform: boolean
|
||||
linkToMarketplaceDetail: boolean
|
||||
showInstallButton: boolean
|
||||
}
|
||||
|
||||
const MarketplaceHome = ({
|
||||
actions,
|
||||
banners,
|
||||
isMarketplacePlatform,
|
||||
linkToMarketplaceDetail,
|
||||
showInstallButton,
|
||||
}: MarketplaceHomeProps) => {
|
||||
return (
|
||||
<div className="flex min-h-full w-full flex-col bg-background-default">
|
||||
<HomeHeader
|
||||
actions={actions}
|
||||
isMarketplacePlatform={isMarketplacePlatform}
|
||||
/>
|
||||
<div className="relative flex w-full flex-col">
|
||||
<HomeHero isMarketplacePlatform={isMarketplacePlatform} />
|
||||
<HomeSearch isMarketplacePlatform={isMarketplacePlatform} />
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={cn('shrink-0', isMarketplacePlatform ? 'h-6' : 'h-12')}
|
||||
/>
|
||||
<HomeTrending
|
||||
banners={banners}
|
||||
isMarketplacePlatform={isMarketplacePlatform}
|
||||
/>
|
||||
<div className="contents [&>div]:bg-background-default!">
|
||||
<ListWrapper
|
||||
showInstallButton={showInstallButton}
|
||||
linkToMarketplaceDetail={linkToMarketplaceDetail}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MarketplaceHome
|
||||
@ -1,17 +1,26 @@
|
||||
import type { SearchParams } from 'nuqs'
|
||||
import type { BannerRecommend } from './home/banners'
|
||||
import { PluginInstallPermissionProviderGuard } from '@/app/components/plugins/install-plugin/components/plugin-install-permission-provider'
|
||||
import { TanStackQueryProvider } from '@/app/query-provider'
|
||||
import { getLocaleOnServer } from '@/i18n-config/server'
|
||||
import Description from './description'
|
||||
import MarketplaceHome from './home'
|
||||
import { fetchPluginRecommendBanners } from './home/banners'
|
||||
import { HydrateQueryClient } from './hydration-server'
|
||||
import ListWrapper from './list/list-wrapper'
|
||||
import StickySearchAndSwitchWrapper from './sticky-search-and-switch-wrapper'
|
||||
|
||||
type MarketplaceVariant = 'default' | 'home'
|
||||
|
||||
type MarketplaceProps = {
|
||||
showInstallButton?: boolean
|
||||
linkToMarketplaceDetail?: boolean
|
||||
pluginTypeSwitchClassName?: string
|
||||
isMarketplacePlatform?: boolean
|
||||
marketplaceNav?: React.ReactNode
|
||||
variant?: MarketplaceVariant
|
||||
language?: string
|
||||
homeHeaderActions?: React.ReactNode
|
||||
/**
|
||||
* Pass the search params from the request to prefetch data on the server.
|
||||
*/
|
||||
@ -24,23 +33,53 @@ const Marketplace = async ({
|
||||
pluginTypeSwitchClassName,
|
||||
isMarketplacePlatform = false,
|
||||
marketplaceNav,
|
||||
variant = 'default',
|
||||
language,
|
||||
homeHeaderActions,
|
||||
searchParams,
|
||||
}: MarketplaceProps) => {
|
||||
let trendingBanners: BannerRecommend[] = []
|
||||
|
||||
if (variant === 'home') {
|
||||
const locale = language ?? await getLocaleOnServer()
|
||||
|
||||
try {
|
||||
trendingBanners = await fetchPluginRecommendBanners(locale)
|
||||
}
|
||||
catch {
|
||||
// Keep the homepage available if Marketplace banner delivery is unavailable.
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<TanStackQueryProvider>
|
||||
<HydrateQueryClient searchParams={searchParams}>
|
||||
<PluginInstallPermissionProviderGuard canInstallPlugin={showInstallButton}>
|
||||
<Description
|
||||
isMarketplacePlatform={isMarketplacePlatform}
|
||||
marketplaceNav={marketplaceNav}
|
||||
/>
|
||||
{!isMarketplacePlatform && (
|
||||
<StickySearchAndSwitchWrapper pluginTypeSwitchClassName={pluginTypeSwitchClassName} />
|
||||
)}
|
||||
<ListWrapper
|
||||
showInstallButton={showInstallButton}
|
||||
linkToMarketplaceDetail={linkToMarketplaceDetail}
|
||||
/>
|
||||
{variant === 'home'
|
||||
? (
|
||||
<MarketplaceHome
|
||||
actions={homeHeaderActions}
|
||||
banners={trendingBanners}
|
||||
isMarketplacePlatform={isMarketplacePlatform}
|
||||
linkToMarketplaceDetail={linkToMarketplaceDetail}
|
||||
showInstallButton={showInstallButton}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<Description
|
||||
isMarketplacePlatform={isMarketplacePlatform}
|
||||
marketplaceNav={marketplaceNav}
|
||||
/>
|
||||
{!isMarketplacePlatform && (
|
||||
<StickySearchAndSwitchWrapper pluginTypeSwitchClassName={pluginTypeSwitchClassName} />
|
||||
)}
|
||||
<ListWrapper
|
||||
showInstallButton={showInstallButton}
|
||||
linkToMarketplaceDetail={linkToMarketplaceDetail}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</PluginInstallPermissionProviderGuard>
|
||||
</HydrateQueryClient>
|
||||
</TanStackQueryProvider>
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "سوق Dify",
|
||||
"marketplace.discover": "اكتشف",
|
||||
"marketplace.empower": "تمكين تطوير الذكاء الاصطناعي الخاص بك",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "المزيد من السوق",
|
||||
"marketplace.noPluginFound": "لم يتم العثور على إضافة",
|
||||
"marketplace.partnerTip": "تم التحقق بواسطة شريك Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Dify Marktplatz",
|
||||
"marketplace.discover": "Entdecken",
|
||||
"marketplace.empower": "Unterstützen Sie Ihre KI-Entwicklung",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Mehr aus dem Marketplace",
|
||||
"marketplace.noPluginFound": "Kein Plugin gefunden",
|
||||
"marketplace.partnerTip": "Von einem Dify-Partner verifiziert",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Dify Marketplace",
|
||||
"marketplace.discover": "Discover",
|
||||
"marketplace.empower": "Empower your AI development",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "More from Marketplace",
|
||||
"marketplace.noPluginFound": "No integration found",
|
||||
"marketplace.partnerTip": "Verified by a Dify partner",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Mercado de Dify",
|
||||
"marketplace.discover": "Descubrir",
|
||||
"marketplace.empower": "Potencie su desarrollo de IA",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Más de Marketplace",
|
||||
"marketplace.noPluginFound": "No se ha encontrado ninguna integración",
|
||||
"marketplace.partnerTip": "Verificado por un socio de Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "بازار دیفی",
|
||||
"marketplace.discover": "کشف",
|
||||
"marketplace.empower": "توسعه هوش مصنوعی خود را توانمند کنید",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "اطلاعات بیشتر از Marketplace",
|
||||
"marketplace.noPluginFound": "هیچ افزونهای یافت نشد",
|
||||
"marketplace.partnerTip": "تأیید شده توسط یک شریک دیفی",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Marché Dify",
|
||||
"marketplace.discover": "Découvrir",
|
||||
"marketplace.empower": "Renforcez le développement de votre IA",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Plus de Marketplace",
|
||||
"marketplace.noPluginFound": "Aucune intégration trouvée",
|
||||
"marketplace.partnerTip": "Vérifié par un partenaire Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "डिफाई मार्केटप्लेस",
|
||||
"marketplace.discover": "खोजें",
|
||||
"marketplace.empower": "अपने एआई विकास को सशक्त बनाएं",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "मार्केटप्लेस से अधिक",
|
||||
"marketplace.noPluginFound": "कोई इंटीग्रेशन नहीं मिला",
|
||||
"marketplace.partnerTip": "Dify भागीदार द्वारा सत्यापित",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Dify Marketplace",
|
||||
"marketplace.discover": "Menemukan",
|
||||
"marketplace.empower": "Berdayakan pengembangan AI Anda",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Selengkapnya dari Marketplace",
|
||||
"marketplace.noPluginFound": "Tidak ada integrasi yang ditemukan",
|
||||
"marketplace.partnerTip": "Diverifikasi oleh partner Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Mercato Dify",
|
||||
"marketplace.discover": "Scoprire",
|
||||
"marketplace.empower": "Potenzia lo sviluppo dell'intelligenza artificiale",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Altro da Marketplace",
|
||||
"marketplace.noPluginFound": "Nessuna integrazione trovata",
|
||||
"marketplace.partnerTip": "Verificato da un partner Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Dify マーケットプレイス",
|
||||
"marketplace.discover": "探索",
|
||||
"marketplace.empower": "AI 開発をサポートする",
|
||||
"marketplace.home.heroSubtitle": "Dify Marketplace で、より安全で信頼性の高いプラグインを見つけましょう。",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "プラグインまたはテンプレートを検索",
|
||||
"marketplace.home.trendingByCreator": "{{creator}} 作成",
|
||||
"marketplace.home.trendingDescription": "実際の利用状況に基づく人気プラグインを2週間ごとに更新。ワークスペースでの実行数によるランキングで、有料掲載や編集部による選定はありません。",
|
||||
"marketplace.home.trendingEyebrow": "トレンド",
|
||||
"marketplace.home.trendingPaginationLabel": "トレンドページ",
|
||||
"marketplace.home.trendingTitle": "みんながインストールしているプラグイン",
|
||||
"marketplace.moreFrom": "マーケットプレイスからのさらなる情報",
|
||||
"marketplace.noPluginFound": "インテグレーションが見つかりません",
|
||||
"marketplace.partnerTip": "このプラグインは Dify のパートナーによって認証されています",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Dify 마켓플레이스",
|
||||
"marketplace.discover": "발견하다",
|
||||
"marketplace.empower": "AI 개발 역량 강화",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Marketplace 에서 더 보기",
|
||||
"marketplace.noPluginFound": "플러그인을 찾을 수 없습니다.",
|
||||
"marketplace.partnerTip": "Dify 파트너에 의해 확인됨",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Dify Marketplace",
|
||||
"marketplace.discover": "Discover",
|
||||
"marketplace.empower": "Empower your AI development",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "More from Marketplace",
|
||||
"marketplace.noPluginFound": "Geen plugin gevonden",
|
||||
"marketplace.partnerTip": "Verified by a Dify partner",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Rynek Dify",
|
||||
"marketplace.discover": "Odkryć",
|
||||
"marketplace.empower": "Zwiększ możliwości rozwoju sztucznej inteligencji",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Więcej z Marketplace",
|
||||
"marketplace.noPluginFound": "Nie znaleziono integracji",
|
||||
"marketplace.partnerTip": "Zweryfikowane przez partnera Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Mercado Dify",
|
||||
"marketplace.discover": "Descobrir",
|
||||
"marketplace.empower": "Capacite seu desenvolvimento de IA",
|
||||
"marketplace.home.heroSubtitle": "Crie com plugins mais seguros e confiáveis do Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Buscar plugins ou modelos",
|
||||
"marketplace.home.trendingByCreator": "por {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Destaques por uso real, atualizados a cada duas semanas. Classificados pelas execuções reais nos espaços de trabalho — sem promoção paga ou seleção editorial.",
|
||||
"marketplace.home.trendingEyebrow": "Em alta agora",
|
||||
"marketplace.home.trendingPaginationLabel": "Páginas em alta",
|
||||
"marketplace.home.trendingTitle": "Os plugins que todos estão instalando",
|
||||
"marketplace.moreFrom": "Mais do Marketplace",
|
||||
"marketplace.noPluginFound": "Nenhuma integração encontrada",
|
||||
"marketplace.partnerTip": "Verificado por um parceiro da Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Piața Dify",
|
||||
"marketplace.discover": "Descoperi",
|
||||
"marketplace.empower": "Îmbunătățește-ți dezvoltarea AI",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Mai multe din Marketplace",
|
||||
"marketplace.noPluginFound": "Nu s-a găsit niciun plugin",
|
||||
"marketplace.partnerTip": "Verificat de un partener Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Торговая площадка Dify",
|
||||
"marketplace.discover": "Обнаруживать",
|
||||
"marketplace.empower": "Расширьте возможности разработки ИИ",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Больше из Marketplace",
|
||||
"marketplace.noPluginFound": "Плагин не найден",
|
||||
"marketplace.partnerTip": "Подтверждено партнером Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Dify Marketplace",
|
||||
"marketplace.discover": "Odkrijte",
|
||||
"marketplace.empower": "Okrepite svoj razvoj AI",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Več iz tržnice",
|
||||
"marketplace.noPluginFound": "Nobenega vtičnika ni bilo najti.",
|
||||
"marketplace.partnerTip": "Potrjeno s strani partnerja Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "ตลาด Dify",
|
||||
"marketplace.discover": "ค้นพบ",
|
||||
"marketplace.empower": "เพิ่มศักยภาพในการพัฒนา AI ของคุณ",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "แอปเพิ่มเติมจาก Marketplace",
|
||||
"marketplace.noPluginFound": "ไม่พบปลั๊กอิน",
|
||||
"marketplace.partnerTip": "ได้รับการตรวจสอบโดยพันธมิตรของ Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Dify Pazar Yeri",
|
||||
"marketplace.discover": "Keşfet",
|
||||
"marketplace.empower": "Yapay zeka geliştirmenizi güçlendirin",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Pazar Yeri'nden daha fazlası",
|
||||
"marketplace.noPluginFound": "Eklenti bulunamadı",
|
||||
"marketplace.partnerTip": "Dify partner'ı tarafından doğrulandı",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Dify Marketplace",
|
||||
"marketplace.discover": "Виявити",
|
||||
"marketplace.empower": "Розширюйте можливості розробки штучного інтелекту",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Більше від Marketplace",
|
||||
"marketplace.noPluginFound": "Плагін не знайдено",
|
||||
"marketplace.partnerTip": "Перевірено партнером Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Thị trường Dify",
|
||||
"marketplace.discover": "Khám phá",
|
||||
"marketplace.empower": "Hỗ trợ phát triển AI của bạn",
|
||||
"marketplace.home.heroSubtitle": "Build with safer, more reliable plugins from the Dify Marketplace.",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "Search plugins or templates",
|
||||
"marketplace.home.trendingByCreator": "by {{creator}}",
|
||||
"marketplace.home.trendingDescription": "Top picks by real usage, refreshed every two weeks. Ranked by actual runs across workspaces — no paid placement, no editorial picks.",
|
||||
"marketplace.home.trendingEyebrow": "Trending Now",
|
||||
"marketplace.home.trendingPaginationLabel": "Trending pages",
|
||||
"marketplace.home.trendingTitle": "The plugins everyone is installing",
|
||||
"marketplace.moreFrom": "Các ứng dụng khác từ Marketplace",
|
||||
"marketplace.noPluginFound": "Không tìm thấy plugin nào",
|
||||
"marketplace.partnerTip": "Được xác nhận bởi một đối tác của Dify",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Dify Marketplace",
|
||||
"marketplace.discover": "探索",
|
||||
"marketplace.empower": "助力您的 AI 开发",
|
||||
"marketplace.home.heroSubtitle": "在 Dify Marketplace 探索更安全、更可靠的插件。",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "搜索插件或模板",
|
||||
"marketplace.home.trendingByCreator": "由 {{creator}} 发布",
|
||||
"marketplace.home.trendingDescription": "基于真实使用情况选出的热门插件,每两周更新一次。榜单按各工作区的实际运行次数排序,不含付费推广或编辑推荐。",
|
||||
"marketplace.home.trendingEyebrow": "当前热门",
|
||||
"marketplace.home.trendingPaginationLabel": "热门推荐页码",
|
||||
"marketplace.home.trendingTitle": "大家都在安装的插件",
|
||||
"marketplace.moreFrom": "来自 Marketplace 的更多内容",
|
||||
"marketplace.noPluginFound": "未找到集成",
|
||||
"marketplace.partnerTip": "此插件由 Dify 合作伙伴认证",
|
||||
|
||||
@ -226,6 +226,14 @@
|
||||
"marketplace.difyMarketplace": "Dify Marketplace",
|
||||
"marketplace.discover": "發現",
|
||||
"marketplace.empower": "為您的 AI 開發提供支援",
|
||||
"marketplace.home.heroSubtitle": "在 Dify Marketplace 探索更安全、更可靠的外掛程式。",
|
||||
"marketplace.home.heroTitle": "Discover. Extend. Build",
|
||||
"marketplace.home.searchPlaceholder": "搜尋外掛程式或範本",
|
||||
"marketplace.home.trendingByCreator": "由 {{creator}} 發布",
|
||||
"marketplace.home.trendingDescription": "根據真實使用情況選出的熱門外掛程式,每兩週更新一次。榜單按各工作區的實際執行次數排序,不含付費推廣或編輯推薦。",
|
||||
"marketplace.home.trendingEyebrow": "目前熱門",
|
||||
"marketplace.home.trendingPaginationLabel": "熱門推薦頁碼",
|
||||
"marketplace.home.trendingTitle": "大家都在安裝的外掛程式",
|
||||
"marketplace.moreFrom": "來自 Marketplace 的更多內容",
|
||||
"marketplace.noPluginFound": "未找到集成",
|
||||
"marketplace.partnerTip": "由 Dify 合作夥伴驗證",
|
||||
|
||||