fix(ui): per-key 编辑器画布画错容器、保存成功却不关弹窗

CDP 实测发现两个真 bug,都是复用画布时引入的。

一、画布画进了后台标签页。sortCanvasEl() 原本按文档顺序找,先命中
#scr-canvas(全局优先级页的画布)。而弹窗是挂在设置页之上的,两个画布
同时存在于 DOM —— 于是 per-key 的 36 条泳道被画进隐藏的全局画布,弹窗
里空空如也,回���全局页还会发现自己的链被覆盖了。

改为按 sortState.scope 解析:有 scope 就用 #key-auto-canvas,否则用
#scr-canvas。

二、保存成功却弹窗不关。!j.slots 分支里我写了 return,跳过了
keyAutoClose。写确实成功了(服务端已存),但用户看到弹窗还开着、按钮
还是禁用状态,会反复点保存。而且那条 toast 文案写成"保存后 AUTO 请求
会失败",读起来像保存失败了 —— 一并改成"已保存,但槽位无法解析…"。

判据两条,变异验证:恢复按文档顺序查找 → 第一条判红;!slots 分支加
return → 第二条判红。第二条最初只查 keyAutoClose() 是否存在,变异加了
return 仍然通过,属于判据漏放,已改为扫描 try 块内 close 调用之前的
所有 return 语句。

CDP 复测(先开全局页制造干扰,再开 per-key 弹窗):
- 弹窗画布 36 泳道,全局画布保持 4 泳道未被污染
- 保存 gpt-5.2@xinjianya 后弹窗关闭、服务端存储正确、scope 清空
- 重开弹窗读回 [["gpt-5.2@xinjianya"]]

Co-Authored-By: ModelRouter <noreply@modelrouter.dev>
This commit is contained in:
JianFeeeee
2026-10-03 09:21:02 +08:00
parent 28149bdd1a
commit aa003e84d3
2 changed files with 98 additions and 9 deletions

View File

@ -832,7 +832,7 @@
kAutoChainSave: "保存",
kAutoChainTier: "层级",
kAutoChainEmpty: "该密钥使用全局 AUTO 链",
kAutoChainBad: "槽位无法解析(模型或源不存在),保存后 AUTO 请求会失败",
kAutoChainBad: "已保存,但槽位无法解析(模型与源的组合不存在),AUTO 请求会失败",
kName: "名称",
kRole: "角色",
kRoleAdmin: "管理员",
@ -1103,7 +1103,7 @@
kAutoChainSave: "Save",
kAutoChainTier: "Tier",
kAutoChainEmpty: "This key uses the global AUTO chain",
kAutoChainBad: "Slot does not resolve (unknown model or source); AUTO requests would fail",
kAutoChainBad: "Saved, but the slot does not resolve (no source serves that model); AUTO requests will fail",
kName: "Name",
kRole: "Role",
kRoleAdmin: "Admin",
@ -3694,9 +3694,17 @@ const pnote = $("#period-note");
// different id inside its modal, so both share one implementation
// instead of a reduced second copy that would drift.
function sortCanvasEl() {
const own = document.getElementById("scr-canvas");
if (own) return own;
return document.getElementById("key-auto-canvas");
// The modal editor is mounted on top of the settings tab, so BOTH
// canvases can be in the DOM at once — #scr-canvas belongs to the
// hidden tab behind it. Resolve by scope, not by document order: when
// sortState.scope names a key we are editing THAT key's chain, and
// painting into the background tab's canvas both shows an empty
// dialog and corrupts the global chain the user comes back to.
if (sortState.scope) {
const k = document.getElementById("key-auto-canvas");
if (k) return k;
}
return document.getElementById("scr-canvas");
}
function paintSort(affected) {
const cv = sortCanvasEl();
@ -5283,10 +5291,12 @@ const cv = sortCanvasEl();
body: JSON.stringify({ auto: lanesToRules(sortState.lanes) }),
});
if (j.inherits) {
toast(t("kAutoChainGlobal"));
toast(t("kAutoChainGlobal"));
} else if (!j.slots) {
// Persisted, but nothing resolves — say so now instead of letting
// the user meet it as a 503 on their next AUTO request.
// Persisted, but nothing resolves. Still close: the write
// SUCCEEDED, and leaving the dialog open after a successful save
// makes the user click Save repeatedly. The warning carries the
// real problem — the toast survives the modal closing.
toast(t("kAutoChainBad"));
} else {
toast(t("sortSaved") + " (" + j.slots + ")");