From 1279cc12999ff8d36f325252ec0589fe925ccd25 Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Mon, 15 Jun 2026 17:23:54 +0800 Subject: [PATCH] feat(pluginStore): add support for third-party plugin sources with localization and configuration updates --- src/components/config/VisualConfigEditor.tsx | 32 ++++++++++++++++ .../config/VisualConfigEditorBlocks.tsx | 2 +- src/hooks/useVisualConfig.ts | 37 ++++++++++++++++++- src/i18n/locales/en.json | 5 +++ src/i18n/locales/ru.json | 5 +++ src/i18n/locales/zh-CN.json | 5 +++ src/i18n/locales/zh-TW.json | 5 +++ src/types/visualConfig.ts | 2 + 8 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/components/config/VisualConfigEditor.tsx b/src/components/config/VisualConfigEditor.tsx index 187cb1f..9c8b694 100644 --- a/src/components/config/VisualConfigEditor.tsx +++ b/src/components/config/VisualConfigEditor.tsx @@ -37,6 +37,7 @@ import { ApiKeysCardEditor, PayloadFilterRulesEditor, PayloadRulesEditor, + StringListEditor, } from './VisualConfigEditorBlocks'; import styles from './VisualConfigEditor.module.scss'; @@ -218,6 +219,10 @@ export function VisualConfigEditor({ (apiKeysText: string) => onChange({ apiKeysText }), [onChange] ); + const handlePluginStoreSourcesChange = useCallback( + (pluginStoreSources: string[]) => onChange({ pluginStoreSources }), + [onChange] + ); const handlePayloadDefaultRulesChange = useCallback( (payloadDefaultRules: PayloadRule[]) => onChange({ payloadDefaultRules }), [onChange] @@ -667,6 +672,33 @@ export function VisualConfigEditor({ /> + +
+ + +
+ {t('config_management.visual.sections.system.plugin_store_sources_hint')} +
+
+
+ value.trim()).filter(Boolean); + if (nextValues.length > 0) { + doc.setIn(path, nextValues); + return; + } + if (docHas(doc, path)) doc.deleteIn(path); +} + function setIntFromStringInDoc(doc: YamlDocument, path: YamlPath, value: unknown): void { const safe = typeof value === 'string' ? value : ''; const trimmed = safe.trim(); @@ -790,6 +799,13 @@ function getNextDirtyFields( ] as Array ).forEach(updateScalarDirty); + if (Object.prototype.hasOwnProperty.call(patch, 'pluginStoreSources')) { + updateDirty( + 'pluginStoreSources', + areStringArraysEqual(nextValues.pluginStoreSources, baselineValues.pluginStoreSources) + ); + } + if (Object.prototype.hasOwnProperty.call(patch, 'payloadDefaultRules')) { updateDirty( 'payloadDefaultRules', @@ -957,6 +973,7 @@ export function useVisualConfig() { authDir: typeof parsed['auth-dir'] === 'string' ? parsed['auth-dir'] : '', apiKeysText: resolveApiKeysText(parsed), pluginsEnabled: Boolean(plugins?.enabled), + pluginStoreSources: parseStringList(plugins?.['store-sources']), debug: Boolean(parsed.debug), commercialMode: Boolean(parsed['commercial-mode']), @@ -1126,10 +1143,28 @@ export function useVisualConfig() { if ( docHas(doc, ['plugins']) || values.pluginsEnabled || - shouldWriteManagedField(doc, ['plugins', 'enabled'], dirtyFields, 'pluginsEnabled') + values.pluginStoreSources.length > 0 || + shouldWriteManagedField(doc, ['plugins', 'enabled'], dirtyFields, 'pluginsEnabled') || + shouldWriteManagedField( + doc, + ['plugins', 'store-sources'], + dirtyFields, + 'pluginStoreSources' + ) ) { ensureMapInDoc(doc, ['plugins']); setBooleanInDoc(doc, ['plugins', 'enabled'], values.pluginsEnabled); + if ( + values.pluginStoreSources.length > 0 || + shouldWriteManagedField( + doc, + ['plugins', 'store-sources'], + dirtyFields, + 'pluginStoreSources' + ) + ) { + setStringListInDoc(doc, ['plugins', 'store-sources'], values.pluginStoreSources); + } deleteIfMapEmpty(doc, ['plugins']); } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 5e38613..da8e237 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -920,6 +920,11 @@ "logging_to_file_desc": "Save logs to files", "plugins_enabled": "Enable Plugin System", "plugins_enabled_desc": "Enable standard dynamic-library plugin loading; individual plugin instances are still managed on the Plugins page", + "plugin_store_sources": "Third-party Plugin Sources", + "plugin_store_sources_desc": "Append plugin-store registry sources; the built-in official source is always kept", + "plugin_store_sources_label": "Plugin source registry URL (plugins.store-sources)", + "plugin_store_sources_placeholder": "https://example.com/cliproxy-plugins/registry.json", + "plugin_store_sources_hint": "One registry.json URL per row. Empty rows are filtered on save", "logs_max_size": "Log File Size Limit (MB)", "error_logs_max_files": "Retained Error Log Files", "usage_statistics_enabled": "Enable In-memory Usage Statistics", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 9774899..13ee47c 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -907,6 +907,11 @@ "logging_to_file_desc": "Сохранять журналы в файлы", "plugins_enabled": "Включить систему плагинов", "plugins_enabled_desc": "Включает загрузку стандартных dynamic-library плагинов; отдельные экземпляры управляются на странице плагинов", + "plugin_store_sources": "Сторонние источники плагинов", + "plugin_store_sources_desc": "Добавляет registry-источники магазина плагинов; встроенный официальный источник всегда сохраняется", + "plugin_store_sources_label": "URL registry источника плагинов (plugins.store-sources)", + "plugin_store_sources_placeholder": "https://example.com/cliproxy-plugins/registry.json", + "plugin_store_sources_hint": "Один URL registry.json в строке. Пустые строки отфильтровываются при сохранении", "logs_max_size": "Максимальный размер файла журнала (МБ)", "error_logs_max_files": "Файлов журнала ошибок", "usage_statistics_enabled": "Включить статистику использования в памяти", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 5dc424f..097da25 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -920,6 +920,11 @@ "logging_to_file_desc": "将日志保存到文件", "plugins_enabled": "启用插件系统", "plugins_enabled_desc": "启用标准动态库插件加载;具体插件实例仍在插件管理页启停", + "plugin_store_sources": "第三方插件源", + "plugin_store_sources_desc": "追加插件商店 registry 源;内置官方源始终保留", + "plugin_store_sources_label": "插件源 registry URL (plugins.store-sources)", + "plugin_store_sources_placeholder": "https://example.com/cliproxy-plugins/registry.json", + "plugin_store_sources_hint": "每行一个 registry.json URL,保存时会过滤空行", "logs_max_size": "日志文件大小限制 (MB)", "error_logs_max_files": "错误日志保留文件数", "usage_statistics_enabled": "启用内存用量统计", diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json index f8d45df..a81c7e9 100644 --- a/src/i18n/locales/zh-TW.json +++ b/src/i18n/locales/zh-TW.json @@ -946,6 +946,11 @@ "logging_to_file_desc": "將記錄儲存到檔案", "plugins_enabled": "啟用插件系統", "plugins_enabled_desc": "啟用標準動態庫插件載入;具體插件實例仍在插件管理頁啟停", + "plugin_store_sources": "第三方插件源", + "plugin_store_sources_desc": "追加插件商店 registry 源;內建官方源始終保留", + "plugin_store_sources_label": "插件源 registry URL(plugins.store-sources)", + "plugin_store_sources_placeholder": "https://example.com/cliproxy-plugins/registry.json", + "plugin_store_sources_hint": "每列一個 registry.json URL,儲存時會過濾空列", "logs_max_size": "記錄檔大小限制(MB)", "error_logs_max_files": "錯誤記錄保留檔案數", "usage_statistics_enabled": "啟用記憶體用量統計", diff --git a/src/types/visualConfig.ts b/src/types/visualConfig.ts index 28178a8..315d201 100644 --- a/src/types/visualConfig.ts +++ b/src/types/visualConfig.ts @@ -81,6 +81,7 @@ export type VisualConfigValues = { authDir: string; apiKeysText: string; pluginsEnabled: boolean; + pluginStoreSources: string[]; debug: boolean; commercialMode: boolean; loggingToFile: boolean; @@ -145,6 +146,7 @@ export const DEFAULT_VISUAL_VALUES: VisualConfigValues = { authDir: '', apiKeysText: '', pluginsEnabled: false, + pluginStoreSources: [], debug: false, commercialMode: false, loggingToFile: false,