mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-02 02:00:39 +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)
|
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):
|
class PluginFetchManifestApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_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(PluginDeleteAllInstallTaskItemsApi, "/workspaces/current/plugin/tasks/delete_all")
|
||||||
api.add_resource(PluginDeleteInstallTaskItemApi, "/workspaces/current/plugin/tasks/<task_id>/delete/<path:identifier>")
|
api.add_resource(PluginDeleteInstallTaskItemApi, "/workspaces/current/plugin/tasks/<task_id>/delete/<path:identifier>")
|
||||||
api.add_resource(PluginUninstallApi, "/workspaces/current/plugin/uninstall")
|
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(PluginChangePermissionApi, "/workspaces/current/plugin/permission/change")
|
||||||
api.add_resource(PluginFetchPermissionApi, "/workspaces/current/plugin/permission/fetch")
|
api.add_resource(PluginFetchPermissionApi, "/workspaces/current/plugin/permission/fetch")
|
||||||
|
@ -70,6 +70,9 @@ class PluginDeclaration(BaseModel):
|
|||||||
models: Optional[list[str]] = Field(default_factory=list)
|
models: Optional[list[str]] = Field(default_factory=list)
|
||||||
endpoints: 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})?$")
|
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}$")
|
author: Optional[str] = Field(..., pattern=r"^[a-zA-Z0-9_-]{1,64}$")
|
||||||
name: str = Field(..., pattern=r"^[a-z0-9_-]{1,128}$")
|
name: str = Field(..., pattern=r"^[a-z0-9_-]{1,128}$")
|
||||||
@ -86,6 +89,7 @@ class PluginDeclaration(BaseModel):
|
|||||||
model: Optional[ProviderEntity] = None
|
model: Optional[ProviderEntity] = None
|
||||||
endpoint: Optional[EndpointProviderDeclaration] = None
|
endpoint: Optional[EndpointProviderDeclaration] = None
|
||||||
agent_strategy: Optional[AgentStrategyProviderEntity] = None
|
agent_strategy: Optional[AgentStrategyProviderEntity] = None
|
||||||
|
meta: Meta
|
||||||
|
|
||||||
@model_validator(mode="before")
|
@model_validator(mode="before")
|
||||||
@classmethod
|
@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
|
@staticmethod
|
||||||
def install_from_marketplace_pkg(
|
def install_from_marketplace_pkg(
|
||||||
tenant_id: str, plugin_unique_identifiers: Sequence[str], verify_signature: bool = False
|
tenant_id: str, plugin_unique_identifiers: Sequence[str], verify_signature: bool = False
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect, useMemo } from 'react'
|
||||||
import { type PluginDeclaration, TaskStatus } from '../../../types'
|
import { type PluginDeclaration, TaskStatus } from '../../../types'
|
||||||
import Card from '../../../card'
|
import Card from '../../../card'
|
||||||
import { pluginManifestToCardPluginProps } from '../../utils'
|
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 useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
|
||||||
import { uninstallPlugin } from '@/service/plugins'
|
import { uninstallPlugin } from '@/service/plugins'
|
||||||
import Version from '../../base/version'
|
import Version from '../../base/version'
|
||||||
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
import { gte } from 'semver'
|
||||||
|
|
||||||
const i18nPrefix = 'plugin.installModal'
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3'>
|
<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' /> }}
|
components={{ trustSource: <span className='system-md-semibold' /> }}
|
||||||
/>
|
/>
|
||||||
</p>
|
</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>
|
||||||
<div className='flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2'>
|
<div className='flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2'>
|
||||||
<Card
|
<Card
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect, useMemo } from 'react'
|
||||||
// import { RiInformation2Line } from '@remixicon/react'
|
// import { RiInformation2Line } from '@remixicon/react'
|
||||||
import { type Plugin, type PluginManifestInMarket, TaskStatus } from '../../../types'
|
import { type Plugin, type PluginManifestInMarket, TaskStatus } from '../../../types'
|
||||||
import Card from '../../../card'
|
import Card from '../../../card'
|
||||||
@ -8,11 +8,13 @@ import { pluginManifestInMarketToPluginProps } from '../../utils'
|
|||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { RiLoader2Line } from '@remixicon/react'
|
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 checkTaskStatus from '../../base/check-task-status'
|
||||||
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
|
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
|
||||||
import Version from '../../base/version'
|
import Version from '../../base/version'
|
||||||
import { usePluginTaskList } from '@/service/use-plugins'
|
import { usePluginTaskList } from '@/service/use-plugins'
|
||||||
|
import { gte } from 'semver'
|
||||||
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
|
||||||
const i18nPrefix = 'plugin.installModal'
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3'>
|
<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'>
|
<div className='system-md-regular text-text-secondary'>
|
||||||
<p>{t(`${i18nPrefix}.readyToInstall`)}</p>
|
<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>
|
||||||
<div className='flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2'>
|
<div className='flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2'>
|
||||||
<Card
|
<Card
|
||||||
|
@ -4,6 +4,7 @@ import React, { useMemo } from 'react'
|
|||||||
import {
|
import {
|
||||||
RiArrowRightUpLine,
|
RiArrowRightUpLine,
|
||||||
RiBugLine,
|
RiBugLine,
|
||||||
|
RiErrorWarningLine,
|
||||||
RiHardDrive3Line,
|
RiHardDrive3Line,
|
||||||
RiLoginCircleLine,
|
RiLoginCircleLine,
|
||||||
RiVerifiedBadgeLine,
|
RiVerifiedBadgeLine,
|
||||||
@ -23,6 +24,9 @@ import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
|||||||
import { useSingleCategories } from '../hooks'
|
import { useSingleCategories } from '../hooks'
|
||||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
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 = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
@ -48,12 +52,20 @@ const PluginItem: FC<Props> = ({
|
|||||||
meta,
|
meta,
|
||||||
plugin_id,
|
plugin_id,
|
||||||
} = plugin
|
} = 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(() => {
|
const orgName = useMemo(() => {
|
||||||
return [PluginSource.github, PluginSource.marketplace].includes(source) ? author : ''
|
return [PluginSource.github, PluginSource.marketplace].includes(source) ? author : ''
|
||||||
}, [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 = () => {
|
const handleDelete = () => {
|
||||||
refreshPluginList({ category } as any)
|
refreshPluginList({ category } as any)
|
||||||
}
|
}
|
||||||
@ -89,6 +101,9 @@ const PluginItem: FC<Props> = ({
|
|||||||
<div className="flex h-5 items-center">
|
<div className="flex h-5 items-center">
|
||||||
<Title title={title} />
|
<Title title={title} />
|
||||||
{verified && <RiVerifiedBadgeLine className="ml-0.5 h-4 w-4 shrink-0 text-text-accent" />}
|
{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} />
|
<Badge className='ml-1 shrink-0' text={source === PluginSource.github ? plugin.meta!.version : plugin.version} />
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
|
@ -53,6 +53,11 @@ export type EndpointListItem = {
|
|||||||
hook_id: string
|
hook_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PluginDeclarationMeta = {
|
||||||
|
version: string
|
||||||
|
minimum_dify_version?: string
|
||||||
|
}
|
||||||
|
|
||||||
// Plugin manifest
|
// Plugin manifest
|
||||||
export type PluginDeclaration = {
|
export type PluginDeclaration = {
|
||||||
plugin_unique_identifier: string
|
plugin_unique_identifier: string
|
||||||
@ -72,6 +77,7 @@ export type PluginDeclaration = {
|
|||||||
model: any
|
model: any
|
||||||
tags: string[]
|
tags: string[]
|
||||||
agent_strategy: any
|
agent_strategy: any
|
||||||
|
meta: PluginDeclarationMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PluginManifestInMarket = {
|
export type PluginManifestInMarket = {
|
||||||
|
@ -209,6 +209,7 @@ const translation = {
|
|||||||
clearAll: 'Clear all',
|
clearAll: 'Clear all',
|
||||||
},
|
},
|
||||||
submitPlugin: 'Submit plugin',
|
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
|
export default translation
|
||||||
|
@ -206,6 +206,7 @@ const translation = {
|
|||||||
installPlugin: 'プラグインをインストールする',
|
installPlugin: 'プラグインをインストールする',
|
||||||
searchInMarketplace: 'マーケットプレイスで検索',
|
searchInMarketplace: 'マーケットプレイスで検索',
|
||||||
submitPlugin: 'プラグインを提出する',
|
submitPlugin: 'プラグインを提出する',
|
||||||
|
difyVersionNotCompatible: '現在のDifyバージョンはこのプラグインと互換性がありません。最小バージョンは{{minimalDifyVersion}}です。',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -209,6 +209,7 @@ const translation = {
|
|||||||
clearAll: '清除所有',
|
clearAll: '清除所有',
|
||||||
},
|
},
|
||||||
submitPlugin: '上传插件',
|
submitPlugin: '上传插件',
|
||||||
|
difyVersionNotCompatible: '当前 Dify 版本不兼容该插件,其最低版本要求为 {{minimalDifyVersion}}',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -14,6 +14,7 @@ import type {
|
|||||||
PackageDependency,
|
PackageDependency,
|
||||||
Permissions,
|
Permissions,
|
||||||
Plugin,
|
Plugin,
|
||||||
|
PluginDeclaration,
|
||||||
PluginDetail,
|
PluginDetail,
|
||||||
PluginInfoFromMarketPlace,
|
PluginInfoFromMarketPlace,
|
||||||
PluginTask,
|
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) => {
|
export const useVersionListOfPlugin = (pluginID: string) => {
|
||||||
return useQuery<{ data: VersionListResponse }>({
|
return useQuery<{ data: VersionListResponse }>({
|
||||||
enabled: !!pluginID,
|
enabled: !!pluginID,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user