gui: a provider's Fallback picker says how many more models match past the dozen it draws, and that typing narrows them (01huadalang on Discord: 这个备用模型的来源是哪里,怎么只有这么几个,是否可以选择供应商里的别的模型,例如 workbuddy 的模型) — focusing the picker drew the first 12 of every on model of every ready provider and nothing else, so the rest (a WorkBuddy model among them) looked missing. Past 12 it now ends with "{n} more — type to narrow" (还有 {n} 项 — 输入以筛选), the string the other pickers use. fallback-more.test.cjs is new (a provider beside one with 20 models: 12 chips and "8 more", a typed filter finds the 17th, the note gone; en and zh): it fails without the change and passes with it in WebKit; click-scroll passes.

This commit is contained in:
yetone committed 2026-10-03 01:40:30 +08:00
1 parent 3d8c6f24c5
commit d49d889cd7
2 files changed
+64

No files matched your search

+2
View File
@@ -6680,6 +6680,8 @@ function renderFallback(p) {
c.onclick = () => add(x.id);
sugg.append(c);
}
// only the first dozen are drawn: the rest are said to be there
if (hits.length > 12) sugg.append(el("span", "hint", t("{n} more — type to narrow", { n: hits.length - 12 })));
if (!hits.length && f) sugg.append(el("span", "hint", f.includes("/") ? t("Enter adds {id}", { id: q.value.trim() }) : t("No model matches")));
};
q.onfocus = () => { open = true; draw(); };
+62
View File
@@ -0,0 +1,62 @@
// Run with Node's test runner and Playwright on the module path; see README.md.
// A provider's Fallback picker draws the first dozen models that match; when
// more match, it says how many more there are and that typing narrows them
// (01huadalang on Discord: "怎么只有这么几个" — the rest looked missing), and a
// typed filter finds one past the dozen. In English and Chinese.
const assert = require("node:assert/strict");
const fs = require("node:fs/promises");
const path = require("node:path");
const { test } = require("node:test");
const { chromium, webkit } = require("playwright");
const assets = path.resolve(__dirname, "../assets");
const base = { chat: "", responses: "", anthropic: "", catalog: "", agents: [], fallback: [], headers: {}, keyList: [], key: { set: true }, ready: true };
const acme = { ...base, id: "acme", name: "Acme", icon: "generic", models: [{ id: "a1", name: "A1", on: true }] };
const wb = { ...base, id: "wb", name: "WorkBuddy", icon: "generic", models: Array.from({ length: 20 }, (_, i) => ({ id: "m" + (i + 1), name: "WB Model " + (i + 1), on: true })) };
function serve(lang) {
const state = { agents: [], profiles: [], settings: { lang, theme: "light" } };
return async (route) => {
const url = new URL(route.request().url());
const json = (data) => route.fulfill({ json: data });
if (url.pathname === "/boot.js") return route.fulfill({ contentType: "text/javascript", body: `window.bootPrefs = {lang:"${lang}",theme:"light",web:true};` });
if (url.pathname === "/wails/runtime.js") return route.fulfill({ contentType: "text/javascript", body: "export const Window = {};" });
if (url.pathname === "/api/state") return json(state);
if (url.pathname === "/api/providers") return json({ providers: [acme, wb], presets: [], excluded: [], gateway: { running: true, window: true } });
if (url.pathname === "/api/groups") return json({ groups: [] });
if (url.pathname === "/api/plugins") return json({ plugins: [] });
if (url.pathname.startsWith("/api/")) return json({});
const file = path.join(assets, url.pathname === "/" ? "index.html" : url.pathname);
const contentType = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".svg": "image/svg+xml", ".png": "image/png" }[path.extname(file)];
await route.fulfill({ body: await fs.readFile(file), contentType });
};
}
const words = { en: { more: "8 more — type to narrow" }, zh: { more: "还有 8 项 — 输入以筛选" } };
for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium", "webkit"])) {
for (const lang of ["en", "zh"]) {
test(`${engine} ${lang}: the Fallback picker says how many more models match`, async (t) => {
const browser = await (engine === "webkit" ? webkit.launch() : chromium.launch({ channel: "chromium" }));
t.after(() => browser.close());
const page = await (await browser.newContext({ viewport: { width: 900, height: 760 }, reducedMotion: "reduce" })).newPage();
page.setDefaultTimeout(5000);
const errors = [];
page.on("pageerror", (e) => errors.push(e.message));
await page.route("**/*", serve(lang));
await page.goto("http://magpie.test/?view=providers");
await page.locator(".row.provider", { hasText: "Acme" }).first().click();
const box = page.locator(".editor .fallback");
await box.locator("input").focus();
const chips = box.locator(".mchips .mchip");
await assert.doesNotReject(chips.nth(11).waitFor());
assert.equal(await chips.count(), 12);
assert.equal((await box.locator(".mchips .hint").textContent()).trim(), words[lang].more);
await box.locator("input").fill("model 17");
assert.equal(await chips.count(), 1);
assert.equal(await chips.first().getAttribute("title"), "wb/m17");
assert.equal(await box.locator(".mchips .hint").count(), 0);
assert.deepEqual(errors, []);
});
}
}