Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins

pull/12372/head
Yi 2 years ago
commit 912f84777b

@ -10,6 +10,7 @@ import Description from './base/description'
import Placeholder from './base/placeholder' import Placeholder from './base/placeholder'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { useGetLanguage } from '@/context/i18n' import { useGetLanguage } from '@/context/i18n'
import { getLanguage } from '@/i18n/language'
export type Props = { export type Props = {
className?: string className?: string
@ -22,6 +23,7 @@ export type Props = {
footer?: React.ReactNode footer?: React.ReactNode
isLoading?: boolean isLoading?: boolean
loadingFileName?: string loadingFileName?: string
locale?: string
} }
const Card = ({ const Card = ({
@ -35,8 +37,10 @@ const Card = ({
footer, footer,
isLoading = false, isLoading = false,
loadingFileName, loadingFileName,
locale: localeFromProps,
}: Props) => { }: Props) => {
const locale = useGetLanguage() const defaultLocale = useGetLanguage()
const locale = localeFromProps ? getLanguage(localeFromProps) : defaultLocale
const { type, name, org, label, brief, icon, verified } = payload const { type, name, org, label, brief, icon, verified } = payload

@ -1,12 +1,14 @@
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { TFunction } from 'i18next'
type Tag = { type Tag = {
name: string name: string
label: string label: string
} }
export const useTags = () => { export const useTags = (translateFromOut?: TFunction) => {
const { t } = useTranslation() const { t: translation } = useTranslation()
const t = translateFromOut || translation
const tags = [ const tags = [
{ {

@ -1,7 +1,7 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import type { PluginDeclaration, PluginManifestInMarket } from '../../types' import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../../types'
import Card from '../../card' import Card from '../../card'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { pluginManifestInMarketToPluginProps, pluginManifestToCardPluginProps } from '../utils' import { pluginManifestInMarketToPluginProps, pluginManifestToCardPluginProps } from '../utils'
@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next'
import Badge, { BadgeState } from '@/app/components/base/badge/index' import Badge, { BadgeState } from '@/app/components/base/badge/index'
type Props = { type Props = {
payload?: PluginDeclaration | PluginManifestInMarket | null payload?: Plugin | PluginDeclaration | PluginManifestInMarket | null
isMarketPayload?: boolean isMarketPayload?: boolean
isFailed: boolean isFailed: boolean
errMsg?: string | null errMsg?: string | null

@ -2,7 +2,7 @@
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import type { PluginManifestInMarket } from '../../types' import type { Plugin, PluginManifestInMarket } from '../../types'
import { InstallStep } from '../../types' import { InstallStep } from '../../types'
import Install from './steps/install' import Install from './steps/install'
import Installed from '../base/installed' import Installed from '../base/installed'
@ -12,7 +12,7 @@ const i18nPrefix = 'plugin.installModal'
type InstallFromMarketplaceProps = { type InstallFromMarketplaceProps = {
uniqueIdentifier: string uniqueIdentifier: string
manifest: PluginManifestInMarket manifest: PluginManifestInMarket | Plugin
onSuccess: () => void onSuccess: () => void
onClose: () => void onClose: () => void
} }
@ -36,7 +36,7 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
if (step === InstallStep.installFailed) if (step === InstallStep.installFailed)
return t(`${i18nPrefix}.installFailed`) return t(`${i18nPrefix}.installFailed`)
return t(`${i18nPrefix}.installPlugin`) return t(`${i18nPrefix}.installPlugin`)
}, [step]) }, [step, t])
const handleInstalled = useCallback(() => { const handleInstalled = useCallback(() => {
setStep(InstallStep.installed) setStep(InstallStep.installed)

@ -2,7 +2,7 @@
import type { FC } from 'react' import type { FC } from 'react'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { RiInformation2Line } from '@remixicon/react' import { RiInformation2Line } from '@remixicon/react'
import type { PluginManifestInMarket } from '../../../types' import type { Plugin, PluginManifestInMarket } from '../../../types'
import Card from '../../../card' import Card from '../../../card'
import { pluginManifestInMarketToPluginProps } from '../../utils' import { pluginManifestInMarketToPluginProps } from '../../utils'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
@ -16,7 +16,7 @@ const i18nPrefix = 'plugin.installModal'
type Props = { type Props = {
uniqueIdentifier: string uniqueIdentifier: string
payload: PluginManifestInMarket payload: PluginManifestInMarket | Plugin
onCancel: () => void onCancel: () => void
onStartToInstall?: () => void onStartToInstall?: () => void
onInstalled: () => void onInstalled: () => void
@ -104,7 +104,7 @@ const Installed: FC<Props> = ({
<div className='flex p-2 items-start content-start gap-1 self-stretch flex-wrap rounded-2xl bg-background-section-burn'> <div className='flex p-2 items-start content-start gap-1 self-stretch flex-wrap rounded-2xl bg-background-section-burn'>
<Card <Card
className='w-full' className='w-full'
payload={pluginManifestInMarketToPluginProps(payload)} payload={pluginManifestInMarketToPluginProps(payload as PluginManifestInMarket)}
titleLeft={versionInfo} titleLeft={versionInfo}
/> />
</div> </div>

@ -1,4 +1,17 @@
const Description = () => { import {
getLocaleOnServer,
useTranslation as translate,
} from '@/i18n/server'
type DescriptionProps = {
locale?: string
}
const Description = async ({
locale: localeFromProps,
}: DescriptionProps) => {
const localeDefault = getLocaleOnServer()
const { t } = await translate(localeFromProps || localeDefault, 'plugin')
return ( return (
<> <>
<h1 className='mb-2 text-center title-4xl-semi-bold text-text-primary'> <h1 className='mb-2 text-center title-4xl-semi-bold text-text-primary'>
@ -7,19 +20,19 @@ const Description = () => {
<h2 className='flex justify-center items-center text-center body-md-regular text-text-tertiary'> <h2 className='flex justify-center items-center text-center body-md-regular text-text-tertiary'>
Discover Discover
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected"> <span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
models {t('category.models')}
</span> </span>
, ,
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected"> <span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
tools {t('category.tools')}
</span> </span>
, ,
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected"> <span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
extensions {t('category.extensions')}
</span> </span>
and and
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected"> <span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
bundles {t('category.bundles')}
</span> </span>
in Dify Marketplace in Dify Marketplace
</h2> </h2>

@ -2,6 +2,7 @@ import {
useCallback, useCallback,
useState, useState,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next'
import { useDebounceFn } from 'ahooks' import { useDebounceFn } from 'ahooks'
import type { Plugin } from '../types' import type { Plugin } from '../types'
import type { import type {
@ -13,6 +14,7 @@ import {
getMarketplaceCollectionsAndPlugins, getMarketplaceCollectionsAndPlugins,
getMarketplacePlugins, getMarketplacePlugins,
} from './utils' } from './utils'
import i18n from '@/i18n/i18next-config'
export const useMarketplaceCollectionsAndPlugins = () => { export const useMarketplaceCollectionsAndPlugins = () => {
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
@ -63,3 +65,14 @@ export const useMarketplacePlugins = () => {
setIsLoading, setIsLoading,
} }
} }
export const useMixedTranslation = (localeFromOuter?: string) => {
let t = useTranslation().t
if (localeFromOuter)
t = i18n.getFixedT(localeFromOuter)
return {
t,
}
}

@ -7,20 +7,23 @@ import ListWrapper from './list/list-wrapper'
import { getMarketplaceCollectionsAndPlugins } from './utils' import { getMarketplaceCollectionsAndPlugins } from './utils'
type MarketplaceProps = { type MarketplaceProps = {
locale?: string
showInstallButton?: boolean showInstallButton?: boolean
} }
const Marketplace = async ({ const Marketplace = async ({
locale,
showInstallButton = true, showInstallButton = true,
}: MarketplaceProps) => { }: MarketplaceProps) => {
const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins() const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins()
return ( return (
<MarketplaceContextProvider> <MarketplaceContextProvider>
<Description /> <Description locale={locale} />
<IntersectionLine /> <IntersectionLine />
<SearchBoxWrapper /> <SearchBoxWrapper locale={locale} />
<PluginTypeSwitch /> <PluginTypeSwitch locale={locale} />
<ListWrapper <ListWrapper
locale={locale}
marketplaceCollections={marketplaceCollections} marketplaceCollections={marketplaceCollections}
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap} marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
showInstallButton={showInstallButton} showInstallButton={showInstallButton}

@ -1,26 +1,42 @@
'use client' 'use client'
import { RiArrowRightUpLine } from '@remixicon/react' import { RiArrowRightUpLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import Card from '@/app/components/plugins/card' import Card from '@/app/components/plugins/card'
import CardMoreInfo from '@/app/components/plugins/card/card-more-info' import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
import type { Plugin } from '@/app/components/plugins/types' import type { Plugin } from '@/app/components/plugins/types'
import { MARKETPLACE_URL_PREFIX } from '@/config' import { MARKETPLACE_URL_PREFIX } from '@/config'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import { useBoolean } from 'ahooks'
type CardWrapperProps = { type CardWrapperProps = {
plugin: Plugin plugin: Plugin
showInstallButton?: boolean showInstallButton?: boolean
locale?: string
} }
const CardWrapper = ({ const CardWrapper = ({
plugin, plugin,
showInstallButton, showInstallButton,
locale,
}: CardWrapperProps) => { }: CardWrapperProps) => {
const { t } = useTranslation() const { t } = useMixedTranslation(locale)
const [isShowInstallFromMarketplace, {
setTrue: showInstallFromMarketplace,
setFalse: hideInstallFromMarketplace,
}] = useBoolean(false)
return ( return (
<div className='group relative rounded-xl cursor-pointer'> <div
className='group relative rounded-xl cursor-pointer'
onClick={() => {
if (!showInstallButton)
window.open(`${MARKETPLACE_URL_PREFIX}/plugin/${plugin.org}/${plugin.name}`)
}}
>
<Card <Card
key={plugin.name} key={plugin.name}
payload={plugin} payload={plugin}
locale={locale}
footer={ footer={
<CardMoreInfo <CardMoreInfo
downloadCount={plugin.install_count} downloadCount={plugin.install_count}
@ -34,6 +50,7 @@ const CardWrapper = ({
<Button <Button
variant='primary' variant='primary'
className='flex-1' className='flex-1'
onClick={showInstallFromMarketplace}
> >
{t('plugin.detailPanel.operation.install')} {t('plugin.detailPanel.operation.install')}
</Button> </Button>
@ -48,6 +65,16 @@ const CardWrapper = ({
</div> </div>
) )
} }
{
isShowInstallFromMarketplace && (
<InstallFromMarketplace
manifest={plugin as any}
uniqueIdentifier={plugin.latest_package_identifier}
onClose={hideInstallFromMarketplace}
onSuccess={hideInstallFromMarketplace}
/>
)
}
</div> </div>
) )
} }

@ -10,12 +10,14 @@ type ListProps = {
marketplaceCollectionPluginsMap: Record<string, Plugin[]> marketplaceCollectionPluginsMap: Record<string, Plugin[]>
plugins?: Plugin[] plugins?: Plugin[]
showInstallButton?: boolean showInstallButton?: boolean
locale?: string
} }
const List = ({ const List = ({
marketplaceCollections, marketplaceCollections,
marketplaceCollectionPluginsMap, marketplaceCollectionPluginsMap,
plugins, plugins,
showInstallButton, showInstallButton,
locale,
}: ListProps) => { }: ListProps) => {
return ( return (
<> <>
@ -25,6 +27,7 @@ const List = ({
marketplaceCollections={marketplaceCollections} marketplaceCollections={marketplaceCollections}
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap} marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
showInstallButton={showInstallButton} showInstallButton={showInstallButton}
locale={locale}
/> />
) )
} }
@ -37,6 +40,7 @@ const List = ({
key={plugin.name} key={plugin.name}
plugin={plugin} plugin={plugin}
showInstallButton={showInstallButton} showInstallButton={showInstallButton}
locale={locale}
/> />
)) ))
} }

@ -7,11 +7,13 @@ type ListWithCollectionProps = {
marketplaceCollections: MarketplaceCollection[] marketplaceCollections: MarketplaceCollection[]
marketplaceCollectionPluginsMap: Record<string, Plugin[]> marketplaceCollectionPluginsMap: Record<string, Plugin[]>
showInstallButton?: boolean showInstallButton?: boolean
locale?: string
} }
const ListWithCollection = ({ const ListWithCollection = ({
marketplaceCollections, marketplaceCollections,
marketplaceCollectionPluginsMap, marketplaceCollectionPluginsMap,
showInstallButton, showInstallButton,
locale,
}: ListWithCollectionProps) => { }: ListWithCollectionProps) => {
return ( return (
<> <>
@ -30,6 +32,7 @@ const ListWithCollection = ({
key={plugin.name} key={plugin.name}
plugin={plugin} plugin={plugin}
showInstallButton={showInstallButton} showInstallButton={showInstallButton}
locale={locale}
/> />
)) ))
} }

@ -9,11 +9,13 @@ type ListWrapperProps = {
marketplaceCollections: MarketplaceCollection[] marketplaceCollections: MarketplaceCollection[]
marketplaceCollectionPluginsMap: Record<string, Plugin[]> marketplaceCollectionPluginsMap: Record<string, Plugin[]>
showInstallButton?: boolean showInstallButton?: boolean
locale?: string
} }
const ListWrapper = ({ const ListWrapper = ({
marketplaceCollections, marketplaceCollections,
marketplaceCollectionPluginsMap, marketplaceCollectionPluginsMap,
showInstallButton, showInstallButton,
locale,
}: ListWrapperProps) => { }: ListWrapperProps) => {
const plugins = useMarketplaceContext(v => v.plugins) const plugins = useMarketplaceContext(v => v.plugins)
const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient) const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient)
@ -35,6 +37,7 @@ const ListWrapper = ({
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMapFromClient || marketplaceCollectionPluginsMap} marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMapFromClient || marketplaceCollectionPluginsMap}
plugins={plugins} plugins={plugins}
showInstallButton={showInstallButton} showInstallButton={showInstallButton}
locale={locale}
/> />
</div> </div>
) )

@ -1,5 +1,4 @@
'use client' 'use client'
import { import {
RiArchive2Line, RiArchive2Line,
RiBrain2Line, RiBrain2Line,
@ -8,6 +7,7 @@ import {
} from '@remixicon/react' } from '@remixicon/react'
import { PluginType } from '../types' import { PluginType } from '../types'
import { useMarketplaceContext } from './context' import { useMarketplaceContext } from './context'
import { useMixedTranslation } from './hooks'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
export const PLUGIN_TYPE_SEARCH_MAP = { export const PLUGIN_TYPE_SEARCH_MAP = {
@ -17,7 +17,17 @@ export const PLUGIN_TYPE_SEARCH_MAP = {
extension: PluginType.extension, extension: PluginType.extension,
bundle: 'bundle', bundle: 'bundle',
} }
const options = [ type PluginTypeSwitchProps = {
locale?: string
}
const PluginTypeSwitch = ({
locale,
}: PluginTypeSwitchProps) => {
const { t } = useMixedTranslation(locale)
const activePluginType = useMarketplaceContext(s => s.activePluginType)
const handleActivePluginTypeChange = useMarketplaceContext(s => s.handleActivePluginTypeChange)
const options = [
{ {
value: PLUGIN_TYPE_SEARCH_MAP.all, value: PLUGIN_TYPE_SEARCH_MAP.all,
text: 'All', text: 'All',
@ -25,28 +35,25 @@ const options = [
}, },
{ {
value: PLUGIN_TYPE_SEARCH_MAP.model, value: PLUGIN_TYPE_SEARCH_MAP.model,
text: 'Models', text: t('plugin.category.models'),
icon: <RiBrain2Line className='mr-1.5 w-4 h-4' />, icon: <RiBrain2Line className='mr-1.5 w-4 h-4' />,
}, },
{ {
value: PLUGIN_TYPE_SEARCH_MAP.tool, value: PLUGIN_TYPE_SEARCH_MAP.tool,
text: 'Tools', text: t('plugin.category.tools'),
icon: <RiHammerLine className='mr-1.5 w-4 h-4' />, icon: <RiHammerLine className='mr-1.5 w-4 h-4' />,
}, },
{ {
value: PLUGIN_TYPE_SEARCH_MAP.extension, value: PLUGIN_TYPE_SEARCH_MAP.extension,
text: 'Extensions', text: t('plugin.category.extensions'),
icon: <RiPuzzle2Line className='mr-1.5 w-4 h-4' />, icon: <RiPuzzle2Line className='mr-1.5 w-4 h-4' />,
}, },
{ {
value: PLUGIN_TYPE_SEARCH_MAP.bundle, value: PLUGIN_TYPE_SEARCH_MAP.bundle,
text: 'Bundles', text: t('plugin.category.bundles'),
icon: <RiArchive2Line className='mr-1.5 w-4 h-4' />, icon: <RiArchive2Line className='mr-1.5 w-4 h-4' />,
}, },
] ]
const PluginTypeSwitch = () => {
const activePluginType = useMarketplaceContext(s => s.activePluginType)
const handleActivePluginTypeChange = useMarketplaceContext(s => s.handleActivePluginTypeChange)
return ( return (
<div className={cn( <div className={cn(

@ -12,6 +12,7 @@ type SearchBoxProps = {
onTagsChange: (tags: string[]) => void onTagsChange: (tags: string[]) => void
size?: 'small' | 'large' size?: 'small' | 'large'
placeholder?: string placeholder?: string
locale?: string
} }
const SearchBox = ({ const SearchBox = ({
search, search,
@ -20,7 +21,8 @@ const SearchBox = ({
tags, tags,
onTagsChange, onTagsChange,
size = 'small', size = 'small',
placeholder = 'Search tools...', placeholder = '',
locale,
}: SearchBoxProps) => { }: SearchBoxProps) => {
return ( return (
<div <div
@ -35,6 +37,7 @@ const SearchBox = ({
tags={tags} tags={tags}
onTagsChange={onTagsChange} onTagsChange={onTagsChange}
size={size} size={size}
locale={locale}
/> />
<div className='mx-1 w-[1px] h-3.5 bg-divider-regular'></div> <div className='mx-1 w-[1px] h-3.5 bg-divider-regular'></div>
<div className='grow flex items-center p-1 pl-2'> <div className='grow flex items-center p-1 pl-2'>

@ -1,9 +1,16 @@
'use client' 'use client'
import { useMarketplaceContext } from '../context' import { useMarketplaceContext } from '../context'
import { useMixedTranslation } from '../hooks'
import SearchBox from './index' import SearchBox from './index'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
const SearchBoxWrapper = () => { type SearchBoxWrapperProps = {
locale?: string
}
const SearchBoxWrapper = ({
locale,
}: SearchBoxWrapperProps) => {
const { t } = useMixedTranslation(locale)
const intersected = useMarketplaceContext(v => v.intersected) const intersected = useMarketplaceContext(v => v.intersected)
const searchPluginText = useMarketplaceContext(v => v.searchPluginText) const searchPluginText = useMarketplaceContext(v => v.searchPluginText)
const handleSearchPluginTextChange = useMarketplaceContext(v => v.handleSearchPluginTextChange) const handleSearchPluginTextChange = useMarketplaceContext(v => v.handleSearchPluginTextChange)
@ -21,6 +28,8 @@ const SearchBoxWrapper = () => {
tags={filterPluginTags} tags={filterPluginTags}
onTagsChange={handleFilterPluginTagsChange} onTagsChange={handleFilterPluginTagsChange}
size='large' size='large'
locale={locale}
placeholder={t('plugin.searchPlugins')}
/> />
) )
} }

@ -1,7 +1,6 @@
'use client' 'use client'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { import {
RiArrowDownSLine, RiArrowDownSLine,
RiCloseCircleFill, RiCloseCircleFill,
@ -16,21 +15,24 @@ import Checkbox from '@/app/components/base/checkbox'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import Input from '@/app/components/base/input' import Input from '@/app/components/base/input'
import { useTags } from '@/app/components/plugins/hooks' import { useTags } from '@/app/components/plugins/hooks'
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
type TagsFilterProps = { type TagsFilterProps = {
tags: string[] tags: string[]
onTagsChange: (tags: string[]) => void onTagsChange: (tags: string[]) => void
size: 'small' | 'large' size: 'small' | 'large'
locale?: string
} }
const TagsFilter = ({ const TagsFilter = ({
tags, tags,
onTagsChange, onTagsChange,
size, size,
locale,
}: TagsFilterProps) => { }: TagsFilterProps) => {
const { t } = useTranslation() const { t } = useMixedTranslation(locale)
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [searchText, setSearchText] = useState('') const [searchText, setSearchText] = useState('')
const { tags: options, tagsMap } = useTags() const { tags: options, tagsMap } = useTags(t)
const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchText.toLowerCase())) const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchText.toLowerCase()))
const handleCheck = (id: string) => { const handleCheck = (id: string) => {
if (tags.includes(id)) if (tags.includes(id))

@ -52,13 +52,14 @@ const DetailHeader = ({
meta, meta,
} = detail } = detail
const { author, name, label, description, icon, verified } = detail.declaration const { author, name, label, description, icon, verified } = detail.declaration
const isFromGitHub = source === PluginSource.github
// Only plugin installed from GitHub need to check if it's the new version // Only plugin installed from GitHub need to check if it's the new version
const hasNewVersion = useMemo(() => { const hasNewVersion = useMemo(() => {
return source === PluginSource.github && latest_version !== version return source === PluginSource.github && latest_version !== version
}, [source, latest_version, version]) }, [source, latest_version, version])
// #plugin TODO# update plugin // #plugin TODO# update plugin
const handleUpdate = () => {} const handleUpdate = () => { }
const [isShowPluginInfo, { const [isShowPluginInfo, {
setTrue: showPluginInfo, setTrue: showPluginInfo,
@ -151,7 +152,7 @@ const DetailHeader = ({
<Description className='mt-3' text={description[locale]} descriptionLineRows={2}></Description> <Description className='mt-3' text={description[locale]} descriptionLineRows={2}></Description>
{isShowPluginInfo && ( {isShowPluginInfo && (
<PluginInfo <PluginInfo
repository={meta?.repo} repository={isFromGitHub ? meta?.repo : ''}
release={version} release={version}
packageName={meta?.package} packageName={meta?.package}
onHide={hidePluginInfo} onHide={hidePluginInfo}

@ -21,7 +21,6 @@ import Title from '../card/base/title'
import Action from './action' import Action from './action'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import I18n from '@/context/i18n' import I18n from '@/context/i18n'
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config' import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
type Props = { type Props = {

@ -7,9 +7,9 @@ import Modal from '../../base/modal'
const i18nPrefix = 'plugin.pluginInfoModal' const i18nPrefix = 'plugin.pluginInfoModal'
type Props = { type Props = {
repository: string repository?: string
release: string release?: string
packageName: string packageName?: string
onHide: () => void onHide: () => void
} }
@ -30,9 +30,9 @@ const PlugInfo: FC<Props> = ({
closable closable
> >
<div className='mt-5 space-y-3'> <div className='mt-5 space-y-3'>
<KeyValueItem label={t(`${i18nPrefix}.repository`)} labelWidthClassName={labelWidthClassName} value={repository} /> {repository && <KeyValueItem label={t(`${i18nPrefix}.repository`)} labelWidthClassName={labelWidthClassName} value={repository} />}
<KeyValueItem label={t(`${i18nPrefix}.release`)} labelWidthClassName={labelWidthClassName} value={release} /> {release && <KeyValueItem label={t(`${i18nPrefix}.release`)} labelWidthClassName={labelWidthClassName} value={release} />}
<KeyValueItem label={t(`${i18nPrefix}.packageName`)} labelWidthClassName={labelWidthClassName} value={packageName} /> {packageName && <KeyValueItem label={t(`${i18nPrefix}.packageName`)} labelWidthClassName={labelWidthClassName} value={packageName} />}
</div> </div>
</Modal> </Modal>
) )

@ -10,7 +10,7 @@ import { useDebounceFn } from 'ahooks'
import Empty from './empty' import Empty from './empty'
const PluginsPanel = () => { const PluginsPanel = () => {
const [filters, setFilters] = usePluginPageContext(v => [v.filters, v.setFilters]) const [filters, setFilters] = usePluginPageContext(v => [v.filters, v.setFilters]) as [FilterState, (filter: FilterState) => void]
const pluginList = usePluginPageContext(v => v.installedPluginList) as PluginDetail[] const pluginList = usePluginPageContext(v => v.installedPluginList) as PluginDetail[]
const mutateInstalledPluginList = usePluginPageContext(v => v.mutateInstalledPluginList) const mutateInstalledPluginList = usePluginPageContext(v => v.mutateInstalledPluginList)
@ -19,11 +19,11 @@ const PluginsPanel = () => {
}, { wait: 500 }) }, { wait: 500 })
const filteredList = useMemo(() => { const filteredList = useMemo(() => {
// todo: filter by tags const { categories, searchQuery, tags } = filters
const { categories, searchQuery } = filters
const filteredList = pluginList.filter((plugin) => { const filteredList = pluginList.filter((plugin) => {
return ( return (
(categories.length === 0 || categories.includes(plugin.declaration.category)) (categories.length === 0 || categories.includes(plugin.declaration.category))
&& (tags.length === 0 || tags.some(tag => plugin.declaration.tags.includes(tag)))
&& (searchQuery === '' || plugin.plugin_id.toLowerCase().includes(searchQuery.toLowerCase())) && (searchQuery === '' || plugin.plugin_id.toLowerCase().includes(searchQuery.toLowerCase()))
) )
}) })

@ -12,7 +12,9 @@ import DownloadCount from './card/base/download-count'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { useGetLanguage } from '@/context/i18n' import { useGetLanguage } from '@/context/i18n'
import { MARKETPLACE_URL_PREFIX } from '@/config' import { MARKETPLACE_URL_PREFIX } from '@/config'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { useBoolean } from 'ahooks'
type Props = { type Props = {
className?: string className?: string
@ -24,6 +26,10 @@ const ProviderCard: FC<Props> = ({
payload, payload,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const [isShowInstallFromMarketplace, {
setTrue: showInstallFromMarketplace,
setFalse: hideInstallFromMarketplace,
}] = useBoolean(false)
const language = useGetLanguage() const language = useGetLanguage()
const { org, label } = payload const { org, label } = payload
@ -58,6 +64,7 @@ const ProviderCard: FC<Props> = ({
<Button <Button
className='flex-grow' className='flex-grow'
variant='primary' variant='primary'
onClick={showInstallFromMarketplace}
> >
{t('plugin.detailPanel.operation.install')} {t('plugin.detailPanel.operation.install')}
</Button> </Button>
@ -71,6 +78,16 @@ const ProviderCard: FC<Props> = ({
</a> </a>
</Button> </Button>
</div> </div>
{
isShowInstallFromMarketplace && (
<InstallFromMarketplace
manifest={payload as any}
uniqueIdentifier={payload.latest_package_identifier}
onClose={hideInstallFromMarketplace}
onSuccess={hideInstallFromMarketplace}
/>
)
}
</div> </div>
) )
} }

@ -69,6 +69,7 @@ export type PluginDeclaration = {
endpoint: PluginEndpointDeclaration endpoint: PluginEndpointDeclaration
tool: PluginToolDeclaration tool: PluginToolDeclaration
model: any // TODO model: any // TODO
tags: string[]
} }
export type PluginManifestInMarket = { export type PluginManifestInMarket = {
@ -110,6 +111,7 @@ export type Plugin = {
plugin_id: string plugin_id: string
version: string version: string
latest_version: string latest_version: string
latest_package_identifier: string
icon: string icon: string
verified: boolean verified: boolean
label: Record<Locale, string> label: Record<Locale, string>

@ -1,7 +1,9 @@
import { RiArrowUpDoubleLine } from '@remixicon/react' import { RiArrowUpDoubleLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useMarketplace } from './hooks' import { useMarketplace } from './hooks'
import List from '@/app/components/plugins/marketplace/list' import List from '@/app/components/plugins/marketplace/list'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import { getLocaleOnClient } from '@/i18n'
type MarketplaceProps = { type MarketplaceProps = {
searchPluginText: string searchPluginText: string
@ -13,6 +15,8 @@ const Marketplace = ({
filterPluginTags, filterPluginTags,
onMarketplaceScroll, onMarketplaceScroll,
}: MarketplaceProps) => { }: MarketplaceProps) => {
const locale = getLocaleOnClient()
const { t } = useTranslation()
const { const {
isLoading, isLoading,
marketplaceCollections, marketplaceCollections,
@ -31,19 +35,19 @@ const Marketplace = ({
<div className='flex items-center text-center body-md-regular text-text-tertiary'> <div className='flex items-center text-center body-md-regular text-text-tertiary'>
Discover Discover
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected"> <span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
models {t('plugin.category.models')}
</span> </span>
, ,
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected"> <span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
tools {t('plugin.category.tools')}
</span> </span>
, ,
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected"> <span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
extensions {t('plugin.category.extensions')}
</span> </span>
and and
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected"> <span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
bundles {t('plugin.category.bundles')}
</span> </span>
in Dify Marketplace in Dify Marketplace
</div> </div>
@ -62,6 +66,7 @@ const Marketplace = ({
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap || {}} marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap || {}}
plugins={plugins} plugins={plugins}
showInstallButton showInstallButton
locale={locale}
/> />
) )
} }

@ -1,4 +1,11 @@
const translation = { const translation = {
category: {
models: 'models',
tools: 'tools',
extensions: 'extensions',
bundles: 'bundles',
},
searchPlugins: 'Search plugins',
from: 'From', from: 'From',
findMoreInMarketplace: 'Find more in Marketplace', findMoreInMarketplace: 'Find more in Marketplace',
searchInMarketplace: 'Search in Marketplace', searchInMarketplace: 'Search in Marketplace',

@ -1,4 +1,11 @@
const translation = { const translation = {
category: {
models: '模型',
tools: '工具',
extensions: '扩展',
bundles: '捆绑包',
},
searchPlugins: '搜索插件',
from: '来自', from: '来自',
findMoreInMarketplace: '在 Marketplace 中查找更多', findMoreInMarketplace: '在 Marketplace 中查找更多',
searchInMarketplace: '在 Marketplace 中搜索', searchInMarketplace: '在 Marketplace 中搜索',

Loading…
Cancel
Save