From 8b0f2f58b114b70ab47e44cc75a37e49d78f9e62 Mon Sep 17 00:00:00 2001 From: langbot-dev Date: Wed, 12 Aug 2026 02:34:30 +0800 Subject: [PATCH] fix(wizard): prevent duplicate model additions in scan page - Track added model names in state - Disable already-added models with 'Added' badge - Clear selection after successful add - Add Next button at bottom when models have been added --- web/src/app/wizard/page.tsx | 79 +++++++++++++++++++++++---------- web/src/i18n/locales/en-US.ts | 1 + web/src/i18n/locales/es-ES.ts | 1 + web/src/i18n/locales/ja-JP.ts | 1 + web/src/i18n/locales/ru-RU.ts | 1 + web/src/i18n/locales/th-TH.ts | 1 + web/src/i18n/locales/vi-VN.ts | 1 + web/src/i18n/locales/zh-Hans.ts | 1 + web/src/i18n/locales/zh-Hant.ts | 1 + 9 files changed, 64 insertions(+), 23 deletions(-) diff --git a/web/src/app/wizard/page.tsx b/web/src/app/wizard/page.tsx index fb3e13389..c30340eaa 100644 --- a/web/src/app/wizard/page.tsx +++ b/web/src/app/wizard/page.tsx @@ -1155,6 +1155,9 @@ function WizardModelScan({ const [searchQuery, setSearchQuery] = useState(''); const [manualModelName, setManualModelName] = useState(''); const [manualAdding, setManualAdding] = useState(false); + const [addedModelNames, setAddedModelNames] = useState>( + new Set(initialSelectedModels), + ); // Auto-scan on mount useEffect(() => { @@ -1216,7 +1219,13 @@ function WizardModelScan({ toast.success( t('wizard.provider.modelsAdded', { count: selectedModels.size }), ); - onModelsAdded(); + setAddedModelNames((prev) => { + const next = new Set(prev); + for (const name of selectedModels) next.add(name); + return next; + }); + setSelectedModels(new Set()); + onSelectedModelsChange(new Set()); } catch { toast.error(t('wizard.provider.modelsAddError')); } finally { @@ -1238,8 +1247,8 @@ function WizardModelScan({ extra_args: {}, } as never); toast.success(t('wizard.provider.modelsAdded', { count: 1 })); + setAddedModelNames((prev) => new Set(prev).add(name)); setManualModelName(''); - onModelsAdded(); } catch { toast.error(t('wizard.provider.modelsAddError')); } finally { @@ -1282,29 +1291,43 @@ function WizardModelScan({ {scannedModels.length > 0 ? ( <>
- {filteredModels.map((model) => ( - - ))} + + ); + })} {filteredModels.length === 0 && (

{t('wizard.provider.noMatch')} @@ -1365,6 +1388,16 @@ function WizardModelScan({

)} + + {/* Next button when models have been added */} + {addedModelNames.size > 0 && ( +
+ +
+ )} ); diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index e1da065db..968f4a67d 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -1873,6 +1873,7 @@ const enUS = { manualModelPlaceholder: 'Enter model name to add manually', addManual: 'Add', saveAndNext: 'Save & Next', + alreadyAdded: 'Added', }, modelSource: { title: 'Choose Model Source', diff --git a/web/src/i18n/locales/es-ES.ts b/web/src/i18n/locales/es-ES.ts index d090f6597..6895579e6 100644 --- a/web/src/i18n/locales/es-ES.ts +++ b/web/src/i18n/locales/es-ES.ts @@ -1731,6 +1731,7 @@ const esES = { manualModelPlaceholder: 'Ingresa el nombre del modelo para agregar manualmente', addManual: 'Agregar', saveAndNext: 'Guardar y siguiente', + alreadyAdded: 'Agregado', }, modelSource: { title: 'Elegir fuente del modelo', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index bf4f192f4..1404b9996 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -1792,6 +1792,7 @@ const jaJP = { manualModelPlaceholder: 'モデル名を入力して手動追加', addManual: '追加', saveAndNext: '保存して次へ', + alreadyAdded: '追加済み', }, modelSource: { title: 'モデルソースを選択', diff --git a/web/src/i18n/locales/ru-RU.ts b/web/src/i18n/locales/ru-RU.ts index c1188e6db..07d82b9f8 100644 --- a/web/src/i18n/locales/ru-RU.ts +++ b/web/src/i18n/locales/ru-RU.ts @@ -1701,6 +1701,7 @@ const ruRU = { manualModelPlaceholder: 'Введите имя модели для ручного добавления', addManual: 'Добавить', saveAndNext: 'Сохранить и далее', + alreadyAdded: 'Добавлено', }, modelSource: { title: 'Выберите источник модели', diff --git a/web/src/i18n/locales/th-TH.ts b/web/src/i18n/locales/th-TH.ts index 23919a658..9e591db36 100644 --- a/web/src/i18n/locales/th-TH.ts +++ b/web/src/i18n/locales/th-TH.ts @@ -1665,6 +1665,7 @@ const thTH = { manualModelPlaceholder: 'กรอกชื่อโมเดลเพื่อเพิ่มด้วยตนเอง', addManual: 'เพิ่ม', saveAndNext: 'บันทึกและถัดไป', + alreadyAdded: 'เพิ่มแล้ว', }, modelSource: { title: 'เลือกแหล่งโมเดล', diff --git a/web/src/i18n/locales/vi-VN.ts b/web/src/i18n/locales/vi-VN.ts index 83c18d23a..057512422 100644 --- a/web/src/i18n/locales/vi-VN.ts +++ b/web/src/i18n/locales/vi-VN.ts @@ -1691,6 +1691,7 @@ const viVN = { manualModelPlaceholder: 'Nhập tên mô hình để thêm thủ công', addManual: 'Thêm', saveAndNext: 'Lưu & Tiếp theo', + alreadyAdded: 'Đã thêm', }, modelSource: { title: 'Chọn nguồn mô hình', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 7a1e9afa3..c02ff3fa8 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -1789,6 +1789,7 @@ const zhHans = { manualModelPlaceholder: '输入模型名称手动添加', addManual: '添加', saveAndNext: '保存并下一步', + alreadyAdded: '已添加', }, modelSource: { title: '选择模型来源', diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index 85fe3b0ae..a7c096a8a 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -1612,6 +1612,7 @@ const zhHant = { manualModelPlaceholder: '輸入模型名稱手動新增', addManual: '新增', saveAndNext: '儲存並下一步', + alreadyAdded: '已新增', }, modelSource: { title: '選擇模型來源',