diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index 19d7ce8722..9d7c75e361 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -113,6 +113,22 @@ class PluginIconApi(Resource): icon_cache_max_age = dify_config.TOOL_ICON_CACHE_MAX_AGE return send_file(io.BytesIO(icon_bytes), mimetype=mimetype, max_age=icon_cache_max_age) +class PluginAssetApi(Resource): + @setup_required + @login_required + @account_initialization_required + def get(self): + req = reqparse.RequestParser() + req.add_argument("plugin_unique_identifier", type=str, required=True, location="args") + req.add_argument("file_name", type=str, required=True, location="args") + args = req.parse_args() + + tenant_id = current_user.current_tenant_id + try: + binary = PluginService.extract_asset(tenant_id, args["plugin_unique_identifier"], args["file_name"]) + return send_file(io.BytesIO(binary), mimetype="application/octet-stream") + except PluginDaemonClientSideError as e: + raise ValueError(e) @console_ns.route("/workspaces/current/plugin/upload/pkg") class PluginUploadFromPkgApi(Resource): @@ -688,3 +704,58 @@ class PluginAutoUpgradeExcludePluginApi(Resource): args = req.parse_args() return jsonable_encoder({"success": PluginAutoUpgradeService.exclude_plugin(tenant_id, args["plugin_id"])}) + + +class PluginReadmeApi(Resource): + @setup_required + @login_required + @account_initialization_required + 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") + parser.add_argument("language", type=str, required=False, location="args") + args = parser.parse_args() + return jsonable_encoder( + { + "readme": PluginService.fetch_plugin_readme( + tenant_id, + args["plugin_unique_identifier"], + args.get("language", "en-US") + ) + } + ) + + +api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key") +api.add_resource(PluginListApi, "/workspaces/current/plugin/list") +api.add_resource(PluginReadmeApi, "/workspaces/current/plugin/readme") +api.add_resource(PluginListLatestVersionsApi, "/workspaces/current/plugin/list/latest-versions") +api.add_resource(PluginListInstallationsFromIdsApi, "/workspaces/current/plugin/list/installations/ids") +api.add_resource(PluginIconApi, "/workspaces/current/plugin/icon") +api.add_resource(PluginAssetApi, "/workspaces/current/plugin/asset") +api.add_resource(PluginUploadFromPkgApi, "/workspaces/current/plugin/upload/pkg") +api.add_resource(PluginUploadFromGithubApi, "/workspaces/current/plugin/upload/github") +api.add_resource(PluginUploadFromBundleApi, "/workspaces/current/plugin/upload/bundle") +api.add_resource(PluginInstallFromPkgApi, "/workspaces/current/plugin/install/pkg") +api.add_resource(PluginInstallFromGithubApi, "/workspaces/current/plugin/install/github") +api.add_resource(PluginUpgradeFromMarketplaceApi, "/workspaces/current/plugin/upgrade/marketplace") +api.add_resource(PluginUpgradeFromGithubApi, "/workspaces/current/plugin/upgrade/github") +api.add_resource(PluginInstallFromMarketplaceApi, "/workspaces/current/plugin/install/marketplace") +api.add_resource(PluginFetchManifestApi, "/workspaces/current/plugin/fetch-manifest") +api.add_resource(PluginFetchInstallTasksApi, "/workspaces/current/plugin/tasks") +api.add_resource(PluginFetchInstallTaskApi, "/workspaces/current/plugin/tasks/") +api.add_resource(PluginDeleteInstallTaskApi, "/workspaces/current/plugin/tasks//delete") +api.add_resource(PluginDeleteAllInstallTaskItemsApi, "/workspaces/current/plugin/tasks/delete_all") +api.add_resource(PluginDeleteInstallTaskItemApi, "/workspaces/current/plugin/tasks//delete/") +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") + +api.add_resource(PluginFetchDynamicSelectOptionsApi, "/workspaces/current/plugin/parameters/dynamic-options") + +api.add_resource(PluginFetchPreferencesApi, "/workspaces/current/plugin/preferences/fetch") +api.add_resource(PluginChangePreferencesApi, "/workspaces/current/plugin/preferences/change") +api.add_resource(PluginAutoUpgradeExcludePluginApi, "/workspaces/current/plugin/preferences/autoupgrade/exclude") diff --git a/api/core/plugin/entities/plugin_daemon.py b/api/core/plugin/entities/plugin_daemon.py index 97b8a8c5ba..a3ab65c8f9 100644 --- a/api/core/plugin/entities/plugin_daemon.py +++ b/api/core/plugin/entities/plugin_daemon.py @@ -252,3 +252,7 @@ class CredentialType(enum.StrEnum): return cls.UNAUTHORIZED else: raise ValueError(f"Invalid credential type: {credential_type}") + +class PluginReadmeResponse(BaseModel): + content: str = Field(description="The readme of the plugin.") + language: str = Field(description="The language of the readme.") diff --git a/api/core/plugin/impl/asset.py b/api/core/plugin/impl/asset.py index b9bfe2d2cf..0c6a744bb5 100644 --- a/api/core/plugin/impl/asset.py +++ b/api/core/plugin/impl/asset.py @@ -10,3 +10,9 @@ class PluginAssetManager(BasePluginClient): if response.status_code != 200: raise ValueError(f"can not found asset {id}") return response.content + + def extract_asset(self, tenant_id: str, plugin_unique_identifier: str, filename: str) -> bytes: + response = self._request(method="GET", path=f"plugin/{tenant_id}/asset/{plugin_unique_identifier}") + if response.status_code != 200: + raise ValueError(f"can not found asset {plugin_unique_identifier}, {str(response.status_code)}") + return response.content diff --git a/api/core/plugin/impl/plugin.py b/api/core/plugin/impl/plugin.py index 18b5fa8af6..6124b0dd8d 100644 --- a/api/core/plugin/impl/plugin.py +++ b/api/core/plugin/impl/plugin.py @@ -1,5 +1,7 @@ from collections.abc import Sequence +from requests import HTTPError + from core.plugin.entities.bundle import PluginBundleDependency from core.plugin.entities.plugin import ( MissingPluginDependency, @@ -13,12 +15,35 @@ from core.plugin.entities.plugin_daemon import ( PluginInstallTask, PluginInstallTaskStartResponse, PluginListResponse, + PluginReadmeResponse, ) from core.plugin.impl.base import BasePluginClient from models.provider_ids import GenericProviderID class PluginInstaller(BasePluginClient): + def fetch_plugin_readme(self, tenant_id: str, plugin_unique_identifier: str, language: str) -> str: + """ + Fetch plugin readme + """ + try: + response = self._request_with_plugin_daemon_response( + "GET", + f"plugin/{tenant_id}/management/fetch/readme", + PluginReadmeResponse, + params={ + "tenant_id":tenant_id, + "plugin_unique_identifier": plugin_unique_identifier, + "language": language + } + ) + return response.content + except HTTPError as e: + message = e.args[0] + if "404" in message: + return "" + raise e + def fetch_plugin_by_identifier( self, tenant_id: str, diff --git a/api/services/plugin/plugin_service.py b/api/services/plugin/plugin_service.py index 3ac02428a4..b8303eb724 100644 --- a/api/services/plugin/plugin_service.py +++ b/api/services/plugin/plugin_service.py @@ -193,6 +193,11 @@ class PluginService: mime_type, _ = guess_type(asset_file) return manager.fetch_asset(tenant_id, asset_file), mime_type or "application/octet-stream" + @staticmethod + def extract_asset(tenant_id: str, plugin_unique_identifier: str, file_name: str) -> bytes: + manager = PluginAssetManager() + return manager.extract_asset(tenant_id, plugin_unique_identifier, file_name) + @staticmethod def check_plugin_unique_identifier(tenant_id: str, plugin_unique_identifier: str) -> bool: """ @@ -510,3 +515,11 @@ class PluginService: """ manager = PluginInstaller() return manager.check_tools_existence(tenant_id, provider_ids) + + @staticmethod + def fetch_plugin_readme(tenant_id: str, plugin_unique_identifier: str, language: str) -> str: + """ + Fetch plugin readme + """ + manager = PluginInstaller() + return manager.fetch_plugin_readme(tenant_id, plugin_unique_identifier, language)