diff --git a/locales/en_US/error.json b/locales/en_US/error.json index da53f0f8f8de6..1b84fa913fe6f 100644 --- a/locales/en_US/error.json +++ b/locales/en_US/error.json @@ -26,7 +26,9 @@ "PluginApiParamsError": "Sorry, the input parameter validation for the plugin request failed. Please check if the input parameters match the API description", "PluginSettingsInvalid": "This plugin needs to be correctly configured before it can be used. Please check if your configuration is correct", "PluginServerError": "Plugin server request returned an error. Please check your plugin manifest file, plugin configuration, or server implementation based on the error information below", - "NoAPIKey": "OpenAI API Key is empty, please add a custom OpenAI API Key" + "NoAPIKey": "OpenAI API Key is empty, please add a custom OpenAI API Key", + "PluginGatewayError": "Sorry, there was an error with the plugin gateway. Please check if the plugin gateway configuration is correct.", + "PluginOpenApiInitError": "Sorry, the OpenAPI client failed to initialize. Please check if the OpenAPI configuration information is correct." }, "stt": { "responseError": "Service request failed, please check the configuration or try again" diff --git a/locales/en_US/plugin.json b/locales/en_US/plugin.json index ea7e0651e5780..725f17ffa567e 100644 --- a/locales/en_US/plugin.json +++ b/locales/en_US/plugin.json @@ -2,121 +2,168 @@ "debug": { "arguments": "Arguments", "function_call": "Function Call", - "off": "Turn Off Debug", - "on": "View Plugin Call Information", - "response": "Response" + "response": "Response", + "off": "Turn off debug", + "on": "View plugin invocation information" + }, + "detailModal": { + "info": { + "description": "API Description", + "name": "API Name" + }, + "tabs": { + "info": "Plugin Capabilities", + "manifest": "Installation File", + "settings": "Settings" + }, + "title": "Plugin Details" }, "dev": { "confirmDeleteDevPlugin": "Are you sure you want to delete this local plugin? Once deleted, it cannot be recovered.", "deleteSuccess": "Plugin deleted successfully", "manifest": { "identifier": { - "desc": "Unique identifier for the plugin", + "desc": "The unique identifier of the plugin", "label": "Identifier" }, "mode": { - "local": "Local Configuration", - "local-tooltip": "Local configuration is temporarily not supported", + "local": "Visual Configuration", + "local-tooltip": "Visual configuration is not supported at the moment", "url": "Online Link" }, "name": { - "desc": "Plugin title", + "desc": "The title of the plugin", "label": "Title", "placeholder": "Search Engine" } }, "meta": { "author": { - "desc": "Author of the plugin", + "desc": "The author of the plugin", "label": "Author" }, "avatar": { - "desc": "Plugin icon, can use Emoji or URL", + "desc": "The icon of the plugin, can be an Emoji or a URL", "label": "Icon" }, "description": { - "desc": "Plugin description", + "desc": "The description of the plugin", "label": "Description", - "placeholder": "Query search engine for information" + "placeholder": "Get information from search engines" }, "formFieldRequired": "This field is required", "homepage": { - "desc": "Plugin homepage", + "desc": "The homepage of the plugin", "label": "Homepage" }, "identifier": { - "desc": "Unique identifier for the plugin, will be automatically recognized from the manifest", - "errorDuplicate": "Identifier is already used by another plugin, please modify the identifier", + "desc": "The unique identifier of the plugin, only supports alphanumeric characters, hyphen -, and underscore _", + "errorDuplicate": "The identifier is already used by another plugin, please modify the identifier", "label": "Identifier", - "pattenErrorMessage": "Only English characters, numbers, - and _ are allowed" + "pattenErrorMessage": "Only alphanumeric characters, hyphen -, and underscore _ are allowed" }, "manifest": { - "desc": "LobeChat will install the plugin through this link", - "label": "Plugin Description File (Manifest) URL", + "desc": "LobeChat will install the plugin using this link", + "invalid": "The input manifest link is invalid or does not comply with the specification", + "urlError": "Please enter a valid URL", + "jsonInvalid": "The manifest is not valid, validation result: \n\n {{error}}", "preview": "Preview Manifest", - "refresh": "Refresh" + "refresh": "Refresh", + "requestError": "Failed to request the link, please enter a valid link and check if the link allows cross-origin access", + "label": "Plugin Description (Manifest) URL" }, "title": { - "desc": "Plugin title", + "desc": "The title of the plugin", "label": "Title", "placeholder": "Search Engine" } }, "metaConfig": "Plugin metadata configuration", - "modalDesc": "After adding a custom plugin, it can be used for plugin development validation or directly in the session. For plugin development, please refer to the <1>development documentation↗.", "preview": { - "card": "Preview Plugin Display", - "desc": "Preview Plugin Description", + "card": "Preview of plugin display", + "desc": "Preview of plugin description", "title": "Plugin Name Preview" }, - "save": "Install Plugin", "saveSuccess": "Plugin settings saved successfully", "tabs": { - "manifest": "Manifest Description", + "manifest": "Function Description Manifest (Manifest)", "meta": "Plugin Metadata" }, - "title": "Add Custom Plugin", "update": "Update", - "updateSuccess": "Plugin settings updated successfully" + "updateSuccess": "Plugin settings updated successfully", + "modalDesc": "After adding a custom plugin, it can be used for plugin development verification or directly in the session. Please refer to the <1>development documentation↗ for plugin development.", + "openai": { + "importUrl": "Import from URL link", + "schema": "Schema" + }, + "save": "Install Plugin", + "type": { + "lobe": "LobeChat Plugin", + "openai": "OpenAI Plugin" + }, + "title": { + "create": "Add Custom Plugin", + "edit": "Edit Custom Plugin" + } }, "error": { - "fetchError": "Failed to fetch the manifest link. Please ensure the link is valid and allows cross-origin access.", - "installError": "Failed to install plugin {{name}}", - "manifestInvalid": "The manifest does not comply with the specification. Validation result: \n\n {{error}}", + "installError": "Plugin {{name}} installation failed", "noManifest": "Manifest file does not exist", + "fetchError": "Failed to fetch the manifest link. Please ensure the link is valid and allows cross-origin access.", + "manifestInvalid": "The manifest does not conform to the specification. Validation result: \n\n {{error}}", + "openAPIInvalid": "OpenAPI parsing failed. Error: \n\n {{error}}", "reinstallError": "Failed to refresh plugin {{name}}", - "urlError": "The link did not return content in JSON format. Please ensure it is a valid link" + "urlError": "The link did not return content in JSON format. Please ensure it is a valid link." }, "list": { "item": { - "deprecated.title": "Deprecated", "local.config": "Configuration", - "local.title": "Custom" + "local.title": "Local", + "deprecated.title": "Deleted" } }, "loading": { - "content": "Calling plugin...", - "plugin": "Plugin running..." + "plugin": "Plugin is running...", + "content": "Calling plugin..." }, "pluginList": "Plugin List", "plugins": { - "loading": "Checking plugins...", - "unknown": "Unknown plugin" + "realtimeWeather": "Realtime Weather", + "searchEngine": "Search Engine", + "undefined": "Plugin Detection...", + "websiteCrawler": "Website Crawler", + "loading": "Detecting plugins...", + "unknown": "Unknown Plugin" }, + "realtimeWeather": { + "data": { + "date": "Date", + "daytemp_float": "Daytime Temperature", + "dayweather": "Daytime Weather", + "daywind": "Daytime Wind", + "nighttemp_float": "Nighttime Temperature", + "nightweather": "Nighttime Weather", + "nightwind": "Nighttime Wind", + "week": "Week" + }, + "title": "Weather Data for the Next 7 Days ({{city}})", + "updateAt": "Last Updated" + }, + "responseData": "Response Data", "setting": "Plugin Settings", "settings": { "indexUrl": { "title": "Marketplace Index", - "tooltip": "Online editing is not supported for now. Please set it through environment variables during deployment." + "tooltip": "Editing is not supported at the moment" }, - "modalDesc": "After configuring the address of the plugin marketplace, you can use a custom plugin marketplace.", - "title": "Set Plugin Marketplace" + "modalDesc": "After configuring the address of the plugin marketplace, you can use a custom plugin marketplace", + "title": "Configure Plugin Marketplace" }, "store": { "empty": "No installed plugins yet", "install": "Install", "installAllPlugins": "Install All", - "networkError": "Failed to fetch the plugin store. Please check your network connection and try again.", + "networkError": "Failed to fetch plugin store. Please check your network connection and try again", "releasedAt": "Released at {{createdAt}}", "tabs": { "all": "All", @@ -124,6 +171,15 @@ }, "title": "Plugin Store", "uninstall": "Uninstall", - "placeholder": "Search for plugin name, description, or keywords..." + "placeholder": "Search for plugin name, description, or keyword...", + "actions": { + "confirmUninstall": "The plugin is about to be uninstalled. After uninstalling, the plugin configuration will be cleared. Please confirm your operation.", + "detail": "Details", + "install": "Install", + "manifest": "Edit Installation File", + "settings": "Settings", + "uninstall": "Uninstall" + }, + "customPlugin": "Custom Plugin" } } diff --git a/locales/fr_FR/error.json b/locales/fr_FR/error.json index d76fba01d36b8..99db58c699821 100644 --- a/locales/fr_FR/error.json +++ b/locales/fr_FR/error.json @@ -26,7 +26,9 @@ "PluginServerError": "Erreur de réponse du serveur du plugin. Veuillez vérifier le fichier de description du plugin, la configuration du plugin ou la mise en œuvre côté serveur en fonction des informations d'erreur ci-dessous", "InvalidAccessCode": "Mot de passe incorrect ou vide. Veuillez saisir un mot de passe d'accès correct ou ajouter une clé API OpenAI personnalisée", "OpenAIBizError": "Erreur de service OpenAI. Veuillez diagnostiquer ou réessayer en fonction des informations ci-dessous", - "NoAPIKey": "La clé API OpenAI est vide. Veuillez ajouter une clé API OpenAI personnalisée" + "NoAPIKey": "La clé API OpenAI est vide. Veuillez ajouter une clé API OpenAI personnalisée", + "PluginGatewayError": "Désolé, une erreur est survenue avec la passerelle du plugin. Veuillez vérifier la configuration de la passerelle du plugin.", + "PluginOpenApiInitError": "Désolé, l'initialisation du client OpenAPI a échoué. Veuillez vérifier les informations de configuration d'OpenAPI." }, "stt": { "responseError": "Échec de la requête de service. Veuillez vérifier la configuration ou réessayer" diff --git a/locales/fr_FR/plugin.json b/locales/fr_FR/plugin.json index 9772f672af163..fc0eaa4aaec99 100644 --- a/locales/fr_FR/plugin.json +++ b/locales/fr_FR/plugin.json @@ -6,6 +6,18 @@ "on": "Activer le débogage", "response": "Réponse" }, + "detailModal": { + "info": { + "description": "Description de l'API", + "name": "Nom de l'API" + }, + "tabs": { + "info": "Capacités du plugin", + "manifest": "Fichier d'installation", + "settings": "Paramètres" + }, + "title": "Détails du plugin" + }, "dev": { "confirmDeleteDevPlugin": "Êtes-vous sûr de vouloir supprimer ce plugin local ? Cette action est irréversible.", "deleteSuccess": "Suppression du plugin réussie", @@ -75,9 +87,20 @@ "manifest": "Manifeste des fonctionnalités", "meta": "Métadonnées du plugin" }, - "title": "Ajouter un plugin personnalisé", "update": "Mettre à jour", - "updateSuccess": "Paramètres du plugin mis à jour avec succès" + "updateSuccess": "Paramètres du plugin mis à jour avec succès", + "openai": { + "importUrl": "Importer depuis l'URL", + "schema": "Schéma" + }, + "type": { + "lobe": "Plugin LobeChat", + "openai": "Plugin OpenAI" + }, + "title": { + "create": "Ajouter un plugin personnalisé", + "edit": "Modifier un plugin personnalisé" + } }, "error": { "fetchError": "Échec de la requête vers ce lien de manifest. Veuillez vous assurer que le lien est valide et autorise les requêtes cross-origin.", @@ -85,7 +108,8 @@ "manifestInvalid": "Le manifest ne respecte pas les normes. Résultat de la validation : \n\n {{error}}", "noManifest": "Aucun fichier de description trouvé", "reinstallError": "Échec de la mise à jour du plugin {{name}}", - "urlError": "Ce lien ne renvoie pas de contenu au format JSON. Veuillez vous assurer qu'il s'agit d'un lien valide." + "urlError": "Ce lien ne renvoie pas de contenu au format JSON. Veuillez vous assurer qu'il s'agit d'un lien valide.", + "openAPIInvalid": "Échec d'analyse de l'OpenAPI, erreur : \n\n {{error}}" }, "list": { "item": { @@ -124,6 +148,15 @@ }, "title": "Boutique de plugins", "uninstall": "Désinstaller", - "placeholder": "Rechercher le nom ou les mots-clés de l'extension..." + "placeholder": "Rechercher le nom ou les mots-clés de l'extension...", + "actions": { + "confirmUninstall": "Vous êtes sur le point de désinstaller ce plugin. Une fois désinstallé, sa configuration sera effacée. Veuillez confirmer votre action.", + "detail": "Détails", + "install": "Installer", + "manifest": "Modifier le fichier d'installation", + "settings": "Paramètres", + "uninstall": "Désinstaller" + }, + "customPlugin": "Plugin personnalisé" } } diff --git a/locales/ja_JP/error.json b/locales/ja_JP/error.json index b667be088453c..babf4dcd262e9 100644 --- a/locales/ja_JP/error.json +++ b/locales/ja_JP/error.json @@ -26,7 +26,9 @@ "PluginManifestNotFound": "申し訳ありませんが、サーバーでプラグインのマニフェストファイル (manifest.json) が見つかりませんでした。プラグインのマニフェストファイルのアドレスが正しいかどうかを確認してください", "PluginManifestInvalid": "申し訳ありませんが、このプラグインのマニフェストの検証に失敗しました。マニフェストの形式が正しいかどうかを確認してください", "PluginApiNotFound": "申し訳ありませんが、プラグインのマニフェストに指定されたAPIが見つかりませんでした。リクエストメソッドとプラグインのマニフェストのAPIが一致しているかどうかを確認してください", - "NoAPIKey": "OpenAI APIキーが空です。カスタムOpenAI APIキーを追加してください。" + "NoAPIKey": "OpenAI APIキーが空です。カスタムOpenAI APIキーを追加してください。", + "PluginGatewayError": "申し訳ありませんが、プラグインゲートウェイでエラーが発生しました。プラグインゲートウェイの設定を確認してください。", + "PluginOpenApiInitError": "申し訳ありませんが、OpenAPIクライアントの初期化に失敗しました。OpenAPIの設定情報を確認してください。" }, "stt": { "responseError": "サービスリクエストが失敗しました。設定を確認するか、もう一度お試しください" diff --git a/locales/ja_JP/plugin.json b/locales/ja_JP/plugin.json index 3f6596dbabe2e..c05e8258532bd 100644 --- a/locales/ja_JP/plugin.json +++ b/locales/ja_JP/plugin.json @@ -1,13 +1,25 @@ { "debug": { - "arguments": "调用参数", - "function_call": "函数调用", - "off": "オフ", - "on": "プラグイン呼び出し情報を表示", - "response": "戻り値" + "arguments": "引数", + "function_call": "関数呼び出し", + "response": "レスポンス", + "off": "デバッグをオフにする", + "on": "プラグイン呼び出し情報を表示する" + }, + "detailModal": { + "info": { + "description": "API 説明", + "name": "API 名" + }, + "tabs": { + "info": "プラグイン機能", + "manifest": "インストールファイル", + "settings": "設定" + }, + "title": "プラグインの詳細" }, "dev": { - "confirmDeleteDevPlugin": "このローカルプラグインを削除します。削除後は元に戻せません。このプラグインを削除しますか?", + "confirmDeleteDevPlugin": "このローカルプラグインを削除しますか?削除後は元に戻せません。", "deleteSuccess": "プラグインが正常に削除されました", "manifest": { "identifier": { @@ -15,7 +27,7 @@ "label": "識別子" }, "mode": { - "local": "ローカル設定", + "local": "ビジュアル設定", "local-tooltip": "ビジュアル設定は一時的にサポートされていません", "url": "オンラインリンク" }, @@ -31,13 +43,13 @@ "label": "作者" }, "avatar": { - "desc": "プラグインのアイコン。Emojiを使用するか、URLを使用できます", + "desc": "プラグインのアイコン、絵文字やURLを使用できます", "label": "アイコン" }, "description": { "desc": "プラグインの説明", "label": "説明", - "placeholder": "検索エンジンで情報を取得" + "placeholder": "検索エンジンで情報を取得します" }, "formFieldRequired": "このフィールドは必須です", "homepage": { @@ -45,16 +57,19 @@ "label": "ホームページ" }, "identifier": { - "desc": "プラグインの一意の識別子。既存のプラグインと重複しています。識別子を変更してください", + "desc": "プラグインの一意の識別子、マニフェストから自動的に識別されます", "errorDuplicate": "識別子が既存のプラグインと重複しています。識別子を変更してください", "label": "識別子", - "pattenErrorMessage": "英数字、-、_ のみが入力できます" + "pattenErrorMessage": "英数字、-、_ のみ入力できます" }, "manifest": { "desc": "LobeChatはこのリンクを使用してプラグインをインストールします", - "label": "プラグインの説明ファイル(マニフェスト)のURL", + "jsonInvalid": "マニフェストが規格に準拠していません。検証結果:\n\n{{error}}", "preview": "マニフェストのプレビュー", - "refresh": "更新" + "refresh": "更新", + "requestError": "リンクのリクエストに失敗しました。有効なリンクを入力し、クロスオリジンリクエストが許可されているか確認してください", + "urlError": "このリンクはJSON形式のコンテンツを返していません。有効なリンクを入力してください", + "label": "プラグイン記述ファイル (Manifest) URL" }, "title": { "desc": "プラグインのタイトル", @@ -62,29 +77,41 @@ "placeholder": "検索エンジン" } }, - "metaConfig": "プラグインメタ情報の設定", - "modalDesc": "カスタムプラグインを追加すると、プラグイン開発の検証に使用できます。また、セッション中に直接使用できます。プラグイン開発については、<1>開発ドキュメント↗を参照してください", + "metaConfig": "プラグインのメタ情報の設定", "preview": { "card": "プラグインのプレビュー表示", "desc": "プラグインの説明のプレビュー", "title": "プラグイン名のプレビュー" }, - "save": "プラグインを保存", "saveSuccess": "プラグインの設定が正常に保存されました", "tabs": { - "manifest": "機能記述リスト(マニフェスト)", - "meta": "プラグインメタ情報" + "manifest": "機能のマニフェスト", + "meta": "プラグインのメタ情報" }, - "title": "カスタムプラグインを追加", "update": "更新", - "updateSuccess": "プラグインの設定が正常に更新されました" + "updateSuccess": "プラグインの設定が正常に更新されました", + "modalDesc": "カスタムプラグインを追加すると、プラグインの開発検証に使用したり、セッション中に直接使用したりできます。プラグインの開発については、<1>開発ドキュメント↗を参照してください", + "openai": { + "importUrl": "URLリンクからインポート", + "schema": "スキーマ" + }, + "save": "プラグインを保存", + "type": { + "lobe": "LobeChatプラグイン", + "openai": "OpenAIプラグイン" + }, + "title": { + "create": "カスタムプラグインを追加", + "edit": "カスタムプラグインを編集" + } }, "error": { - "fetchError": "このマニフェストリンクのリクエストに失敗しました。リンクが有効であることを確認し、クロスドメインアクセスが許可されているか確認してください", "installError": "プラグイン {{name}} のインストールに失敗しました", - "manifestInvalid": "マニフェストが規格に準拠していません。検証結果:\n\n{{error}}", - "noManifest": "説明ファイルが存在しません", - "reinstallError": "プラグイン {{name}} の再インストールに失敗しました", + "noManifest": "マニフェストが存在しません", + "fetchError": "このmanifestリンクのリクエストに失敗しました。リンクが有効であることを確認し、リンクがクロスドメインアクセスを許可しているかを確認してください", + "manifestInvalid": "manifestが仕様に準拠していません。検証結果: \n\n {{error}}", + "openAPIInvalid": "OpenAPIの解析に失敗しました。エラー: \n\n {{error}}", + "reinstallError": "プラグイン{{name}}の再インストールに失敗しました", "urlError": "このリンクはJSON形式のコンテンツを返していません。有効なリンクであることを確認してください" }, "list": { @@ -95,28 +122,28 @@ } }, "loading": { - "content": "プラグインを呼び出しています...", - "plugin": "プラグインを実行しています..." + "plugin": "プラグインの実行中...", + "content": "プラグインを呼び出しています..." }, "pluginList": "プラグインリスト", "plugins": { - "loading": "プラグインを読み込んでいます...", - "unknown": "未知のプラグイン" + "loading": "プラグインを読み込み中...", + "unknown": "不明なプラグイン" }, - "setting": "プラグイン設定", + "setting": "プラグインの設定", "settings": { "indexUrl": { - "title": "マーケットのインデックス", - "tooltip": "オンライン編集は現在利用できません。デプロイ時の環境変数を使用して設定してください" + "title": "マーケットインデックス", + "tooltip": "オンライン編集は現在サポートされていません。デプロイ時の環境変数を使用して設定してください" }, - "modalDesc": "プラグインマーケットのアドレスを設定すると、カスタムプラグインマーケットを使用できます", + "modalDesc": "プラグインマーケットのアドレスを設定すると、カスタムのプラグインマーケットを使用できます", "title": "プラグインマーケットの設定" }, "store": { "empty": "インストールされたプラグインはありません", "install": "インストール", "installAllPlugins": "すべてのプラグインをインストール", - "networkError": "プラグインストアの取得に失敗しました。ネットワーク接続を確認してからもう一度お試しください", + "networkError": "プラグインストアの取得に失敗しました。ネットワーク接続を確認してから再試行してください", "releasedAt": "{{createdAt}} にリリース", "tabs": { "all": "すべて", @@ -124,6 +151,15 @@ }, "title": "プラグインストア", "uninstall": "アンインストール", - "placeholder": "プラグイン名、説明、またはキーワードで検索..." + "placeholder": "プラグイン名、説明、またはキーワードで検索...", + "actions": { + "confirmUninstall": "このプラグインをアンインストールします。アンインストール後、プラグインの設定がクリアされます。操作を確認してください。", + "detail": "詳細", + "install": "インストール", + "manifest": "インストールファイルを編集", + "settings": "設定", + "uninstall": "アンインストール" + }, + "customPlugin": "カスタムプラグイン" } } diff --git a/locales/ko_KR/error.json b/locales/ko_KR/error.json index b934e1f3c955b..5df8f9fc8058c 100644 --- a/locales/ko_KR/error.json +++ b/locales/ko_KR/error.json @@ -26,7 +26,9 @@ "PluginServerError": "플러그인 서버 요청이 오류로 반환되었습니다. 플러그인 설명 파일, 플러그인 구성 또는 서버 구현을 확인해주세요.", "InvalidAccessCode": "암호가 올바르지 않거나 비어 있습니다. 올바른 액세스 암호를 입력하거나 사용자 지정 OpenAI API 키를 추가해주세요.", "OpenAIBizError": "OpenAI 서비스 요청 중 오류가 발생했습니다. 아래 정보를 확인하고 문제를 해결하거나 다시 시도해주세요.", - "NoAPIKey": "OpenAI API 키가 비어 있습니다. 사용자 정의 OpenAI API 키를 추가해주세요." + "NoAPIKey": "OpenAI API 키가 비어 있습니다. 사용자 정의 OpenAI API 키를 추가해주세요.", + "PluginGatewayError": "죄송합니다. 플러그인 게이트웨이에 오류가 발생했습니다. 플러그인 게이트웨이 구성을 확인해주세요.", + "PluginOpenApiInitError": "죄송합니다. OpenAPI 클라이언트 초기화에 실패했습니다. OpenAPI 구성 정보를 확인해주세요." }, "stt": { "responseError": "서비스 요청이 실패했습니다. 구성을 확인하거나 다시 시도해주세요." diff --git a/locales/ko_KR/plugin.json b/locales/ko_KR/plugin.json index ca752d669b62d..67f6d56cee1a3 100644 --- a/locales/ko_KR/plugin.json +++ b/locales/ko_KR/plugin.json @@ -1,14 +1,26 @@ { "debug": { - "arguments": "호출 매개변수", + "arguments": "함수 호출 인수", "function_call": "함수 호출", - "off": "디버깅 끄기", - "on": "플러그인 호출 정보 보기", - "response": "응답" + "response": "응답", + "off": "디버그 끄기", + "on": "플러그인 호출 정보 보기" + }, + "detailModal": { + "info": { + "description": "API 설명", + "name": "API 이름" + }, + "tabs": { + "info": "플러그인 능력", + "manifest": "설치 파일", + "settings": "설정" + }, + "title": "플러그인 상세정보" }, "dev": { - "confirmDeleteDevPlugin": "로컬 플러그인을 삭제하려고 합니다. 삭제한 후에는 복구할 수 없습니다. 이 플러그인을 삭제하시겠습니까?", - "deleteSuccess": "플러그인이 성공적으로 삭제되었습니다", + "confirmDeleteDevPlugin": "로컬 플러그인을 삭제하시겠습니까? 삭제 후에는 복구할 수 없습니다.", + "deleteSuccess": "플러그인이 성공적으로 삭제되었습니다.", "manifest": { "identifier": { "desc": "플러그인의 고유 식별자", @@ -16,7 +28,7 @@ }, "mode": { "local": "시각적 구성", - "local-tooltip": "시각적 구성은 현재 지원되지 않습니다", + "local-tooltip": "시각적 구성은 일시적으로 지원되지 않습니다.", "url": "온라인 링크" }, "name": { @@ -31,7 +43,7 @@ "label": "작성자" }, "avatar": { - "desc": "플러그인 아이콘, 이모지 또는 URL을 사용할 수 있습니다", + "desc": "플러그인 아이콘으로는 Emoji 또는 URL을 사용할 수 있습니다.", "label": "아이콘" }, "description": { @@ -39,22 +51,25 @@ "label": "설명", "placeholder": "검색 엔진에서 정보 가져오기" }, - "formFieldRequired": "이 필드는 필수 입력 사항입니다", + "formFieldRequired": "이 필드는 필수 입력 사항입니다.", "homepage": { "desc": "플러그인 홈페이지", "label": "홈페이지" }, "identifier": { - "desc": "플러그인의 고유 식별자는 manifest에서 자동으로 인식됩니다", - "errorDuplicate": "식별자가 기존 플러그인과 중복되었습니다. 식별자를 수정해주세요", + "desc": "플러그인의 고유 식별자는 manifest에서 자동으로 인식됩니다.", + "errorDuplicate": "식별자가 이미 있는 플러그인과 중복되었습니다. 식별자를 수정해주세요.", "label": "식별자", - "pattenErrorMessage": "영문자, 숫자, - 및 _ 만 입력할 수 있습니다" + "pattenErrorMessage": "영문자, 숫자, - 및 _만 입력할 수 있습니다." }, "manifest": { - "desc": "LobeChat은 이 링크를 통해 플러그인을 설치합니다", - "label": "플러그인 설명 파일 (Manifest) URL", - "preview": "미리보기", - "refresh": "새로고침" + "desc": "LobeChat은 이 링크를 통해 플러그인을 설치합니다.", + "jsonInvalid": "manifest가 규칙에 맞지 않습니다. 유효성 검사 결과: \n\n {{error}}", + "preview": "Manifest 미리보기", + "refresh": "새로 고침", + "requestError": "이 링크를 요청하는 중에 오류가 발생했습니다. 유효한 링크를 입력하고 링크가 크로스 도메인 액세스를 허용하는지 확인해주세요.", + "urlError": "이 링크는 JSON 형식의 내용을 반환하지 않습니다. 유효한 링크를 입력해주세요.", + "label": "Manifest 파일 URL" }, "title": { "desc": "플러그인 제목", @@ -63,29 +78,41 @@ } }, "metaConfig": "플러그인 메타 정보 구성", - "modalDesc": "사용자 정의 플러그인을 추가하면 플러그인 개발 확인 및 대화 중에 직접 사용할 수 있습니다. 플러그인 개발은 <1>개발 문서↗를 참조하세요", "preview": { - "card": "플러그인 효과 미리보기", + "card": "플러그인 미리보기", "desc": "플러그인 설명 미리보기", "title": "플러그인 이름 미리보기" }, - "save": "플러그인 설치", - "saveSuccess": "플러그인 설정이 성공적으로 저장되었습니다", + "saveSuccess": "플러그인 설정이 성공적으로 저장되었습니다.", "tabs": { "manifest": "기능 설명 목록 (Manifest)", "meta": "플러그인 메타 정보" }, - "title": "사용자 정의 플러그인 추가", "update": "업데이트", - "updateSuccess": "플러그인 설정이 성공적으로 업데이트되었습니다" + "updateSuccess": "플러그인 설정이 성공적으로 업데이트되었습니다.", + "modalDesc": "사용자 정의 플러그인을 추가하면 플러그인 개발을 검증하거나 세션에서 직접 사용할 수 있습니다. 플러그인 개발은 <1>개발 문서↗를 참조하세요.", + "openai": { + "importUrl": "URL 링크에서 가져오기", + "schema": "스키마" + }, + "save": "플러그인 설치", + "type": { + "lobe": "LobeChat 플러그인", + "openai": "OpenAI 플러그인" + }, + "title": { + "create": "사용자 정의 플러그인 추가", + "edit": "사용자 정의 플러그인 편집" + } }, "error": { - "fetchError": "해당 manifest 링크를 요청하는 중 오류가 발생했습니다. 링크의 유효성을 확인하고, 교차 출처 리소스 공유가 허용되는지 확인하세요", - "installError": "플러그인 {{name}} 설치 중 오류가 발생했습니다", - "manifestInvalid": "manifest가 규격에 맞지 않습니다. 검증 결과: \n\n {{error}}", + "installError": "플러그인 {{name}} 설치 실패", "noManifest": "설명 파일이 없습니다", - "reinstallError": "플러그인 {{name}} 새로고침 중 오류가 발생했습니다", - "urlError": "이 링크는 JSON 형식의 내용을 반환하지 않습니다. 유효한 링크인지 확인하세요" + "fetchError": "해당 manifest 링크를 요청하는 중 오류가 발생했습니다. 링크의 유효성을 확인하고, 링크가 크로스 도메인 액세스를 허용하는지 확인하세요.", + "manifestInvalid": "manifest가 규격에 맞지 않습니다. 유효성 검사 결과: \n\n {{error}}", + "openAPIInvalid": "OpenAPI 파싱에 실패했습니다. 오류: \n\n {{error}}", + "reinstallError": "플러그인 {{name}} 다시 설치 중 오류가 발생했습니다.", + "urlError": "이 링크는 JSON 형식의 내용을 반환하지 않습니다. 유효한 링크인지 확인하세요." }, "list": { "item": { @@ -95,35 +122,44 @@ } }, "loading": { - "content": "플러그인 호출 중...", - "plugin": "플러그인 실행 중..." + "plugin": "플러그인 실행 중...", + "content": "플러그인 호출 중..." }, "pluginList": "플러그인 목록", "plugins": { - "loading": "플러그인 확인 중...", + "loading": "플러그인을 확인하는 중...", "unknown": "알 수 없는 플러그인" }, "setting": "플러그인 설정", "settings": { "indexUrl": { - "title": "마켓 색인", - "tooltip": "온라인 편집을 지원하지 않으며, 배포 시 환경 변수를 통해 설정하십시오" + "title": "마켓 인덱스", + "tooltip": "온라인 편집은 지원되지 않습니다. 배포 환경 변수를 통해 설정해주세요." }, - "modalDesc": "플러그인 마켓 주소를 구성하면 사용자 정의 플러그인 마켓을 사용할 수 있습니다", + "modalDesc": "플러그인 마켓의 주소를 구성하면 사용자 정의 플러그인 마켓을 사용할 수 있습니다.", "title": "플러그인 마켓 설정" }, "store": { "empty": "설치된 플러그인이 없습니다", "install": "설치", "installAllPlugins": "모두 설치", - "networkError": "플러그인 상점을 가져오는 데 실패했습니다. 네트워크 연결을 확인한 후 다시 시도하십시오", - "releasedAt": "{{createdAt}}에 출시됨", + "networkError": "플러그인 스토어를 가져오는 데 실패했습니다. 네트워크 연결을 확인한 후 다시 시도하십시오", + "releasedAt": "{{createdAt}}에 출시", "tabs": { "all": "모두", "installed": "설치됨" }, - "title": "플러그인 상점", + "title": "플러그인 스토어", "uninstall": "제거", - "placeholder": "플러그인 이름 또는 키워드로 검색..." + "placeholder": "플러그인 이름 또는 키워드를 검색하세요...", + "actions": { + "confirmUninstall": "이 플러그인을 제거하려고 합니다. 제거하면 플러그인 구성이 지워지므로 작업을 확인하세요.", + "detail": "상세정보", + "install": "설치", + "manifest": "설치 파일 편집", + "settings": "설정", + "uninstall": "제거" + }, + "customPlugin": "사용자 정의 플러그인" } } diff --git a/locales/ru_RU/error.json b/locales/ru_RU/error.json index b2e9c19e9864c..ababbbdb943ef 100644 --- a/locales/ru_RU/error.json +++ b/locales/ru_RU/error.json @@ -26,7 +26,9 @@ "PluginApiParamsError": "К сожалению, проверка входных параметров для запроса плагина не удалась. Пожалуйста, проверьте, соответствуют ли входные параметры описанию API", "PluginSettingsInvalid": "Этот плагин необходимо правильно настроить, прежде чем его можно будет использовать. Пожалуйста, проверьте правильность вашей конфигурации", "PluginServerError": "Запрос сервера плагина возвратил ошибку. Проверьте файл манифеста плагина, конфигурацию плагина или реализацию сервера на основе информации об ошибке ниже", - "NoAPIKey": "Ключ OpenAI API пуст, пожалуйста, добавьте свой собственный ключ OpenAI API" + "NoAPIKey": "Ключ OpenAI API пуст, пожалуйста, добавьте свой собственный ключ OpenAI API", + "PluginGatewayError": "Извините, возникла ошибка шлюза плагина. Пожалуйста, проверьте правильность конфигурации шлюза плагина.", + "PluginOpenApiInitError": "Извините, не удалось инициализировать клиент OpenAPI. Пожалуйста, проверьте правильность информации конфигурации OpenAPI." }, "stt": { "responseError": "Ошибка запроса сервиса. Пожалуйста, проверьте конфигурацию или повторите попытку" diff --git a/locales/ru_RU/plugin.json b/locales/ru_RU/plugin.json index b23856ad868fe..53f17565bb915 100644 --- a/locales/ru_RU/plugin.json +++ b/locales/ru_RU/plugin.json @@ -2,12 +2,24 @@ "debug": { "arguments": "Аргументы вызова", "function_call": "Вызов функции", - "off": "Отключить отладку", - "on": "Показать информацию о вызове плагина", - "response": "Ответ" + "response": "Ответ", + "off": "Выключить отладку", + "on": "Просмотр информации о вызове плагина" + }, + "detailModal": { + "info": { + "description": "Описание API", + "name": "Название API" + }, + "tabs": { + "info": "Возможности плагина", + "manifest": "Файл установки", + "settings": "Настройки" + }, + "title": "Подробности плагина" }, "dev": { - "confirmDeleteDevPlugin": "Вы уверены, что хотите удалить этот локальный плагин? После удаления его будет невозможно восстановить.", + "confirmDeleteDevPlugin": "Вы собираетесь удалить этот локальный плагин. После удаления его будет невозможно восстановить. Вы уверены, что хотите удалить этот плагин?", "deleteSuccess": "Плагин успешно удален", "manifest": { "identifier": { @@ -15,14 +27,14 @@ "label": "Идентификатор" }, "mode": { - "local": "Локальная настройка", - "local-tooltip": "Визуальная настройка временно не поддерживается", + "local": "Визуальная настройка", + "local-tooltip": "Визуальная настройка временно недоступна", "url": "Онлайн-ссылка" }, "name": { - "desc": "Название плагина", - "label": "Название", - "placeholder": "Поиск поисковой системы" + "desc": "Заголовок плагина", + "label": "Заголовок", + "placeholder": "Поиск в поисковой системе" } }, "meta": { @@ -46,46 +58,61 @@ }, "identifier": { "desc": "Уникальный идентификатор плагина, будет автоматически определен из манифеста", - "errorDuplicate": "Идентификатор совпадает с существующим плагином, измените идентификатор", + "errorDuplicate": "Идентификатор уже используется другим плагином. Пожалуйста, измените идентификатор", "label": "Идентификатор", - "pattenErrorMessage": "Можно вводить только буквы, цифры, - и _" + "pattenErrorMessage": "Можно вводить только латинские буквы, цифры, - и _" }, "manifest": { - "desc": "LobeChat установит плагин через эту ссылку", - "label": "URL манифеста плагина", - "preview": "Предпросмотр манифеста", - "refresh": "Обновить" + "desc": "LobeChat будет устанавливать плагин по этой ссылке", + "jsonInvalid": "Манифест не соответствует стандарту. Результат проверки: \n\n {{error}}", + "preview": "Предварительный просмотр манифеста", + "refresh": "Обновить", + "requestError": "Не удалось получить данные по этой ссылке. Пожалуйста, введите действительную ссылку и проверьте, разрешен ли кросс-доменный доступ", + "urlError": "Ссылка не возвращает данные в формате JSON. Пожалуйста, введите действительную ссылку", + "label": "URL файла манифеста плагина" }, "title": { - "desc": "Название плагина", - "label": "Название", - "placeholder": "Поиск поисковой системы" + "desc": "Заголовок плагина", + "label": "Заголовок", + "placeholder": "Поиск в поисковой системе" } }, "metaConfig": "Настройка метаданных плагина", - "modalDesc": "После добавления пользовательского плагина его можно использовать для проверки разработки плагина или непосредственно в сеансе. См. <1>документацию по разработке↗ для разработки плагинов.", "preview": { - "card": "Предпросмотр отображения плагина", - "desc": "Предпросмотр описания плагина", - "title": "Предпросмотр названия плагина" + "card": "Предварительный просмотр плагина", + "desc": "Предварительный просмотр описания плагина", + "title": "Предварительный просмотр имени плагина" }, - "save": "Установить плагин", "saveSuccess": "Настройки плагина успешно сохранены", "tabs": { - "manifest": "Описание функций (манифест)", + "manifest": "Описание функций (Манифест)", "meta": "Метаданные плагина" }, - "title": "Добавить пользовательский плагин", "update": "Обновить", - "updateSuccess": "Настройки плагина успешно обновлены" + "updateSuccess": "Настройки плагина успешно обновлены", + "modalDesc": "После добавления пользовательского плагина его можно использовать для проверки разработки плагина или непосредственно в сеансе. См. документацию по разработке <1>здесь↗", + "openai": { + "importUrl": "Импорт из URL-ссылки", + "schema": "Схема" + }, + "save": "Установить плагин", + "type": { + "lobe": "Плагин LobeChat", + "openai": "Плагин OpenAI" + }, + "title": { + "create": "Добавить пользовательский плагин", + "edit": "Редактировать пользовательский плагин" + } }, "error": { - "fetchError": "Не удалось получить доступ к этой ссылке на манифест. Убедитесь, что ссылка действительна, и проверьте, разрешен ли кросс-доменный доступ.", "installError": "Ошибка установки плагина {{name}}", - "manifestInvalid": "Манифест не соответствует стандартам. Результат проверки: \n\n {{error}}", - "noManifest": "Файл манифеста не существует", + "noManifest": "Отсутствует файл описания", + "fetchError": "Не удалось получить доступ к файлу манифеста по указанной ссылке. Убедитесь, что ссылка действительна, и проверьте, разрешен ли кросс-доменный доступ", + "manifestInvalid": "Манифест не соответствует стандартам, результат проверки: \n\n {{error}}", + "openAPIInvalid": "Сбой разбора OpenAPI, ошибка: \n\n {{error}}", "reinstallError": "Ошибка обновления плагина {{name}}", - "urlError": "Эта ссылка не возвращает содержимое в формате JSON. Убедитесь, что ссылка действительна." + "urlError": "Ссылка не возвращает содержимое в формате JSON. Убедитесь, что ссылка действительна" }, "list": { "item": { @@ -95,25 +122,25 @@ } }, "loading": { - "content": "Вызов плагина...", - "plugin": "Выполнение плагина..." + "plugin": "Выполнение плагина...", + "content": "Вызов плагина..." }, "pluginList": "Список плагинов", "plugins": { - "loading": "Проверка плагинов...", + "loading": "Плагины загружаются...", "unknown": "Неизвестный плагин" }, "setting": "Настройки плагина", "settings": { "indexUrl": { "title": "Индекс рынка", - "tooltip": "В настоящее время не поддерживается онлайн-редактирование. Установите через переменные среды при развертывании" + "tooltip": "Редактирование в настоящее время недоступно" }, - "modalDesc": "После настройки адреса рынка плагинов можно использовать настраиваемый рынок плагинов", + "modalDesc": "После настройки адреса рынка плагинов можно использовать пользовательский рынок плагинов", "title": "Настройки рынка плагинов" }, "store": { - "empty": "Пока нет установленных плагинов", + "empty": "Нет установленных плагинов", "install": "Установить", "installAllPlugins": "Установить все", "networkError": "Не удалось получить доступ к магазину плагинов. Пожалуйста, проверьте подключение к сети и повторите попытку", @@ -124,6 +151,15 @@ }, "title": "Магазин плагинов", "uninstall": "Удалить", - "placeholder": "Введите название или ключевое слово плагина..." + "placeholder": "Введите название плагина, описание или ключевое слово...", + "actions": { + "confirmUninstall": "Вы собираетесь удалить этот плагин. После удаления будут очищены его настройки. Подтвердить операцию?", + "detail": "Подробнее", + "install": "Установить", + "manifest": "Редактировать файл установки", + "settings": "Настройки", + "uninstall": "Удалить" + }, + "customPlugin": "Пользовательский плагин" } } diff --git a/locales/zh_CN/error.json b/locales/zh_CN/error.json index cf51ae8e960ec..0728a96307d36 100644 --- a/locales/zh_CN/error.json +++ b/locales/zh_CN/error.json @@ -24,6 +24,8 @@ "PluginApiParamsError": "很抱歉,该插件请求的入参校验未通过,请检查入参与 Api 描述信息是否匹配", "PluginSettingsInvalid": "该插件需要正确配置后才可以使用,请检查你的配置是否正确", "PluginServerError": "插件服务端请求返回出错,请检查根据下面的报错信息检查你的插件描述文件、插件配置或服务端实现", + "PluginGatewayError": "很抱歉,插件网关出现错误,请检查插件网关配置是否正确", + "PluginOpenApiInitError": "很抱歉,OpenAPI 客户端初始化失败,请检查 OpenAPI 的配置信息是否正确", "InvalidAccessCode": "密码不正确或为空,请输入正确的访问密码,或者添加自定义 OpenAI API Key", "OpenAIBizError": "请求 OpenAI 服务出错,请根据以下信息排查或重试", "NoAPIKey": "OpenAI API Key 为空,请添加自定义 OpenAI API Key" diff --git a/locales/zh_CN/plugin.json b/locales/zh_CN/plugin.json index 3243ebe4b7c78..c6d9dbc6fbbd1 100644 --- a/locales/zh_CN/plugin.json +++ b/locales/zh_CN/plugin.json @@ -6,6 +6,18 @@ "on": "查看插件调用信息", "response": "返回结果" }, + "detailModal": { + "info": { + "description": "API 描述", + "name": "API 名称" + }, + "tabs": { + "info": "插件能力", + "manifest": "安装文件", + "settings": "设置" + }, + "title": "插件详情" + }, "dev": { "confirmDeleteDevPlugin": "即将删除该本地插件,删除后将无法找回,是否删除该插件?", "deleteSuccess": "插件删除成功", @@ -64,6 +76,10 @@ }, "metaConfig": "插件元信息配置", "modalDesc": "添加自定义插件后,可用于插件开发验证,也可直接在会话中使用。插件开发请参考<1>开发文档↗", + "openai": { + "importUrl": "从 URL 链接导入", + "schema": "Schema" + }, "preview": { "card": "预览插件展示效果", "desc": "预览插件描述", @@ -75,15 +91,23 @@ "manifest": "功能描述清单 (Manifest)", "meta": "插件元信息" }, - "title": "添加自定义插件", + "title": { + "create": "添加自定义插件", + "edit": "编辑自定义插件" + }, + "type": { + "lobe": "LobeChat 插件", + "openai": "OpenAI 插件" + }, "update": "更新", "updateSuccess": "插件设置更新成功" }, "error": { "fetchError": "请求该 manifest 链接失败,请确保链接的有效性,并检查链接是否允许跨域访问", "installError": "插件 {{name}} 安装失败", - "manifestInvalid": " manifest 不符合规范,校验结果: \n\n {{error}}", + "manifestInvalid": "manifest 不符合规范,校验结果: \n\n {{error}}", "noManifest": "描述文件不存在", + "openAPIInvalid": "OpenAPI 解析失败,错误: \n\n {{error}}", "reinstallError": "插件 {{name}} 刷新失败", "urlError": "该链接没有返回 JSON 格式的内容, 请确保是有效的链接" }, @@ -113,8 +137,16 @@ "title": "设置插件市场" }, "store": { + "actions": { + "confirmUninstall": "即将卸载该插件,卸载后将清除该插件配置,请确认你的操作", + "detail": "详情", + "install": "安装", + "manifest": "编辑安装文件", + "settings": "设置", + "uninstall": "卸载" + }, + "customPlugin": "自定义", "empty": "暂无已安装插件", - "install": "安装", "installAllPlugins": "安装全部", "networkError": "获取插件商店失败,请检测网络连接后重试", "placeholder": "搜索插件名称介绍或关键词...", @@ -123,7 +155,6 @@ "all": "全部", "installed": "已安装" }, - "title": "插件商店", - "uninstall": "卸载" + "title": "插件商店" } } diff --git a/locales/zh_TW/error.json b/locales/zh_TW/error.json index 88ea5d8ef0626..4fcf1201050db 100644 --- a/locales/zh_TW/error.json +++ b/locales/zh_TW/error.json @@ -26,7 +26,9 @@ "PluginApiParamsError": "抱歉,該外掛請求的輸入參數驗證失敗。請檢查輸入參數與 API 描述資訊是否相符", "PluginSettingsInvalid": "該外掛需要正確設定後才可以使用。請檢查您的設定是否正確", "PluginServerError": "外掛伺服器請求回傳錯誤。請根據下面的錯誤資訊檢查您的外掛描述檔案、外掛設定或伺服器實作", - "NoAPIKey": "OpenAI API 金鑰為空,請添加自訂 OpenAI API 金鑰" + "NoAPIKey": "OpenAI API 金鑰為空,請添加自訂 OpenAI API 金鑰", + "PluginGatewayError": "很抱歉,插件網關出現錯誤,請檢查插件網關配置是否正確", + "PluginOpenApiInitError": "很抱歉,OpenAPI 客戶端初始化失敗,請檢查 OpenAPI 的配置信息是否正確" }, "stt": { "responseError": "服務請求失敗,請檢查配置或重試" diff --git a/locales/zh_TW/plugin.json b/locales/zh_TW/plugin.json index 210cff067a7ad..ea7a088417715 100644 --- a/locales/zh_TW/plugin.json +++ b/locales/zh_TW/plugin.json @@ -1,129 +1,185 @@ { "debug": { - "arguments": "調用參數", - "function_call": "函數調用", - "off": "關閉調試", - "on": "查看插件調用信息", - "response": "返回結果" + "arguments": "參數", + "function_call": "函式呼叫", + "response": "回應", + "off": "關閉偵錯", + "on": "查看插件呼叫資訊" + }, + "detailModal": { + "info": { + "description": "API 描述", + "name": "API 名稱" + }, + "tabs": { + "info": "插件能力", + "manifest": "安裝檔案", + "settings": "設定" + }, + "title": "插件詳情" }, "dev": { - "confirmDeleteDevPlugin": "即將刪除該本地插件,刪除後將無法找回,是否刪除該插件?", - "deleteSuccess": "插件刪除成功", + "confirmDeleteDevPlugin": "您確定要刪除此本機外掛嗎?一旦刪除,將無法復原。", + "deleteSuccess": "外掛已成功刪除", "manifest": { "identifier": { - "desc": "插件的唯一標識", - "label": "標識符" + "desc": "外掛的唯一識別碼", + "label": "識別碼" }, "mode": { - "local": "可視化配置", - "local-tooltip": "暫時不支持可視化配置", - "url": "在線鏈接" + "local": "視覺設定", + "local-tooltip": "目前不支援視覺設定", + "url": "線上連結" }, "name": { - "desc": "插件標題", - "label": "標題", - "placeholder": "搜索引擎" + "desc": "外掛的名稱", + "label": "名稱", + "placeholder": "搜尋引擎" } }, "meta": { "author": { - "desc": "插件的作者", + "desc": "外掛的作者", "label": "作者" }, "avatar": { - "desc": "插件的圖標,可以使用 Emoji,也可以使用 URL", - "label": "圖標" + "desc": "外掛的圖示,可以是 Emoji 或網址", + "label": "圖示" }, "description": { - "desc": "插件描述", + "desc": "外掛的描述", "label": "描述", - "placeholder": "查詢搜索引擎獲取信息" + "placeholder": "從搜尋引擎取得資訊" }, - "formFieldRequired": "該字段為必填項", + "formFieldRequired": "此欄位必填", "homepage": { - "desc": "插件的首頁", + "desc": "外掛的官方首頁", "label": "首頁" }, "identifier": { - "desc": "插件的唯一標識,將從 manifest 中自動識別", - "errorDuplicate": "標識符和已有插件重複,請修改標識符", - "label": "標識符", - "pattenErrorMessage": "只能輸入英文字符、數字 、- 和_ 這兩個符號" + "desc": "外掛的唯一識別碼,只支援英數字元、連字號 -,以及底線 _", + "errorDuplicate": "此識別碼已被其他外掛使用,請更改識別碼", + "label": "識別碼", + "pattenErrorMessage": "只允許英數字元、連字號 -,以及底線 _" }, "manifest": { - "desc": "LobeChat 將會通過該鏈接安裝插件", - "label": "插件描述文件 (Manifest) URL", + "desc": "LobeChat 會使用此連結安裝外掛", + "invalid": "輸入的 manifest 連結不正確或不符合標準", + "urlError": "請輸入有效的網址", + "jsonInvalid": "manifest 不正確,驗證結果:\n\n {{error}}", "preview": "預覽 Manifest", - "refresh": "刷新" + "refresh": "重新載入", + "requestError": "連結請求失敗,請確認網址是否正確且允許跨域存取", + "label": "插件描述檔 (Manifest) URL" }, "title": { - "desc": "插件標題", - "label": "標題", - "placeholder": "搜索引擎" + "desc": "外掛的名稱", + "label": "名稱", + "placeholder": "搜尋引擎" } }, - "metaConfig": "插件元信息配置", - "modalDesc": "添加自定義插件後,可用於插件開發驗證,也可直接在會話中使用。插件開發請參考<1>開發文檔↗", + "metaConfig": "外掛後設資料設定", "preview": { - "card": "預覽插件展示效果", - "desc": "預覽插件描述", - "title": "插件名稱預覽" + "card": "外掛顯示預覽", + "desc": "外掛描述預覽", + "title": "外掛名稱預覽" }, - "save": "安裝插件", - "saveSuccess": "插件設置保存成功", + "saveSuccess": "外掛設定已成功儲存", "tabs": { "manifest": "功能描述清單 (Manifest)", - "meta": "插件元信息" + "meta": "外掛後設資料" }, - "title": "添加自定義插件", "update": "更新", - "updateSuccess": "插件設置更新成功" + "updateSuccess": "外掛設定已成功更新", + "modalDesc": "添加自訂插件後,可用於插件開發驗證,也可直接在會話中使用。插件開發請參考<1>開發文件↗", + "openai": { + "importUrl": "從 URL 連結匯入", + "schema": "Schema" + }, + "save": "安裝插件", + "type": { + "lobe": "LobeChat 插件", + "openai": "OpenAI 插件" + }, + "title": { + "create": "新增自訂插件", + "edit": "編輯自訂插件" + } }, "error": { - "fetchError": "請求該 manifest 鏈接失敗,請確保鏈接的有效性,並檢查鏈接是否允許跨域訪問", "installError": "插件 {{name}} 安裝失敗", - "manifestInvalid": " manifest 不符合規範,校驗結果: \n\n {{error}}", - "noManifest": "描述文件不存在", + "noManifest": "描述檔不存在", + "fetchError": "請求該 manifest 連結失敗,請確保連結的有效性,並檢查連結是否允許跨域訪問", + "manifestInvalid": "manifest 不符合規範,校驗結果: \n\n {{error}}", + "openAPIInvalid": "OpenAPI 解析失敗,錯誤: \n\n {{error}}", "reinstallError": "插件 {{name}} 刷新失敗", - "urlError": "該鏈接沒有返回 JSON 格式的內容, 請確保是有效的鏈接" + "urlError": "該連結沒有返回 JSON 格式的內容, 請確保是有效的連結" }, "list": { "item": { - "deprecated.title": "已刪除", - "local.config": "配置", - "local.title": "自定義" + "local.config": "設定", + "local.title": "本機", + "deprecated.title": "已刪除" } }, "loading": { - "content": "調用插件中...", - "plugin": "插件運行中..." + "plugin": "外掛執行中...", + "content": "呼叫插件中..." }, - "pluginList": "插件列表", + "pluginList": "外掛清單", "plugins": { + "realtimeWeather": "即時天氣", + "searchEngine": "搜尋引擎", + "undefined": "正在偵測外掛...", + "websiteCrawler": "網站爬蟲", "loading": "插件檢測中...", "unknown": "未知插件" }, - "setting": "插件設定", + "realtimeWeather": { + "data": { + "date": "日期", + "daytemp_float": "日間溫度", + "dayweather": "日間天氣", + "daywind": "日間風向", + "nighttemp_float": "夜間溫度", + "nightweather": "夜間天氣", + "nightwind": "夜間風向", + "week": "週" + }, + "title": "未來 7 天的天氣資料 ({{city}})", + "updateAt": "最後更新時間" + }, + "responseData": "回應資料", + "setting": "插件設置", "settings": { "indexUrl": { - "title": "市場索引", - "tooltip": "暫不支持線上編輯,請透過部署時環境變數進行設定" + "title": "外掛市集索引", + "tooltip": "目前不支援編輯" }, - "modalDesc": "配置插件市場的地址後,可以使用自定義的插件市場", - "title": "設定插件市場" + "modalDesc": "設定外掛市集的網址後,您可以使用自訂的外掛市集", + "title": "設定外掛市集" }, "store": { "empty": "暫無已安裝插件", "install": "安裝", "installAllPlugins": "安裝全部", - "networkError": "獲取插件商店失敗,請檢測網路連接後重試", - "releasedAt": "發布於 {{createdAt}}", + "networkError": "獲取插件商店失敗,請檢查網路連線後重試", + "releasedAt": "發佈於 {{createdAt}}", "tabs": { "all": "全部", "installed": "已安裝" }, "title": "插件商店", - "uninstall": "卸載", - "placeholder": "搜尋插件名稱介紹或關鍵字..." + "uninstall": "解除安裝", + "placeholder": "搜尋插件名稱介紹或關鍵字...", + "actions": { + "confirmUninstall": "即將卸載該插件,卸載後將清除該插件配置,請確認你的操作", + "detail": "詳情", + "install": "安裝", + "manifest": "編輯安裝檔案", + "settings": "設定", + "uninstall": "卸載" + }, + "customPlugin": "自訂插件" } } diff --git a/src/app/api/plugins/route.ts b/src/app/api/plugins/route.ts deleted file mode 100644 index 936d627251e8d..0000000000000 --- a/src/app/api/plugins/route.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createLobeChatPluginGateway } from '@lobehub/chat-plugins-gateway'; - -import { PLUGINS_INDEX_URL } from '@/const/url'; - -export const runtime = 'edge'; - -export const POST = createLobeChatPluginGateway({ pluginsIndexUrl: PLUGINS_INDEX_URL }); diff --git a/src/app/chat/features/Conversation/ChatList/Error/index.tsx b/src/app/chat/features/Conversation/ChatList/Error/index.tsx index 98799fa010711..c4737413c6718 100644 --- a/src/app/chat/features/Conversation/ChatList/Error/index.tsx +++ b/src/app/chat/features/Conversation/ChatList/Error/index.tsx @@ -37,6 +37,12 @@ export const renderErrorMessages: ChatListProps['renderErrorMessages'] = { [PluginErrorType.PluginServerError]: { Render: PluginError, }, + [PluginErrorType.PluginGatewayError]: { + Render: PluginError, + }, + [PluginErrorType.PluginOpenApiInitError]: { + Render: PluginError, + }, [PluginErrorType.PluginSettingsInvalid]: { Render: PluginSettings, config: { diff --git a/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx b/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx index 407fda79d6d7b..cd9b56ef2ef05 100644 --- a/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +++ b/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx @@ -3,7 +3,7 @@ import { LucideSettings } from 'lucide-react'; import { memo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import PluginSettingsModal from '@/features/PluginSettingsModal'; +import PluginDetailModal from 'src/features/PluginDetailModal'; import { useToolStore } from '@/store/tool'; import { pluginSelectors } from '@/store/tool/selectors'; @@ -21,7 +21,7 @@ const Settings = memo<{ id: string }>(({ id }) => { }} title={t('setting')} /> - { setOpen(false); diff --git a/src/app/settings/common/Common.tsx b/src/app/settings/common/Common.tsx index fa2d719300566..ad276935164d0 100644 --- a/src/app/settings/common/Common.tsx +++ b/src/app/settings/common/Common.tsx @@ -69,13 +69,11 @@ const Common = memo(({ showAccessCodeConfig }) => { }, okText: t('ok', { ns: 'common' }), onOk: async () => { - await Promise.all([ - clearSessions, - removeAllPlugins, - clearTopics, - removeAllFiles, - clearAllMessages, - ]); + await clearSessions(); + await removeAllPlugins(); + await clearTopics(); + await removeAllFiles(); + await clearAllMessages(); message.success(t('danger.clear.success')); }, diff --git a/src/const/plugin.ts b/src/const/plugin.ts index 5194a6f12873a..94d5ce8ee4f13 100644 --- a/src/const/plugin.ts +++ b/src/const/plugin.ts @@ -1 +1,2 @@ export const PLUGIN_SCHEMA_SEPARATOR = '____'; +export const PLUGIN_SCHEMA_API_MD5_PREFIX = 'MD5HASH_'; diff --git a/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx b/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx index cede3df15a8fa..370a10840aa95 100644 --- a/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +++ b/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx @@ -1,75 +1,41 @@ -import { Button, Switch } from 'antd'; +import { Switch } from 'antd'; import isEqual from 'fast-deep-equal'; -import { memo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import { memo } from 'react'; import { Flexbox } from 'react-layout-kit'; -import DevModal from '@/features/PluginDevModal'; import { useToolStore } from '@/store/tool'; -import { pluginSelectors } from '@/store/tool/selectors'; import { useStore } from '../store'; const MarketList = memo<{ id: string }>(({ id }) => { - const { t } = useTranslation('plugin'); - - const [showModal, setModal] = useState(false); - const [toggleAgentPlugin, hasPlugin] = useStore((s) => [s.toggleAgentPlugin, !!s.config.plugins]); const plugins = useStore((s) => s.config.plugins || []); - const [useFetchPluginList, fetchPluginManifest, deleteCustomPlugin, updateCustomPlugin] = - useToolStore((s) => [ - s.useFetchPluginStore, - s.installPlugin, - s.uninstallCustomPlugin, - s.updateCustomPlugin, - ]); + const [useFetchPluginList, fetchPluginManifest] = useToolStore((s) => [ + s.useFetchPluginStore, + s.installPlugin, + ]); const pluginManifestLoading = useToolStore((s) => s.pluginInstallLoading, isEqual); - const devPlugin = useToolStore(pluginSelectors.getCustomPluginById(id), isEqual); useFetchPluginList(); return ( - <> - { - deleteCustomPlugin(id); - toggleAgentPlugin(id, false); - }} - onOpenChange={setModal} - onSave={(value) => { - updateCustomPlugin(id, value); + + { + toggleAgentPlugin(id); + if (checked) { + fetchPluginManifest(id); + } }} - open={showModal} - value={devPlugin} /> - - - { - toggleAgentPlugin(id); - if (checked) { - fetchPluginManifest(id); - } - }} - /> - - - + ); }); diff --git a/src/features/PluginDetailModal/APIs.tsx b/src/features/PluginDetailModal/APIs.tsx new file mode 100644 index 0000000000000..76425415585c6 --- /dev/null +++ b/src/features/PluginDetailModal/APIs.tsx @@ -0,0 +1,43 @@ +import { Empty, Table } from 'antd'; +import isEqual from 'fast-deep-equal'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; + +import { useToolStore } from '@/store/tool'; +import { pluginSelectors } from '@/store/tool/selectors'; + +const APIs = memo<{ + id: string; +}>(({ id }) => { + const { t } = useTranslation('plugin'); + const pluginManifest = useToolStore(pluginSelectors.getPluginManifestById(id), isEqual); + + if (!pluginManifest?.api) return ; + + return ( + + + + ); +}); + +export default APIs; diff --git a/src/features/PluginDetailModal/Meta.tsx b/src/features/PluginDetailModal/Meta.tsx new file mode 100644 index 0000000000000..75f874b2ec556 --- /dev/null +++ b/src/features/PluginDetailModal/Meta.tsx @@ -0,0 +1,35 @@ +import { Avatar } from '@lobehub/ui'; +import { Typography } from 'antd'; +import { useTheme } from 'antd-style'; +import isEqual from 'fast-deep-equal'; +import { memo } from 'react'; +import { Flexbox } from 'react-layout-kit'; + +import { pluginHelpers, useToolStore } from '@/store/tool'; +import { pluginSelectors } from '@/store/tool/selectors'; + +const Meta = memo<{ + id: string; +}>(({ id }) => { + const pluginMeta = useToolStore(pluginSelectors.getPluginMetaById(id), isEqual); + + const theme = useTheme(); + + return ( + <> + + + {pluginHelpers.getPluginTitle(pluginMeta)} + + {pluginHelpers.getPluginDesc(pluginMeta)} + + + ); +}); + +export default Meta; diff --git a/src/features/PluginDetailModal/index.tsx b/src/features/PluginDetailModal/index.tsx new file mode 100644 index 0000000000000..c98b283ca2762 --- /dev/null +++ b/src/features/PluginDetailModal/index.tsx @@ -0,0 +1,73 @@ +import { Modal, TabsNav } from '@lobehub/ui'; +import { Divider } from 'antd'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Center } from 'react-layout-kit'; +import useMergeState from 'use-merge-value'; + +import MobilePadding from '@/components/MobilePadding'; +import PluginSettingsConfig from '@/features/PluginSettings'; + +import APIs from './APIs'; +import Meta from './Meta'; + +interface PluginDetailModalProps { + id: string; + onClose: () => void; + onTabChange?: (key: string) => void; + open?: boolean; + schema: any; + tab?: string; +} + +const PluginDetailModal = memo( + ({ schema, onClose, id, onTabChange, open, tab }) => { + const [tabKey, setTabKey] = useMergeState('info', { + onChange: onTabChange, + value: tab, + }); + const { t } = useTranslation('plugin'); + + return ( + { + onClose(); + }} + open={open} + title={t('detailModal.title')} + width={650} + > + +
+ + + + {tabKey === 'settings' ? ( + schema && + ) : ( + + )} +
+
+
+ ); + }, +); + +export default PluginDetailModal; diff --git a/src/features/PluginDevModal/index.tsx b/src/features/PluginDevModal/index.tsx index 7241f3085db39..70e5e1d246641 100644 --- a/src/features/PluginDevModal/index.tsx +++ b/src/features/PluginDevModal/index.tsx @@ -107,7 +107,7 @@ const DevModal = memo( form.submit(); }} open={open} - title={t('dev.title')} + title={t(isEditMode ? 'dev.title.edit' : 'dev.title.create')} > void; - open?: boolean; - schema: any; -} - -const PluginSettingsModal = memo(({ schema, onClose, id, open }) => { - const pluginMeta = useToolStore(pluginSelectors.getPluginMetaById(id), isEqual); - - const { t } = useTranslation('plugin'); - const theme = useTheme(); - return ( - { - onClose(); - }} - open={open} - title={t('setting')} - width={600} - > - -
- - - {pluginHelpers.getPluginTitle(pluginMeta)} - - {pluginHelpers.getPluginDesc(pluginMeta)} - - - {schema && } -
-
-
- ); -}); - -export default PluginSettingsModal; diff --git a/src/features/PluginStore/InstalledPluginList.tsx b/src/features/PluginStore/InstalledPluginList.tsx index 549ac0c7bfbf8..5768c236fe2c5 100644 --- a/src/features/PluginStore/InstalledPluginList.tsx +++ b/src/features/PluginStore/InstalledPluginList.tsx @@ -1,16 +1,32 @@ -import { SpotlightCard } from '@lobehub/ui'; import isEqual from 'fast-deep-equal'; import { memo } from 'react'; +import { Flexbox } from 'react-layout-kit'; import { useToolStore } from '@/store/tool'; import { pluginSelectors } from '@/store/tool/selectors'; +import AddPluginButton from './AddPluginButton'; import PluginItem from './PluginItem'; -export const OnlineList = memo(() => { +export const InstalledPluginList = memo(() => { const installedPlugins = useToolStore(pluginSelectors.installedPluginMetaList, isEqual); - return ; + return ( + + + + + + + + + + {installedPlugins.map((i) => ( + + ))} + + + ); }); -export default OnlineList; +export default InstalledPluginList; diff --git a/src/features/PluginStore/OnlineList.tsx b/src/features/PluginStore/OnlineList.tsx index c5545ee33ee24..6de96ccb86c3b 100644 --- a/src/features/PluginStore/OnlineList.tsx +++ b/src/features/PluginStore/OnlineList.tsx @@ -1,42 +1,95 @@ -import { Icon, SpotlightCard } from '@lobehub/ui'; -import { Empty } from 'antd'; +import { Icon, SearchBar } from '@lobehub/ui'; +import { Button, Empty } from 'antd'; import isEqual from 'fast-deep-equal'; import { ServerCrash } from 'lucide-react'; -import { memo } from 'react'; +import { memo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Center } from 'react-layout-kit'; +import { Center, Flexbox } from 'react-layout-kit'; +import AddPluginButton from '@/features/PluginStore/AddPluginButton'; import { useToolStore } from '@/store/tool'; -import { pluginStoreSelectors } from '@/store/tool/selectors'; +import { pluginSelectors, pluginStoreSelectors } from '@/store/tool/selectors'; import Loading from './Loading'; import PluginItem from './PluginItem'; export const OnlineList = memo(() => { const { t } = useTranslation('plugin'); + const [keywords, setKeywords] = useState(); - const [useFetchPluginList] = useToolStore((s) => [s.useFetchPluginStore]); + const pluginStoreList = useToolStore((s) => { + const custom = pluginSelectors.installedCustomPluginMetaList(s); + const store = pluginStoreSelectors.onlinePluginStore(s); - const pluginStoreList = useToolStore(pluginStoreSelectors.onlinePluginStore, isEqual); + return [...custom, ...store]; + }, isEqual); + const storePluginIds = useToolStore( + (s) => pluginStoreSelectors.onlinePluginStore(s).map((s) => s.identifier), + isEqual, + ); + + const [useFetchPluginList, installPlugins] = useToolStore((s) => [ + s.useFetchPluginStore, + s.installPlugins, + ]); const { isLoading, error } = useFetchPluginList(); + const isEmpty = pluginStoreList.length === 0; - return isLoading ? ( - - ) : isEmpty ? ( -
- {error ? ( - <> - - {t('store.networkError')} - + return ( + + + + setKeywords(e.target.value)} + placeholder={t('store.placeholder')} + type={'block'} + value={keywords} + /> + + + + + + + + {isLoading ? ( + + ) : isEmpty ? ( +
+ {error ? ( + <> + + {t('store.networkError')} + + ) : ( + + )} +
) : ( - + + {pluginStoreList + .filter((item) => + [item.meta?.title, item.meta?.description, item.author, ...(item.meta?.tags || [])] + .filter(Boolean) + .join('') + .toLowerCase() + .includes((keywords || '')?.toLowerCase()), + ) + .map((item) => ( + + ))} + )} -
- ) : ( - +
); }); diff --git a/src/features/PluginStore/PluginItem.tsx b/src/features/PluginStore/PluginItem.tsx deleted file mode 100644 index 1ef9b8e47d45e..0000000000000 --- a/src/features/PluginStore/PluginItem.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Avatar, Tag } from '@lobehub/ui'; -import { Button } from 'antd'; -import { createStyles } from 'antd-style'; -import Link from 'next/link'; -import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Flexbox } from 'react-layout-kit'; - -import { useToolStore } from '@/store/tool'; -import { pluginSelectors, pluginStoreSelectors } from '@/store/tool/selectors'; -import { InstallPluginMeta } from '@/types/tool/plugin'; - -import PluginSettings from './PluginSettings'; - -const useStyles = createStyles(({ css, token }) => ({ - desc: css` - color: ${token.colorTextSecondary}; - `, - link: css` - color: ${token.colorTextDescription}; - `, - tag: css` - font-weight: normal; - `, - title: css` - font-size: ${token.fontSizeLG}px; - font-weight: bold; - line-height: 2; - `, -})); - -const PluginItem = memo( - ({ identifier, createdAt, homepage, author, type, meta = {} }) => { - console.log(type); - const [installed, installing, installPlugin, unInstallPlugin] = useToolStore((s) => [ - pluginSelectors.isPluginInstalled(identifier)(s), - pluginStoreSelectors.isPluginInstallLoading(identifier)(s), - s.installPlugin, - s.uninstallPlugin, - ]); - const { styles } = useStyles(); - - const { t } = useTranslation('plugin'); - return ( - - - - - - - - - - - - -
{meta.title}
- {identifier} -
-
{meta.description}
- - {author && ( - - @{author} - - )} - {createdAt && t('store.releasedAt', { createdAt })} - -
-
- ); - }, -); - -export default PluginItem; diff --git a/src/features/PluginStore/PluginItem/Action.tsx b/src/features/PluginStore/PluginItem/Action.tsx new file mode 100644 index 0000000000000..95941d097ead2 --- /dev/null +++ b/src/features/PluginStore/PluginItem/Action.tsx @@ -0,0 +1,116 @@ +import { ActionIcon, Icon } from '@lobehub/ui'; +import { Button, Dropdown, Popconfirm } from 'antd'; +import { useResponsive } from 'antd-style'; +import { InfoIcon, MoreVerticalIcon, Settings2, Trash2 } from 'lucide-react'; +import { memo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; + +import PluginDetailModal from '@/features/PluginDetailModal'; +import { useToolStore } from '@/store/tool'; +import { pluginSelectors, pluginStoreSelectors } from '@/store/tool/selectors'; +import { LobeToolType } from '@/types/tool/tool'; + +import EditCustomPlugin from './EditCustomPlugin'; + +interface ActionsProps { + identifier: string; + type: LobeToolType; +} + +const Actions = memo(({ identifier, type }) => { + const [installed, installing, installPlugin, unInstallPlugin] = useToolStore((s) => [ + pluginSelectors.isPluginInstalled(identifier)(s), + pluginStoreSelectors.isPluginInstallLoading(identifier)(s), + s.installPlugin, + s.uninstallPlugin, + ]); + const { mobile } = useResponsive(); + const isCustomPlugin = type === 'customPlugin'; + const { t } = useTranslation('plugin'); + const [open, setOpen] = useState(false); + const plugin = useToolStore(pluginSelectors.getPluginManifestById(identifier)); + + const [tab, setTab] = useState('info'); + return ( + <> + + {installed ? ( + <> + {isCustomPlugin && } + {plugin?.settings && ( + { + setOpen(true); + setTab('settings'); + }} + title={t('store.actions.settings')} + /> + )} + , + key: 'detail', + label: t('store.actions.detail'), + onClick: () => { + setOpen(true); + setTab('info'); + }, + }, + { + danger: true, + icon: , + key: 'uninstall', + label: ( + { + unInstallPlugin(identifier); + }} + placement={'topRight'} + title={t('store.actions.confirmUninstall')} + > + {t('store.actions.uninstall')} + + ), + }, + ], + }} + placement="bottomRight" + trigger={['click']} + > + + + + ) : ( + + )} + + { + setOpen(false); + }} + onTabChange={setTab} + open={open} + schema={plugin?.settings} + tab={tab} + /> + + ); +}); + +export default Actions; diff --git a/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx b/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx new file mode 100644 index 0000000000000..cfd2a3363277e --- /dev/null +++ b/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx @@ -0,0 +1,50 @@ +import { ActionIcon } from '@lobehub/ui'; +import isEqual from 'fast-deep-equal'; +import { Package2 } from 'lucide-react'; +import { memo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import DevModal from '@/features/PluginDevModal'; +import { useToolStore } from '@/store/tool'; +import { pluginSelectors } from '@/store/tool/slices/plugin/selectors'; + +const EditCustomPlugin = memo<{ identifier: string }>(({ identifier }) => { + const { t } = useTranslation('plugin'); + const [showModal, setModal] = useState(false); + + const [installCustomPlugin, updateNewDevPlugin] = useToolStore((s) => [ + s.installCustomPlugin, + s.updateNewCustomPlugin, + ]); + + const customPlugin = useToolStore(pluginSelectors.getCustomPluginById(identifier), isEqual); + + return ( +
{ + e.stopPropagation(); + }} + > + { + await installCustomPlugin(devPlugin); + // toggleAgentPlugin(devPlugin.identifier); + }} + onValueChange={updateNewDevPlugin} + open={showModal} + value={customPlugin} + /> + { + setModal(true); + }} + title={t('store.actions.manifest')} + /> +
+ ); +}); + +export default EditCustomPlugin; diff --git a/src/features/PluginStore/PluginItem/index.tsx b/src/features/PluginStore/PluginItem/index.tsx new file mode 100644 index 0000000000000..0c5b4efa65fc3 --- /dev/null +++ b/src/features/PluginStore/PluginItem/index.tsx @@ -0,0 +1,72 @@ +import { Avatar, Icon, Tag } from '@lobehub/ui'; +import { createStyles, useResponsive } from 'antd-style'; +import { BadgeCheck, CircleUser } from 'lucide-react'; +import Link from 'next/link'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; + +import { InstallPluginMeta } from '@/types/tool/plugin'; + +import Actions from './Action'; + +const useStyles = createStyles(({ css, token }) => ({ + desc: css` + color: ${token.colorTextDescription}; + `, + link: css` + color: ${token.colorText}; + `, + tag: css` + color: ${token.colorTextSecondary}; + `, + title: css` + font-size: ${token.fontSizeLG}px; + font-weight: bold; + line-height: 2; + `, +})); + +const PluginItem = memo(({ identifier, homepage, author, type, meta = {} }) => { + const { styles } = useStyles(); + const { mobile } = useResponsive(); + const isCustomPlugin = type === 'customPlugin'; + const { t } = useTranslation('plugin'); + + return ( + + + + + + {homepage ? ( + +
{meta.title}
+ + ) : ( +
{meta.title}
+ )} + + {author && ( + } + > + {author} + + )} + {isCustomPlugin && ( + + {t('store.customPlugin')} + + )} +
+
{meta.description}
+
+
+ +
+ ); +}); + +export default PluginItem; diff --git a/src/features/PluginStore/PluginSettings.tsx b/src/features/PluginStore/PluginSettings.tsx deleted file mode 100644 index f79d7831842ff..0000000000000 --- a/src/features/PluginStore/PluginSettings.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { ActionIcon } from '@lobehub/ui'; -import { Settings2 } from 'lucide-react'; -import { memo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -import PluginSettingsModal from '@/features/PluginSettingsModal'; -import { useToolStore } from '@/store/tool'; -import { pluginSelectors } from '@/store/tool/selectors'; - -const PluginSettings = memo<{ identifier: string }>(({ identifier }) => { - const { t } = useTranslation('setting'); - - const [open, setOpen] = useState(false); - - const plugin = useToolStore(pluginSelectors.getPluginManifestById(identifier)); - - if (!plugin?.settings) return null; - - return ( - <> - { - setOpen(true); - }} - title={t('plugin.settings.tooltip')} - /> - { - setOpen(false); - }} - open={open} - schema={plugin.settings} - /> - - ); -}); - -export default PluginSettings; diff --git a/src/features/PluginStore/index.tsx b/src/features/PluginStore/index.tsx index a49034a7e33e6..a40755b03c6cf 100644 --- a/src/features/PluginStore/index.tsx +++ b/src/features/PluginStore/index.tsx @@ -1,15 +1,12 @@ -import { Modal, TabsNav } from '@lobehub/ui'; -import { Button } from 'antd'; -import isEqual from 'fast-deep-equal'; +import { Modal } from '@lobehub/ui'; +import { Segmented } from 'antd'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { Center, Flexbox } from 'react-layout-kit'; import MobilePadding from '@/components/MobilePadding'; import { useToolStore } from '@/store/tool'; -import { pluginStoreSelectors } from '@/store/tool/selectors'; -import AddPluginButton from './AddPluginButton'; import InstalledPluginList from './InstalledPluginList'; import OnlineList from './OnlineList'; @@ -20,9 +17,7 @@ interface PluginStoreProps { export const PluginStore = memo(({ setOpen, open }) => { const { t } = useTranslation('plugin'); - const [listType, installPlugins] = useToolStore((s) => [s.listType, s.installPlugins]); - - const pluginStoreList = useToolStore(pluginStoreSelectors.onlinePluginStore, isEqual); + const [listType] = useToolStore((s) => [s.listType]); return ( (({ setOpen, open }) => {
- - { - useToolStore.setState({ listType: v as any }); - }} - /> - - - {listType === 'all' && ( - - )} - - + { + useToolStore.setState({ listType: v as any }); + }} + options={[ + { label: t('store.tabs.all'), value: 'all' }, + { label: t('store.tabs.installed'), value: 'installed' }, + ]} + style={{ flex: 1 }} + value={listType} + /> + {listType === 'all' ? : }
diff --git a/src/locales/default/error.ts b/src/locales/default/error.ts index dbeda5bc9fe9d..e220146dce81e 100644 --- a/src/locales/default/error.ts +++ b/src/locales/default/error.ts @@ -29,6 +29,8 @@ export default { PluginSettingsInvalid: '该插件需要正确配置后才可以使用,请检查你的配置是否正确', PluginServerError: '插件服务端请求返回出错,请检查根据下面的报错信息检查你的插件描述文件、插件配置或服务端实现', + PluginGatewayError: '很抱歉,插件网关出现错误,请检查插件网关配置是否正确', + PluginOpenApiInitError: '很抱歉,OpenAPI 客户端初始化失败,请检查 OpenAPI 的配置信息是否正确', InvalidAccessCode: '密码不正确或为空,请输入正确的访问密码,或者添加自定义 OpenAI API Key', OpenAIBizError: '请求 OpenAI 服务出错,请根据以下信息排查或重试', diff --git a/src/locales/default/plugin.ts b/src/locales/default/plugin.ts index 3b9d5bc46612b..c35d35a75a1f9 100644 --- a/src/locales/default/plugin.ts +++ b/src/locales/default/plugin.ts @@ -6,6 +6,18 @@ export default { on: '查看插件调用信息', response: '返回结果', }, + detailModal: { + info: { + description: 'API 描述', + name: 'API 名称', + }, + tabs: { + info: '插件能力', + manifest: '安装文件', + settings: '设置', + }, + title: '插件详情', + }, dev: { confirmDeleteDevPlugin: '即将删除该本地插件,删除后将无法找回,是否删除该插件?', deleteSuccess: '插件删除成功', @@ -80,7 +92,10 @@ export default { manifest: '功能描述清单 (Manifest)', meta: '插件元信息', }, - title: '添加自定义插件', + title: { + create: '添加自定义插件', + edit: '编辑自定义插件', + }, type: { lobe: 'LobeChat 插件', openai: 'OpenAI 插件', @@ -123,8 +138,16 @@ export default { title: '设置插件市场', }, store: { + actions: { + confirmUninstall: '即将卸载该插件,卸载后将清除该插件配置,请确认你的操作', + detail: '详情', + install: '安装', + manifest: '编辑安装文件', + settings: '设置', + uninstall: '卸载', + }, + customPlugin: '自定义', empty: '暂无已安装插件', - install: '安装', installAllPlugins: '安装全部', networkError: '获取插件商店失败,请检测网络连接后重试', placeholder: '搜索插件名称介绍或关键词...', @@ -134,6 +157,5 @@ export default { installed: '已安装', }, title: '插件商店', - uninstall: '卸载', }, }; diff --git a/src/pages/api/plugins.ts b/src/pages/api/plugins.ts new file mode 100644 index 0000000000000..8b1ae5ddcd70d --- /dev/null +++ b/src/pages/api/plugins.ts @@ -0,0 +1,5 @@ +import { createGatewayOnNodeRuntime } from '@lobehub/chat-plugins-gateway'; + +import { PLUGINS_INDEX_URL } from '@/const/url'; + +export default createGatewayOnNodeRuntime({ pluginsIndexUrl: PLUGINS_INDEX_URL }); diff --git a/src/services/__tests__/plugin.test.ts b/src/services/__tests__/plugin.test.ts index 7eae9bb0fec0a..d2f456b18ee37 100644 --- a/src/services/__tests__/plugin.test.ts +++ b/src/services/__tests__/plugin.test.ts @@ -465,6 +465,7 @@ describe('PluginService', () => { apiKeyAuth: { title: 'X-OpenAPIHub-Key', description: 'apiKeyAuth API Key', + format: 'password', type: 'string', }, }, diff --git a/src/services/plugin.ts b/src/services/plugin.ts index 1ac3552d7e80f..1e70e53cab5af 100644 --- a/src/services/plugin.ts +++ b/src/services/plugin.ts @@ -188,6 +188,7 @@ class PluginService { properties: { [key]: { description: value.description || `${key} API Key`, + format: 'password', title: value.name, type: 'string', }, diff --git a/src/store/chat/actions/plugin.test.ts b/src/store/chat/actions/plugin.test.ts new file mode 100644 index 0000000000000..9b77aca0988f2 --- /dev/null +++ b/src/store/chat/actions/plugin.test.ts @@ -0,0 +1,281 @@ +import { act, renderHook } from '@testing-library/react'; +import { Md5 } from 'ts-md5'; +import { Mock, describe, expect, it, vi } from 'vitest'; + +import { PLUGIN_SCHEMA_API_MD5_PREFIX, PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin'; +import { chatService } from '@/services/chat'; +import { messageService } from '@/services/message'; +import { chatSelectors } from '@/store/chat/selectors'; +import { useChatStore } from '@/store/chat/store'; +import { useToolStore } from '@/store/tool'; +import { pluginSelectors } from '@/store/tool/selectors'; +import { LobeTool } from '@/types/tool'; + +// Mock messageService 和 chatSelectors +vi.mock('@/services/message', () => ({ + messageService: { + updateMessageContent: vi.fn(), + updateMessage: vi.fn(), + updateMessageError: vi.fn(), + updateMessagePluginState: vi.fn(), + }, +})); +vi.mock('@/services/chat', () => ({ + chatService: { + runPluginApi: vi.fn(), + }, +})); + +vi.mock('@/store/chat/selectors', () => ({ + chatSelectors: { + currentChats: vi.fn(), + getMessageById: vi.fn(), + }, +})); + +describe('ChatPluginAction', () => { + describe('fillPluginMessageContent', () => { + it('should update message content and process the message', async () => { + // 设置模拟函数的返回值 + const mockCurrentChats: any[] = []; + (chatSelectors.currentChats as Mock).mockReturnValue(mockCurrentChats); + + // 设置初始状态 + const initialState = { + messages: [], + coreProcessMessage: vi.fn(), + refreshMessages: vi.fn(), + }; + useChatStore.setState(initialState); + + const { result } = renderHook(() => useChatStore()); + + const messageId = 'message-id'; + const newContent = 'Updated content'; + + await act(async () => { + await result.current.fillPluginMessageContent(messageId, newContent); + }); + + // 验证 messageService.updateMessageContent 是否被正确调用 + expect(messageService.updateMessageContent).toHaveBeenCalledWith(messageId, newContent); + + // 验证 refreshMessages 是否被调用 + expect(result.current.refreshMessages).toHaveBeenCalled(); + + // 验证 coreProcessMessage 是否被正确调用 + expect(result.current.coreProcessMessage).toHaveBeenCalledWith(mockCurrentChats, messageId); + }); + }); + + describe('runPluginDefaultType', () => { + it('should run the default plugin type and update message content', async () => { + const pluginPayload = { apiName: 'testApi', arguments: { key: 'value' } }; + const messageId = 'message-id'; + const pluginApiResponse = 'Plugin API response'; + + const initialState = { + refreshMessages: vi.fn(), + coreProcessMessage: vi.fn(), + toggleChatLoading: vi.fn(), + }; + useChatStore.setState(initialState); + + (chatService.runPluginApi as Mock).mockResolvedValue(pluginApiResponse); + + const { result } = renderHook(() => useChatStore()); + + await act(async () => { + await result.current.runPluginDefaultType(messageId, pluginPayload); + }); + + expect(initialState.toggleChatLoading).toHaveBeenCalledWith( + true, + messageId, + expect.any(String), + ); + expect(chatService.runPluginApi).toHaveBeenCalledWith(pluginPayload, { signal: undefined }); + expect(messageService.updateMessageContent).toHaveBeenCalledWith( + messageId, + pluginApiResponse, + ); + expect(initialState.refreshMessages).toHaveBeenCalled(); + expect(initialState.coreProcessMessage).toHaveBeenCalled(); + expect(initialState.toggleChatLoading).toHaveBeenCalledWith(false); + }); + + it('should handle errors when the plugin API call fails', async () => { + const pluginPayload = { apiName: 'testApi', arguments: { key: 'value' } }; + const messageId = 'message-id'; + const error = new Error('API call failed'); + + const initialState = { + refreshMessages: vi.fn(), + coreProcessMessage: vi.fn(), + toggleChatLoading: vi.fn(), + }; + useChatStore.setState(initialState); + + (chatService.runPluginApi as Mock).mockRejectedValue(error); + + const { result } = renderHook(() => useChatStore()); + + await act(async () => { + await result.current.runPluginDefaultType(messageId, pluginPayload); + }); + + expect(initialState.toggleChatLoading).toHaveBeenCalledWith( + true, + messageId, + expect.any(String), + ); + expect(chatService.runPluginApi).toHaveBeenCalledWith(pluginPayload, { signal: undefined }); + expect(messageService.updateMessageError).toHaveBeenCalledWith(messageId, error); + expect(initialState.refreshMessages).toHaveBeenCalled(); + expect(initialState.toggleChatLoading).toHaveBeenCalledWith(false); + expect(initialState.coreProcessMessage).not.toHaveBeenCalled(); // 确保在错误情况下不调用此方法 + }); + }); + + describe('triggerFunctionCall', () => { + it('should trigger a function call and update the plugin message accordingly', async () => { + const messageId = 'message-id'; + const messageContent = `{"function_call": {"name": "pluginName${PLUGIN_SCHEMA_SEPARATOR}apiName", "arguments": {"key": "value"}}}`; + const messagePluginPayload = { + apiName: 'apiName', + identifier: 'pluginName', + type: 'default', + arguments: { key: 'value' }, + }; + + const initialState = { + refreshMessages: vi.fn(), + runPluginDefaultType: vi.fn(), + }; + useChatStore.setState(initialState); + + (chatSelectors.getMessageById as Mock).mockImplementation(() => () => ({ + id: messageId, + content: messageContent, + })); + + const { result } = renderHook(() => useChatStore()); + + await act(async () => { + await result.current.triggerFunctionCall(messageId); + }); + + expect(chatSelectors.getMessageById).toHaveBeenCalledWith(messageId); + expect(messageService.updateMessage).toHaveBeenCalledWith(messageId, { + content: '', + plugin: messagePluginPayload, + role: 'function', + }); + expect(initialState.refreshMessages).toHaveBeenCalled(); + expect(initialState.runPluginDefaultType).toHaveBeenCalledWith( + messageId, + messagePluginPayload, + ); + }); + + it('should handle function call with MD5 prefixed API name', async () => { + const messageId = 'message-id'; + const apiName = 'originalApiName'; + const id = 'pluginIdentifier'; + const md5ApiName = PLUGIN_SCHEMA_API_MD5_PREFIX + Md5.hashStr(apiName).toString(); + const messageContent = JSON.stringify({ + function_call: { name: id + PLUGIN_SCHEMA_SEPARATOR + md5ApiName, arguments: {} }, + }); + + const plugin = { identifier: id, manifest: { api: [{ name: apiName }] } } as LobeTool; + + useToolStore.setState({ installedPlugins: [plugin] }); + + useChatStore.setState({ refreshMessages: vi.fn(), runPluginDefaultType: vi.fn() }); + + (chatSelectors.getMessageById as Mock).mockImplementation(() => () => ({ + id: messageId, + content: messageContent, + })); + + const { result } = renderHook(() => useChatStore()); + + await act(async () => { + await result.current.triggerFunctionCall(messageId); + }); + expect(result.current.refreshMessages).toHaveBeenCalled(); + + expect(messageService.updateMessage).toHaveBeenCalledWith( + messageId, + expect.objectContaining({ + // 确保正确的 API 名称被设置 + plugin: expect.objectContaining({ apiName }), + }), + ); + expect(result.current.runPluginDefaultType).toHaveBeenCalledWith( + messageId, + expect.objectContaining({ + apiName: apiName, + }), + ); + }); + + it('should handle standalone plugin type', async () => { + const messageId = 'message-id'; + const messageContent = JSON.stringify({ + function_call: { + name: `pluginName${PLUGIN_SCHEMA_SEPARATOR}apiName${PLUGIN_SCHEMA_SEPARATOR}standalone`, + arguments: {}, + }, + }); + + useChatStore.setState({ + refreshMessages: vi.fn(), + runPluginDefaultType: vi.fn(), + }); + + (chatSelectors.getMessageById as Mock).mockImplementation(() => () => ({ + id: messageId, + content: messageContent, + })); + + const { result } = renderHook(() => useChatStore()); + + await act(async () => { + await result.current.triggerFunctionCall(messageId); + }); + + // 验证 refreshMessages 是否被调用 + expect(result.current.refreshMessages).toHaveBeenCalled(); + + // 验证 runPluginDefaultType 是否没有被调用,因为类型是 standalone + expect(result.current.runPluginDefaultType).not.toHaveBeenCalled(); + }); + }); + + describe('updatePluginState', () => { + it('should update the plugin state for a message', async () => { + const messageId = 'message-id'; + const pluginStateKey = 'key'; + const pluginStateValue = 'value'; + + const initialState = { + refreshMessages: vi.fn(), + }; + useChatStore.setState(initialState); + + const { result } = renderHook(() => useChatStore()); + + await act(async () => { + await result.current.updatePluginState(messageId, pluginStateKey, pluginStateValue); + }); + + expect(messageService.updateMessagePluginState).toHaveBeenCalledWith( + messageId, + pluginStateKey, + pluginStateValue, + ); + expect(initialState.refreshMessages).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/store/chat/actions/plugin.ts b/src/store/chat/actions/plugin.ts index 3c83e02f2a307..e64db6bb70e7f 100644 --- a/src/store/chat/actions/plugin.ts +++ b/src/store/chat/actions/plugin.ts @@ -1,9 +1,12 @@ +import { Md5 } from 'ts-md5'; import { StateCreator } from 'zustand/vanilla'; -import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin'; +import { PLUGIN_SCHEMA_API_MD5_PREFIX, PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin'; import { chatService } from '@/services/chat'; import { messageService } from '@/services/message'; import { ChatStore } from '@/store/chat/store'; +import { useToolStore } from '@/store/tool'; +import { pluginSelectors } from '@/store/tool/selectors'; import { ChatPluginPayload } from '@/types/chatMessage'; import { OpenAIFunctionCall } from '@/types/openai/functionCall'; import { setNamespace } from '@/utils/storeDebug'; @@ -79,6 +82,16 @@ export const chatPlugin: StateCreator< identifier, type: (type ?? 'default') as any, }; + + // if the apiName is md5, try to find the correct apiName in the plugins + if (apiName.startsWith(PLUGIN_SCHEMA_API_MD5_PREFIX)) { + const md5 = apiName.replace(PLUGIN_SCHEMA_API_MD5_PREFIX, ''); + const manifest = pluginSelectors.getPluginManifestById(identifier)(useToolStore.getState()); + + const api = manifest?.api.find((api) => Md5.hashStr(api.name).toString() === md5); + if (!api) return; + payload.apiName = api.name; + } } else { if (message.plugin) payload = message.plugin; } diff --git a/src/store/session/slices/agent/selectors.ts b/src/store/session/slices/agent/selectors.ts index 90a9b2246904f..8825a006a2152 100644 --- a/src/store/session/slices/agent/selectors.ts +++ b/src/store/session/slices/agent/selectors.ts @@ -78,7 +78,7 @@ const currentAgentMeta = (s: SessionStore): MetaData => { avatar: isInbox ? DEFAULT_INBOX_AVATAR : DEFAULT_AVATAR, backgroundColor: DEFAULT_BACKGROUND_COLOR, description: isInbox ? t('inbox.desc') : currentAgentSystemRole(s) || t('noDescription'), - title: isInbox ? t('inbox') : t('defaultSession'), + title: isInbox ? t('inbox.title', { ns: 'chat' }) : t('defaultSession'), }; const session = sessionSelectors.currentSession(s); diff --git a/src/store/tool/slices/plugin/selectors.ts b/src/store/tool/slices/plugin/selectors.ts index 6c0d949ccdb90..617f6274438c1 100644 --- a/src/store/tool/slices/plugin/selectors.ts +++ b/src/store/tool/slices/plugin/selectors.ts @@ -1,7 +1,8 @@ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk'; import { uniq, uniqBy } from 'lodash-es'; +import { Md5 } from 'ts-md5'; -import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin'; +import { PLUGIN_SCHEMA_API_MD5_PREFIX, PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin'; import { ChatCompletionFunctions } from '@/types/openai/chat'; import { InstallPluginMeta, LobeToolCustomPlugin } from '@/types/tool/plugin'; @@ -68,6 +69,8 @@ const installedPluginMetaList = (s: ToolStoreState) => meta: getPluginMetaById(p.identifier)(s), type: p.type, })); +const installedCustomPluginMetaList = (s: ToolStoreState) => + installedPluginMetaList(s).filter((p) => p.type === 'customPlugin'); const isPluginHasUI = (id: string) => (s: ToolStoreState) => { const plugin = getPluginManifestById(id)(s); @@ -91,7 +94,16 @@ const enabledSchema = const pluginType = manifest.type ? `${PLUGIN_SCHEMA_SEPARATOR + manifest.type}` : ''; // 将插件的 identifier 作为前缀,避免重复 - const apiName = manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name + pluginType; + let apiName = manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name + pluginType; + + // OpenAI GPT function_call name can't be longer than 64 characters + // So we need to use md5 to shorten the name + // and then find the correct apiName in response by md5 + if (apiName.length >= 64) { + const md5Content = PLUGIN_SCHEMA_API_MD5_PREFIX + Md5.hashStr(m.name).toString(); + + apiName = manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + md5Content + pluginType; + } return { ...m, @@ -111,6 +123,7 @@ export const pluginSelectors = { getPluginManifestLoadingStatus, getPluginMetaById, getPluginSettingsById, + installedCustomPluginMetaList, installedPluginManifestList, installedPluginMetaList, installedPlugins, diff --git a/src/store/tool/slices/store/selectors.test.ts b/src/store/tool/slices/store/selectors.test.ts index de0a684dbbe99..67c990db2bd6a 100644 --- a/src/store/tool/slices/store/selectors.test.ts +++ b/src/store/tool/slices/store/selectors.test.ts @@ -27,7 +27,24 @@ describe('pluginStoreSelectors', () => { describe('onlinePluginStore', () => { it('should return the online plugin list', () => { const result = pluginStoreSelectors.onlinePluginStore(mockState); - expect(result).toEqual(mockState.pluginStoreList); + expect(result).toEqual([ + { + identifier: 'plugin-1', + author: 'Author 1', + createdAt: '2021-01-01', + meta: { avatar: 'avatar-url-1', title: 'Plugin 1' }, + homepage: 'http://homepage-1.com', + type: 'plugin', + }, + { + identifier: 'plugin-2', + author: 'Author 2', + createdAt: '2022-02-02', + meta: { avatar: 'avatar-url-2', title: 'Plugin 2' }, + homepage: 'http://homepage-2.com', + type: 'plugin', + }, + ]); }); }); }); diff --git a/src/store/tool/slices/store/selectors.ts b/src/store/tool/slices/store/selectors.ts index a5114040f8bb3..b3ff871a25111 100644 --- a/src/store/tool/slices/store/selectors.ts +++ b/src/store/tool/slices/store/selectors.ts @@ -1,11 +1,22 @@ +import { InstallPluginMeta } from '@/types/tool/plugin'; + import type { ToolStoreState } from '../../initialState'; const onlinePluginStore = (s: ToolStoreState) => { - if (s.listType === 'all') return s.pluginStoreList; - const installedPluginIds = new Set(s.installedPlugins.map((i) => i.identifier)); + const list = + s.listType === 'all' + ? s.pluginStoreList + : s.pluginStoreList.filter((p) => installedPluginIds.has(p.identifier)); - return s.pluginStoreList.filter((p) => installedPluginIds.has(p.identifier)); + return list.map((p) => ({ + author: p.author, + createdAt: p.createdAt, + homepage: p.homepage, + identifier: p.identifier, + meta: p.meta, + type: 'plugin', + })); }; const isPluginInstallLoading = (id: string) => (s: ToolStoreState) => s.pluginInstallLoading[id];