mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-01 04:52:04 +08:00
feat: add minimum dify version requirement to plugins (#18022)
This commit is contained in:
parent
9f8947f1dd
commit
2134a76517
@ -249,6 +249,31 @@ class PluginInstallFromMarketplaceApi(Resource):
|
||||
return jsonable_encoder(response)
|
||||
|
||||
|
||||
class PluginFetchMarketplacePkgApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@plugin_permission_required(install_required=True)
|
||||
def get(self):
|
||||
tenant_id = current_user.current_tenant_id
|
||||
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument("plugin_unique_identifier", type=str, required=True, location="args")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
return jsonable_encoder(
|
||||
{
|
||||
"manifest": PluginService.fetch_marketplace_pkg(
|
||||
tenant_id,
|
||||
args["plugin_unique_identifier"],
|
||||
)
|
||||
}
|
||||
)
|
||||
except PluginDaemonClientSideError as e:
|
||||
raise ValueError(e)
|
||||
|
||||
|
||||
class PluginFetchManifestApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@ -488,6 +513,7 @@ api.add_resource(PluginDeleteInstallTaskApi, "/workspaces/current/plugin/tasks/<
|
||||
api.add_resource(PluginDeleteAllInstallTaskItemsApi, "/workspaces/current/plugin/tasks/delete_all")
|
||||
api.add_resource(PluginDeleteInstallTaskItemApi, "/workspaces/current/plugin/tasks/<task_id>/delete/<path:identifier>")
|
||||
api.add_resource(PluginUninstallApi, "/workspaces/current/plugin/uninstall")
|
||||
api.add_resource(PluginFetchMarketplacePkgApi, "/workspaces/current/plugin/marketplace/pkg")
|
||||
|
||||
api.add_resource(PluginChangePermissionApi, "/workspaces/current/plugin/permission/change")
|
||||
api.add_resource(PluginFetchPermissionApi, "/workspaces/current/plugin/permission/fetch")
|
||||
|
@ -70,6 +70,9 @@ class PluginDeclaration(BaseModel):
|
||||
models: Optional[list[str]] = Field(default_factory=list)
|
||||
endpoints: Optional[list[str]] = Field(default_factory=list)
|
||||
|
||||
class Meta(BaseModel):
|
||||
minimum_dify_version: Optional[str] = Field(default=None, pattern=r"^\d{1,4}(\.\d{1,4}){1,3}(-\w{1,16})?$")
|
||||
|
||||
version: str = Field(..., pattern=r"^\d{1,4}(\.\d{1,4}){1,3}(-\w{1,16})?$")
|
||||
author: Optional[str] = Field(..., pattern=r"^[a-zA-Z0-9_-]{1,64}$")
|
||||
name: str = Field(..., pattern=r"^[a-z0-9_-]{1,128}$")
|
||||
@ -86,6 +89,7 @@ class PluginDeclaration(BaseModel):
|
||||
model: Optional[ProviderEntity] = None
|
||||
endpoint: Optional[EndpointProviderDeclaration] = None
|
||||
agent_strategy: Optional[AgentStrategyProviderEntity] = None
|
||||
meta: Meta
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
|
@ -309,6 +309,22 @@ class PluginService:
|
||||
],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def fetch_marketplace_pkg(
|
||||
tenant_id: str, plugin_unique_identifier: str, verify_signature: bool = False
|
||||
) -> PluginDeclaration:
|
||||
"""
|
||||
Fetch marketplace package
|
||||
"""
|
||||
manager = PluginInstallationManager()
|
||||
try:
|
||||
declaration = manager.fetch_plugin_manifest(tenant_id, plugin_unique_identifier)
|
||||
except Exception:
|
||||
pkg = download_plugin_pkg(plugin_unique_identifier)
|
||||
declaration = manager.upload_pkg(tenant_id, pkg, verify_signature).manifest
|
||||
|
||||
return declaration
|
||||
|
||||
@staticmethod
|
||||
def install_from_marketplace_pkg(
|
||||
tenant_id: str, plugin_unique_identifiers: Sequence[str], verify_signature: bool = False
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
import { type PluginDeclaration, TaskStatus } from '../../../types'
|
||||
import Card from '../../../card'
|
||||
import { pluginManifestToCardPluginProps } from '../../utils'
|
||||
@ -12,6 +12,8 @@ import { useInstallPackageFromLocal, usePluginTaskList } from '@/service/use-plu
|
||||
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
|
||||
import { uninstallPlugin } from '@/service/plugins'
|
||||
import Version from '../../base/version'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { gte } from 'semver'
|
||||
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
@ -103,6 +105,13 @@ const Installed: FC<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const { langeniusVersionInfo } = useAppContext()
|
||||
const isDifyVersionCompatible = useMemo(() => {
|
||||
if (!langeniusVersionInfo.current_version)
|
||||
return true
|
||||
return gte(langeniusVersionInfo.current_version, payload.meta.minimum_dify_version ?? '0.0.0')
|
||||
}, [langeniusVersionInfo.current_version, payload.meta.minimum_dify_version])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3'>
|
||||
@ -114,6 +123,11 @@ const Installed: FC<Props> = ({
|
||||
components={{ trustSource: <span className='system-md-semibold' /> }}
|
||||
/>
|
||||
</p>
|
||||
{!isDifyVersionCompatible && (
|
||||
<p className='system-md-regular flex items-center gap-1 text-text-secondary text-text-warning'>
|
||||
{t('plugin.difyVersionNotCompatible', { minimalDifyVersion: payload.meta.minimum_dify_version })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2'>
|
||||
<Card
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
// import { RiInformation2Line } from '@remixicon/react'
|
||||
import { type Plugin, type PluginManifestInMarket, TaskStatus } from '../../../types'
|
||||
import Card from '../../../card'
|
||||
@ -8,11 +8,13 @@ import { pluginManifestInMarketToPluginProps } from '../../utils'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import { useInstallPackageFromMarketPlace, useUpdatePackageFromMarketPlace } from '@/service/use-plugins'
|
||||
import { useInstallPackageFromMarketPlace, usePluginDeclarationFromMarketPlace, useUpdatePackageFromMarketPlace } from '@/service/use-plugins'
|
||||
import checkTaskStatus from '../../base/check-task-status'
|
||||
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
|
||||
import Version from '../../base/version'
|
||||
import { usePluginTaskList } from '@/service/use-plugins'
|
||||
import { gte } from 'semver'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
@ -117,11 +119,23 @@ const Installed: FC<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const { langeniusVersionInfo } = useAppContext()
|
||||
const { data: pluginDeclaration } = usePluginDeclarationFromMarketPlace(uniqueIdentifier)
|
||||
const isDifyVersionCompatible = useMemo(() => {
|
||||
if (!pluginDeclaration || !langeniusVersionInfo.current_version) return true
|
||||
return gte(langeniusVersionInfo.current_version, pluginDeclaration?.manifest.meta.minimum_dify_version ?? '0.0.0')
|
||||
}, [langeniusVersionInfo.current_version, pluginDeclaration?.manifest.meta.minimum_dify_version])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3'>
|
||||
<div className='system-md-regular text-text-secondary'>
|
||||
<p>{t(`${i18nPrefix}.readyToInstall`)}</p>
|
||||
{!isDifyVersionCompatible && (
|
||||
<p className='system-md-regular text-text-secondary text-text-warning'>
|
||||
{t('plugin.difyVersionNotCompatible', { minimalDifyVersion: pluginDeclaration?.manifest.meta.minimum_dify_version })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2'>
|
||||
<Card
|
||||
|
@ -4,6 +4,7 @@ import React, { useMemo } from 'react'
|
||||
import {
|
||||
RiArrowRightUpLine,
|
||||
RiBugLine,
|
||||
RiErrorWarningLine,
|
||||
RiHardDrive3Line,
|
||||
RiLoginCircleLine,
|
||||
RiVerifiedBadgeLine,
|
||||
@ -23,6 +24,9 @@ import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
||||
import { useSingleCategories } from '../hooks'
|
||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { gte } from 'semver'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@ -48,12 +52,20 @@ const PluginItem: FC<Props> = ({
|
||||
meta,
|
||||
plugin_id,
|
||||
} = plugin
|
||||
const { category, author, name, label, description, icon, verified } = plugin.declaration
|
||||
const { category, author, name, label, description, icon, verified, meta: declarationMeta } = plugin.declaration
|
||||
|
||||
const orgName = useMemo(() => {
|
||||
return [PluginSource.github, PluginSource.marketplace].includes(source) ? author : ''
|
||||
}, [source, author])
|
||||
|
||||
const { langeniusVersionInfo } = useAppContext()
|
||||
|
||||
const isDifyVersionCompatible = useMemo(() => {
|
||||
if (!langeniusVersionInfo.current_version)
|
||||
return true
|
||||
return gte(langeniusVersionInfo.current_version, declarationMeta.minimum_dify_version ?? '0.0.0')
|
||||
}, [declarationMeta.minimum_dify_version, langeniusVersionInfo.current_version])
|
||||
|
||||
const handleDelete = () => {
|
||||
refreshPluginList({ category } as any)
|
||||
}
|
||||
@ -89,6 +101,9 @@ const PluginItem: FC<Props> = ({
|
||||
<div className="flex h-5 items-center">
|
||||
<Title title={title} />
|
||||
{verified && <RiVerifiedBadgeLine className="ml-0.5 h-4 w-4 shrink-0 text-text-accent" />}
|
||||
{!isDifyVersionCompatible && <Tooltip popupContent={
|
||||
t('plugin.difyVersionNotCompatible', { minimalDifyVersion: declarationMeta.minimum_dify_version })
|
||||
}><RiErrorWarningLine color='red' className="ml-0.5 h-4 w-4 shrink-0 text-text-accent" /></Tooltip>}
|
||||
<Badge className='ml-1 shrink-0' text={source === PluginSource.github ? plugin.meta!.version : plugin.version} />
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
|
@ -53,6 +53,11 @@ export type EndpointListItem = {
|
||||
hook_id: string
|
||||
}
|
||||
|
||||
export type PluginDeclarationMeta = {
|
||||
version: string
|
||||
minimum_dify_version?: string
|
||||
}
|
||||
|
||||
// Plugin manifest
|
||||
export type PluginDeclaration = {
|
||||
plugin_unique_identifier: string
|
||||
@ -72,6 +77,7 @@ export type PluginDeclaration = {
|
||||
model: any
|
||||
tags: string[]
|
||||
agent_strategy: any
|
||||
meta: PluginDeclarationMeta
|
||||
}
|
||||
|
||||
export type PluginManifestInMarket = {
|
||||
|
@ -209,6 +209,7 @@ const translation = {
|
||||
clearAll: 'Clear all',
|
||||
},
|
||||
submitPlugin: 'Submit plugin',
|
||||
difyVersionNotCompatible: 'The current Dify version is not compatible with this plugin, please upgrade to the minimum version required: {{minimalDifyVersion}}',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
@ -206,6 +206,7 @@ const translation = {
|
||||
installPlugin: 'プラグインをインストールする',
|
||||
searchInMarketplace: 'マーケットプレイスで検索',
|
||||
submitPlugin: 'プラグインを提出する',
|
||||
difyVersionNotCompatible: '現在のDifyバージョンはこのプラグインと互換性がありません。最小バージョンは{{minimalDifyVersion}}です。',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
@ -209,6 +209,7 @@ const translation = {
|
||||
clearAll: '清除所有',
|
||||
},
|
||||
submitPlugin: '上传插件',
|
||||
difyVersionNotCompatible: '当前 Dify 版本不兼容该插件,其最低版本要求为 {{minimalDifyVersion}}',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
@ -14,6 +14,7 @@ import type {
|
||||
PackageDependency,
|
||||
Permissions,
|
||||
Plugin,
|
||||
PluginDeclaration,
|
||||
PluginDetail,
|
||||
PluginInfoFromMarketPlace,
|
||||
PluginTask,
|
||||
@ -118,6 +119,14 @@ export const useUpdatePackageFromMarketPlace = (options?: MutateOptions<InstallP
|
||||
})
|
||||
}
|
||||
|
||||
export const usePluginDeclarationFromMarketPlace = (pluginUniqueIdentifier: string) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'pluginDeclaration', pluginUniqueIdentifier],
|
||||
queryFn: () => get<{ manifest: PluginDeclaration }>('/workspaces/current/plugin/marketplace/pkg', { params: { plugin_unique_identifier: pluginUniqueIdentifier } }),
|
||||
enabled: !!pluginUniqueIdentifier,
|
||||
})
|
||||
}
|
||||
|
||||
export const useVersionListOfPlugin = (pluginID: string) => {
|
||||
return useQuery<{ data: VersionListResponse }>({
|
||||
enabled: !!pluginID,
|
||||
|
Loading…
x
Reference in New Issue
Block a user