mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-10-05 23:17:24 +00:00
feat(ui): per-key AUTO 链默认复制全局链作为编辑起点
此前给没有独立链的密钥打开编辑器是空白画布,管理员要从 226 个模型里 一条条重新挑。真实任务是"这个用户拿全局链,但去掉他不该碰的两个源", 所以起点应该是全局链本身。 - 无独立链时读 /api/auto 的 rules 预填(实测 4 泳道 / 10 块,与全局链 一致),不是 discovery 兜底(那会把每个源的每个模型都堆进来)。 - 提示区分三种状态,因为它们在画布上长得一样、点取消的后果完全不同: 复制来的起点 / 该密钥已有的独立链 / 清空后保存将恢复跟随全局。 - seeded 标记随关闭清理,避免下次的提示说错来源。 CDP 实测:打开无独立链的密钥 → 画布 4 泳道 10 块 + 提示"已复制全局链 作为编辑起点";删一个块服务端仍 inherits:true;保存后 inherits:false n=9、弹窗关闭;重开读回 4 泳道 9 块且提示消失(它现在有自己的链了)。 横向滚动可达性一并验证:scrollLeft 172 = scrollWidth-clientWidth, 滚到底后无残留不可达块。 判据 2 条 + 变异(恢复空白起点 / 去掉 seeded 标记均判红)。 Co-Authored-By: ModelRouter <noreply@modelrouter.dev>
This commit is contained in:
@ -287,3 +287,45 @@ func TestUIStatsPeriodRangeHandlesHourlyBuckets(t *testing.T) {
|
||||
"this throws on the day view and leaves the range line empty")
|
||||
}
|
||||
}
|
||||
|
||||
// A key with no chain of its own must open on a COPY OF THE GLOBAL CHAIN, not
|
||||
// on an empty canvas. The realistic admin task is "this user gets the global chain
|
||||
// minus the sources they must not hit"; starting blank forces retyping every
|
||||
// tier against a 226-model picker. This is deliberately NOT the discovery
|
||||
// fallback (which dumps every model of every source) — it is the same rules
|
||||
// the global chain actually runs.
|
||||
func TestUIKeyAutoSeedsFromGlobalChain(t *testing.T) {
|
||||
src := uiSource(t)
|
||||
body := readFuncBody(t, src, "openKeyAutoModal")
|
||||
if !strings.Contains(body, `api("/api/auto")`) {
|
||||
t.Fatal("openKeyAutoModal must read the global chain to seed the editor")
|
||||
}
|
||||
if !strings.Contains(body, "seededFromGlobal") {
|
||||
t.Fatal("the editor must remember whether its content came from the " +
|
||||
"global chain, so the notice can say so")
|
||||
}
|
||||
// And it must not fall back to the discovery path that fills 226 models.
|
||||
if strings.Contains(body, `buildLanes(idx, [], `) {
|
||||
t.Fatal("seeding from an empty rule list triggers buildLanes discovery")
|
||||
}
|
||||
}
|
||||
|
||||
// The notice must distinguish "already this key's chain" from "a copy that
|
||||
// only becomes its chain on Save" — those two look identical on the canvas and
|
||||
// behave very differently if the admin hits Cancel.
|
||||
func TestUIKeyAutoNoticeDistinguishesSavedFromCopied(t *testing.T) {
|
||||
src := uiSource(t)
|
||||
body := readFuncBody(t, src, "keyAutoInheritNotice")
|
||||
for _, k := range []string{"kAutoChainSeeded", "kAutoChainNewOwn", "kAutoChainWillInherit"} {
|
||||
if !strings.Contains(body, k) {
|
||||
t.Fatalf("the notice never uses %s — a copied chain and a saved one "+
|
||||
"would look the same", k)
|
||||
}
|
||||
}
|
||||
// Both languages, or a non-Chinese admin sees raw keys.
|
||||
for _, k := range []string{"kAutoChainSeeded", "kAutoChainNewOwn", "kAutoChainWillInherit"} {
|
||||
if n := strings.Count(src, k+`: "`); n < 2 {
|
||||
t.Fatalf("i18n key %s defined %d time(s), want both zh and en", k, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user