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
This commit is contained in:
langbot-dev
2026-08-12 02:34:30 +08:00
parent 72c03276ff
commit 8b0f2f58b1
9 changed files with 64 additions and 23 deletions
+56 -23
View File
@@ -1155,6 +1155,9 @@ function WizardModelScan({
const [searchQuery, setSearchQuery] = useState('');
const [manualModelName, setManualModelName] = useState('');
const [manualAdding, setManualAdding] = useState(false);
const [addedModelNames, setAddedModelNames] = useState<Set<string>>(
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 ? (
<>
<div className="max-h-48 overflow-y-auto space-y-2">
{filteredModels.map((model) => (
<label
key={model.name}
className="flex items-center gap-3 p-3 rounded-lg border cursor-pointer hover:bg-accent transition-colors"
>
<input
type="checkbox"
checked={selectedModels.has(model.name)}
onChange={() => toggleModel(model.name)}
className="w-4 h-4 rounded"
/>
<div className="flex-1 min-w-0">
<span className="text-sm font-medium truncate block">
{model.name}
</span>
{model.context_length && (
<span className="text-xs text-muted-foreground">
ctx: {model.context_length.toLocaleString()}
{filteredModels.map((model) => {
const isAdded = addedModelNames.has(model.name);
return (
<label
key={model.name}
className={cn(
'flex items-center gap-3 p-3 rounded-lg border transition-colors',
isAdded
? 'opacity-50 cursor-not-allowed bg-muted/50'
: 'cursor-pointer hover:bg-accent',
)}
>
<input
type="checkbox"
checked={selectedModels.has(model.name) || isAdded}
disabled={isAdded}
onChange={() => toggleModel(model.name)}
className="w-4 h-4 rounded"
/>
<div className="flex-1 min-w-0">
<span className="text-sm font-medium truncate block">
{model.name}
</span>
{model.context_length && (
<span className="text-xs text-muted-foreground">
ctx: {model.context_length.toLocaleString()}
</span>
)}
</div>
{isAdded && (
<span className="text-xs text-green-600 font-medium shrink-0">
{t('wizard.provider.alreadyAdded')}
</span>
)}
</div>
</label>
))}
</label>
);
})}
{filteredModels.length === 0 && (
<p className="text-sm text-muted-foreground text-center py-4">
{t('wizard.provider.noMatch')}
@@ -1365,6 +1388,16 @@ function WizardModelScan({
</div>
</>
)}
{/* Next button when models have been added */}
{addedModelNames.size > 0 && (
<div className="flex justify-end pt-2 border-t">
<Button onClick={onModelsAdded}>
{t('wizard.next')}
<ArrowRight className="w-4 h-4 ml-1.5" />
</Button>
</div>
)}
</div>
</div>
);
+1
View File
@@ -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',
+1
View File
@@ -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',
+1
View File
@@ -1792,6 +1792,7 @@ const jaJP = {
manualModelPlaceholder: 'モデル名を入力して手動追加',
addManual: '追加',
saveAndNext: '保存して次へ',
alreadyAdded: '追加済み',
},
modelSource: {
title: 'モデルソースを選択',
+1
View File
@@ -1701,6 +1701,7 @@ const ruRU = {
manualModelPlaceholder: 'Введите имя модели для ручного добавления',
addManual: 'Добавить',
saveAndNext: 'Сохранить и далее',
alreadyAdded: 'Добавлено',
},
modelSource: {
title: 'Выберите источник модели',
+1
View File
@@ -1665,6 +1665,7 @@ const thTH = {
manualModelPlaceholder: 'กรอกชื่อโมเดลเพื่อเพิ่มด้วยตนเอง',
addManual: 'เพิ่ม',
saveAndNext: 'บันทึกและถัดไป',
alreadyAdded: 'เพิ่มแล้ว',
},
modelSource: {
title: 'เลือกแหล่งโมเดล',
+1
View File
@@ -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',
+1
View File
@@ -1789,6 +1789,7 @@ const zhHans = {
manualModelPlaceholder: '输入模型名称手动添加',
addManual: '添加',
saveAndNext: '保存并下一步',
alreadyAdded: '已添加',
},
modelSource: {
title: '选择模型来源',
+1
View File
@@ -1612,6 +1612,7 @@ const zhHant = {
manualModelPlaceholder: '輸入模型名稱手動新增',
addManual: '新增',
saveAndNext: '儲存並下一步',
alreadyAdded: '已新增',
},
modelSource: {
title: '選擇模型來源',