From a13c9ee6bae21ceac0d4cb522fc842162e93f723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Sat, 16 May 2026 12:07:35 +0800 Subject: [PATCH] fix(models): stop auto-filling fallback chain and add clear-all button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fallback editor's "Add" buttons appeared to do nothing because applyDefaultModel auto-populated defaults.model.fallbacks with every non-primary model whenever the list was empty (and the same for defaults.models). After any save with an empty chain, the chain got filled with all 17+ candidates; the candidate pool became "No candidate models available" and any subsequent Add click hit the early return `if (modelConfig.fallbacks.includes(full)) return`. Even worse, the auto-fill made it impossible for users to keep an empty fallback chain on purpose: deleting all chips would silently get replaced with every model on the next debounced autosave. Remove the auto-fill in applyDefaultModel. An empty fallback chain is a valid configuration that means "no implicit fallback"; Gateway already surfaces a clear primary-model error in that case. normalizeDefaultModel Selection still cleans up invalid/duplicate entries. Also add a small "Clear All" button next to the active chain title so users can drop the entire fallback list in one click instead of removing chips one by one (especially useful for the existing bloated 17-fallback state created by the old auto-fill path). ## Verification - node --check src/pages/models.js - node --check src/locales/modules/models.js - npm run build - Playwright repro: open /#/models → Clear All → fallbacks on disk go from 17 → 0 → click Add on one candidate → fallbacks on disk are exactly that one entry, no longer auto-expanded back to 17. --- src/locales/modules/models.js | 3 +++ src/pages/models.js | 40 +++++++++++++++++++++++------------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/locales/modules/models.js b/src/locales/modules/models.js index e3b12ad..b0e5ae6 100644 --- a/src/locales/modules/models.js +++ b/src/locales/modules/models.js @@ -191,4 +191,7 @@ export default { setAsPrimary: _('设为主用', 'Set as Primary', '設為主用'), remove: _('移除', 'Remove', '移除'), add: _('加入', 'Add', '加入'), + addAll: _('全部加入', 'Add All', '全部加入'), + clearAll: _('清空全部', 'Clear All', '清空全部'), + confirmClearAll: _('确定清空所有备选模型?主模型不会被影响。', 'Clear all fallback models? Primary model is not affected.', '確定清空所有備選模型?主模型不會被影響。'), } diff --git a/src/pages/models.js b/src/pages/models.js index 6865375..f96b17f 100644 --- a/src/pages/models.js +++ b/src/pages/models.js @@ -301,7 +301,10 @@ function renderFallbackWaterfall(state) {
-
${t('models.activeChainTitle')}
+
+
${t('models.activeChainTitle')}
+ ${currentFallbacks.length > 0 ? `` : ''} +
${currentFallbacks.map((f, i) => `
@@ -398,6 +401,22 @@ function bindWaterfallActions(page, state) { } }) + // 清空全部备选 + const clearAllBtn = container.querySelector('.btn-clear-all-fb') + if (clearAllBtn) { + clearAllBtn.onclick = async () => { + const yes = await showConfirm(t('models.confirmClearAll')) + if (!yes) return + const modelConfig = ensureDefaultModelConfig(state) + if (!modelConfig.fallbacks.length) return + pushUndo(state) + modelConfig.fallbacks = [] + renderDefaultBar(page, state) + updateUndoBtn(page, state) + autoSave(state) + } + } + // 折叠候选服务商 container.querySelectorAll('.candidate-provider-header').forEach(header => { header.onclick = () => { @@ -1131,20 +1150,13 @@ function applyDefaultModel(state) { const defaults = state.config.agents.defaults if (!defaults.model) defaults.model = {} defaults.model.primary = primary + if (!Array.isArray(defaults.model.fallbacks)) defaults.model.fallbacks = [] + if (!defaults.models || typeof defaults.models !== 'object' || Array.isArray(defaults.models)) defaults.models = {} - // fallbacks / models 仅在为空时初始化(首次安装友好),不再每次保存都覆盖 - // 避免用户精心维护的精简 fallback 链被重写,且随模型增多不断膨胀 (fixes #190) - if (!defaults.model.fallbacks || defaults.model.fallbacks.length === 0) { - const allModels = collectAllModels(state.config) - defaults.model.fallbacks = allModels.filter(m => m.full !== primary).map(m => m.full) - } - if (!defaults.models || Object.keys(defaults.models).length === 0) { - const allModels = collectAllModels(state.config) - const modelsMap = {} - modelsMap[primary] = {} - for (const m of allModels) { if (m.full !== primary) modelsMap[m.full] = {} } - defaults.models = modelsMap - } + // 注意:不再在 fallbacks/models 为空时自动塞入"全部可用模型"。 + // 旧逻辑会导致用户清空备选链或新增主模型后,一次保存就把所有候选自动加进 fallbacks/models, + // 用户点"加入"时模型已经全部在备选链里 → 候选池空 → "加入"按钮显示"无可用候选模型", + // 看起来就像"加入按钮没效果"。空备选链是合法状态:此时 Gateway 主模型失败直接报错,不做隐式 fallback。 normalizeDefaultModelSelection(state.config) // 注意:不再强制同步到各 agent 的 model.primary