From 1b37dababacf3cdc7873d92ab4d869e83f4640ef Mon Sep 17 00:00:00 2001 From: youhuanghe <1051233107@qq.com> Date: Sun, 8 Mar 2026 15:05:05 +0000 Subject: [PATCH] feat(rag): add data-only migration option and fix dialog width Add option to migrate knowledge base data without auto-installing the LangRAG plugin (for offline/intranet environments). Also narrow the migration dialog to match other confirmation dialogs. Co-Authored-By: Claude Opus 4.6 --- .../controller/groups/knowledge/migration.py | 97 ++++++++++--------- .../kb-migration-dialog/KBMigrationDialog.tsx | 66 +++++++------ web/src/app/infra/http/BackendClient.ts | 8 +- web/src/i18n/locales/en-US.ts | 7 +- web/src/i18n/locales/ja-JP.ts | 7 +- web/src/i18n/locales/zh-Hans.ts | 6 +- web/src/i18n/locales/zh-Hant.ts | 7 +- 7 files changed, 109 insertions(+), 89 deletions(-) diff --git a/src/langbot/pkg/api/http/controller/groups/knowledge/migration.py b/src/langbot/pkg/api/http/controller/groups/knowledge/migration.py index 51f1f993..68faa00b 100644 --- a/src/langbot/pkg/api/http/controller/groups/knowledge/migration.py +++ b/src/langbot/pkg/api/http/controller/groups/knowledge/migration.py @@ -2,6 +2,7 @@ import asyncio import json import httpx +import quart import sqlalchemy from ... import group @@ -52,55 +53,56 @@ class KnowledgeMigrationRouterGroup(group.RouterGroup): ) return result.first() is not None - async def _execute_rag_migration(self, task_context: taskmgr.TaskContext): - """Execute RAG migration: install langrag plugin and restore backup data.""" + async def _execute_rag_migration(self, task_context: taskmgr.TaskContext, install_plugin: bool = True): + """Execute RAG migration: optionally install langrag plugin and restore backup data.""" warnings = [] - # Step 1: Install langrag plugin from marketplace - task_context.trace('Installing LangRAG plugin from marketplace...', action='install-plugin') - try: - # Query marketplace for latest version - space_url = self.ap.instance_config.data.get('space', {}).get('url', DEFAULT_SPACE_URL).rstrip('/') - async with httpx.AsyncClient(trust_env=True, timeout=15) as client: - resp = await client.get( - f'{space_url}/api/v1/marketplace/plugins/{LANGRAG_PLUGIN_AUTHOR}/{LANGRAG_PLUGIN_NAME}' - ) - resp.raise_for_status() - plugin_data = resp.json().get('data', {}).get('plugin', {}) - plugin_version = plugin_data.get('latest_version') - if not plugin_version: - raise Exception('Could not determine latest LangRAG version from marketplace') - - install_info = { - 'plugin_author': LANGRAG_PLUGIN_AUTHOR, - 'plugin_name': LANGRAG_PLUGIN_NAME, - 'plugin_version': plugin_version, - } - await self.ap.plugin_connector.install_plugin( - PluginInstallSource.MARKETPLACE, install_info, task_context=task_context - ) - except Exception as e: - # Plugin may already be installed - self.ap.logger.warning(f'LangRAG plugin install returned: {e}') - task_context.trace(f'Plugin install note: {e}') - - # Step 2: Wait for the plugin to be available - task_context.trace('Waiting for LangRAG plugin to become available...', action='wait-plugin') - max_retries = 30 - for i in range(max_retries): + if install_plugin: + # Step 1: Install langrag plugin from marketplace + task_context.trace('Installing LangRAG plugin from marketplace...', action='install-plugin') try: - engines = await self.ap.plugin_connector.list_knowledge_engines() - engine_ids = [e.get('plugin_id') for e in engines] - if LANGRAG_PLUGIN_ID in engine_ids: - task_context.trace('LangRAG plugin is ready.') - break - except Exception: - pass - if i == max_retries - 1: - raise Exception( - f'LangRAG plugin ({LANGRAG_PLUGIN_ID}) did not become available after {max_retries} retries' + # Query marketplace for latest version + space_url = self.ap.instance_config.data.get('space', {}).get('url', DEFAULT_SPACE_URL).rstrip('/') + async with httpx.AsyncClient(trust_env=True, timeout=15) as client: + resp = await client.get( + f'{space_url}/api/v1/marketplace/plugins/{LANGRAG_PLUGIN_AUTHOR}/{LANGRAG_PLUGIN_NAME}' + ) + resp.raise_for_status() + plugin_data = resp.json().get('data', {}).get('plugin', {}) + plugin_version = plugin_data.get('latest_version') + if not plugin_version: + raise Exception('Could not determine latest LangRAG version from marketplace') + + install_info = { + 'plugin_author': LANGRAG_PLUGIN_AUTHOR, + 'plugin_name': LANGRAG_PLUGIN_NAME, + 'plugin_version': plugin_version, + } + await self.ap.plugin_connector.install_plugin( + PluginInstallSource.MARKETPLACE, install_info, task_context=task_context ) - await asyncio.sleep(2) + except Exception as e: + # Plugin may already be installed + self.ap.logger.warning(f'LangRAG plugin install returned: {e}') + task_context.trace(f'Plugin install note: {e}') + + # Step 2: Wait for the plugin to be available + task_context.trace('Waiting for LangRAG plugin to become available...', action='wait-plugin') + max_retries = 30 + for i in range(max_retries): + try: + engines = await self.ap.plugin_connector.list_knowledge_engines() + engine_ids = [e.get('plugin_id') for e in engines] + if LANGRAG_PLUGIN_ID in engine_ids: + task_context.trace('LangRAG plugin is ready.') + break + except Exception: + pass + if i == max_retries - 1: + raise Exception( + f'LangRAG plugin ({LANGRAG_PLUGIN_ID}) did not become available after {max_retries} retries' + ) + await asyncio.sleep(2) # Step 3: Restore internal knowledge bases from backup task_context.trace('Restoring internal knowledge bases...', action='restore-internal') @@ -283,9 +285,12 @@ class KnowledgeMigrationRouterGroup(group.RouterGroup): if not needed: return self.http_status(400, -1, 'RAG migration is not needed') + data = await quart.request.get_json(silent=True) or {} + install_plugin = data.get('install_plugin', True) + ctx = taskmgr.TaskContext.new() wrapper = self.ap.task_mgr.create_user_task( - self._execute_rag_migration(task_context=ctx), + self._execute_rag_migration(task_context=ctx, install_plugin=install_plugin), kind='rag-migration', name='rag-migration-execute', label='Migrating knowledge bases to plugin architecture', diff --git a/web/src/app/home/knowledge/components/kb-migration-dialog/KBMigrationDialog.tsx b/web/src/app/home/knowledge/components/kb-migration-dialog/KBMigrationDialog.tsx index 703084c2..f0d018e0 100644 --- a/web/src/app/home/knowledge/components/kb-migration-dialog/KBMigrationDialog.tsx +++ b/web/src/app/home/knowledge/components/kb-migration-dialog/KBMigrationDialog.tsx @@ -45,9 +45,9 @@ export default function KBMigrationDialog({ }, }); - const handleStartMigration = async () => { + const handleMigration = async (installPlugin: boolean) => { try { - const resp = await httpClient.executeRagMigration(); + const resp = await httpClient.executeRagMigration(installPlugin); asyncTask.startTask(resp.task_id); } catch { toast.error(t('knowledge.migration.error')); @@ -77,7 +77,7 @@ export default function KBMigrationDialog({ if (!isRunning) onOpenChange(v); }} > - + {t('knowledge.migration.title')} @@ -87,18 +87,13 @@ export default function KBMigrationDialog({
{!isRunning && !isError && ( - <> -

- {t('knowledge.migration.detected', { - total: totalCount, - internal: internalKbCount, - external: externalKbCount, - })} -

-

- {t('knowledge.migration.installHint')} -

- +

+ {t('knowledge.migration.detected', { + total: totalCount, + internal: internalKbCount, + external: externalKbCount, + })} +

)} {isRunning && ( @@ -122,26 +117,39 @@ export default function KBMigrationDialog({ )}
- - {!isRunning && ( - - )} + {!isRunning && !isError && ( - + <> + + +

+ {t('knowledge.migration.dataOnlyHint')} +

+ )} {isError && ( - )} + {!isRunning && ( + + )}
diff --git a/web/src/app/infra/http/BackendClient.ts b/web/src/app/infra/http/BackendClient.ts index 63c5adfa..1c3609c4 100644 --- a/web/src/app/infra/http/BackendClient.ts +++ b/web/src/app/infra/http/BackendClient.ts @@ -716,8 +716,12 @@ export class BackendClient extends BaseHttpClient { return this.get('/api/v1/knowledge/migration/status'); } - public executeRagMigration(): Promise { - return this.post('/api/v1/knowledge/migration/execute'); + public executeRagMigration( + installPlugin: boolean = true, + ): Promise { + return this.post('/api/v1/knowledge/migration/execute', { + install_plugin: installPlugin, + }); } public dismissRagMigration(): Promise { diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index f6994ce2..2d6df66c 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -779,9 +779,10 @@ const enUS = { 'Legacy knowledge base data detected. Migration to the new plugin-based RAG architecture is required.', detected: 'Found {{total}} knowledge base(s) to migrate ({{internal}} internal, {{external}} external).', - installHint: - 'Migration will automatically install the LangRAG plugin and restore your knowledge base data. Documents and vector data will be preserved.', - start: 'Migrate Now', + startWithInstall: 'Auto-install Plugin & Migrate', + startDataOnly: 'Migrate Data Only', + dataOnlyHint: + '"Migrate Data Only" is for offline/intranet environments. Please install the LangRAG plugin manually before migrating.', dismiss: 'Skip for Now', running: 'Migrating knowledge bases, please wait...', success: 'Knowledge base migration completed', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index ee34f974..2defcae0 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -768,9 +768,10 @@ const jaJP = { '旧バージョンのナレッジベースデータが検出されました。新しいプラグインベースのRAGアーキテクチャへの移行が必要です。', detected: '移行が必要なナレッジベースが{{total}}件見つかりました(内部{{internal}}件、外部{{external}}件)。', - installHint: - '移行により LangRAG プラグインが自動的にインストールされ、ナレッジベースデータが復元されます。ドキュメントとベクトルデータは保持されます。', - start: '今すぐ移行', + startWithInstall: 'プラグインを自動インストールして移行', + startDataOnly: 'データのみ移行', + dataOnlyHint: + '「データのみ移行」はオフライン環境向けです。事前に LangRAG プラグインを手動でインストールしてください。', dismiss: '後で', running: 'ナレッジベースを移行中です。しばらくお待ちください...', success: 'ナレッジベースの移行が完了しました', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 85e81828..b97c6ccf 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -747,9 +747,9 @@ const zhHans = { description: '检测到旧版知识库数据,需要迁移到新的插件化 RAG 架构。', detected: '共检测到 {{total}} 个知识库需要迁移({{internal}} 个内置知识库,{{external}} 个外部知识库)。', - installHint: - '迁移将自动安装 LangRAG 插件并恢复您的知识库数据,文档和向量数据将被保留。', - start: '立即迁移', + startWithInstall: '自动安装插件并迁移', + startDataOnly: '仅迁移数据', + dataOnlyHint: '「仅迁移数据」适合内网环境使用,请自行安装 LangRAG 插件后再迁移。', dismiss: '暂不迁移', running: '正在迁移知识库,请稍候...', success: '知识库迁移完成', diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index 6ffdcc98..6fd6f5c2 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -727,9 +727,10 @@ const zhHant = { description: '檢測到舊版知識庫資料,需要遷移到新的插件化 RAG 架構。', detected: '共檢測到 {{total}} 個知識庫需要遷移({{internal}} 個內建知識庫,{{external}} 個外部知識庫)。', - installHint: - '遷移將自動安裝 LangRAG 插件並恢復您的知識庫資料,文件和向量資料將被保留。', - start: '立即遷移', + startWithInstall: '自動安裝插件並遷移', + startDataOnly: '僅遷移資料', + dataOnlyHint: + '「僅遷移資料」適合內網環境使用,請自行安裝 LangRAG 插件後再遷移。', dismiss: '暫不遷移', running: '正在遷移知識庫,請稍候...', success: '知識庫遷移完成',