gui: Codex's panel says when the Codex app's model menu can't reach every model: the app asks for 100 and never the next page, so with more the last ones can't be picked there; it points to Pick and Order (#1262)
magpie writes every model to Codex. ChatGPT.app 26.930 asks Codex's app-server for model/list with limit 100 and doesn't follow nextCursor; the Codex CLI's /model asks with no limit. Checked with the real codex 0.160.1 app-server in a sandbox CODEX_HOME holding 150 models: limit 100 gave 100 and nextCursor "100", no limit gave all 150. Run: codex-app-menu-cap.test.cjs (Chromium and WebKit, en and zh, 1000 and 560px; 8/8 fail on the old app.js), gui-ja, gui-de, gui-zh-tw, i18n-keys; full GUI suite 3708/3715, the failures (azure-endpoint, mobile-web, routing-kind) pass alone and touch nothing changed here. Not run: the real ChatGPT.app.
This commit is contained in:
1 parent
52ad2d9845
commit
7e2fc88260
4 files changed
+105
-1
No files matched your search
@@ -3187,6 +3187,10 @@ body.window .row + .row.agent.conn > .who { min-height: 41px; } /* the line abov
|
||||
:where(html[data-fonts]) .ag-code, :where(html[data-fonts]) .ag-diff code, :where(html[data-fonts]) .ag-path { font-style: var(--code-style, inherit); font-weight: var(--code-weight, inherit); font-stretch: var(--code-stretch, 100%); }
|
||||
.ag-code { padding: 1px 5px; border-radius: 4px; background: var(--pill); color: var(--fg); }
|
||||
.ag-warn { gap: 8px; font-size: 12px; }
|
||||
/* a note of several lines: its dot stays by the first line, the words wrap beside it */
|
||||
.ag-vl.ag-menu-cap { flex-wrap: nowrap; align-items: flex-start; line-height: 1.6; }
|
||||
.ag-menu-cap > .ag-dot { margin-top: calc(0.8em - 3px); }
|
||||
.ag-menu-cap > span:last-child { flex: 1; min-width: 0; }
|
||||
/* which copy is left on the old list: a sentence that wraps as one, under the warning's words */
|
||||
.ag-vl.ag-stale-copy { display: block; padding-left: 14px; font-size: 12px; line-height: 1.6; color: var(--fg-2); }
|
||||
.ag-hint { font-size: 12px; color: var(--muted); }
|
||||
|
||||
@@ -1344,7 +1344,14 @@ function connectPanel(a, { fields, fieldBtn }) {
|
||||
pick.onclick = (ev) => openAgentModels(a, pick, ev);
|
||||
holdAgentModels(a, pick);
|
||||
chips.append(pick);
|
||||
kv(t("Model list"), chips);
|
||||
const v = kv(t("Model list"), chips);
|
||||
// the Codex app's menu reaches only so many of them (#1262)
|
||||
if (a.id === "codex" && a.models.shown > CODEX_APP_MENU) {
|
||||
const w = line();
|
||||
w.classList.add("ag-warn", "ag-menu-cap");
|
||||
w.append(el("span", "ag-dot"), el("span", "", t("The Codex app's model menu shows only the first {max}: the last {n} models can't be picked there (the Codex CLI's /model lists them all). In Pick, turn off the ones you don't use, or drag the ones you use to the front under Order.", { max: CODEX_APP_MENU, n: a.models.shown - CODEX_APP_MENU })));
|
||||
v.append(w);
|
||||
}
|
||||
}
|
||||
// what a new session starts on: optional, the agent's own last pick
|
||||
// unset; Codex's is the very value its /model picks, so one choice
|
||||
@@ -2191,6 +2198,12 @@ function dragCards(e, handle, card, list, cards, commit, idle = () => {}) {
|
||||
|
||||
// ---------- an agent's model list ----------
|
||||
|
||||
// CODEX_APP_MENU is how many models the Codex app's model menu reaches: it
|
||||
// asks Codex's app-server for model/list with limit 100 and never for the
|
||||
// next page (ChatGPT.app 26.930), so a model past them can't be picked
|
||||
// there; the Codex CLI's /model asks for every one (#1262).
|
||||
const CODEX_APP_MENU = 100;
|
||||
|
||||
// modelsEntry: the line under an agent's name counting the models its
|
||||
// lists show ("All 41 models", or "Showing 5 / 32 models"); it opens the
|
||||
// list to take models out of them and put them back.
|
||||
|
||||
@@ -410,6 +410,7 @@ const I18N = {
|
||||
"Add a Claude subscription": "添加 Claude 订阅",
|
||||
"claude-* models need a Claude subscription in magpie": "claude-* 模型要先在 magpie 里添加 Claude 订阅",
|
||||
"Pick": "挑选",
|
||||
"The Codex app's model menu shows only the first {max}: the last {n} models can't be picked there (the Codex CLI's /model lists them all). In Pick, turn off the ones you don't use, or drag the ones you use to the front under Order.": "Codex App 的模型菜单只显示前 {max} 个:后面 {n} 个模型在那里选不到(Codex CLI 的 /model 能列出全部)。在「挑选」里关掉不用的,或在「排序」里把要用的拖到前面。",
|
||||
"Model list": "模型列表",
|
||||
"New sessions": "新会话默认",
|
||||
"The same choice as {agent}'s {cmd}: change it here or there": "与 {agent} 的 {cmd} 同步,两边均可修改",
|
||||
@@ -4148,6 +4149,7 @@ const I18N = {
|
||||
"Add a Claude subscription": "新增 Claude 訂閱",
|
||||
"claude-* models need a Claude subscription in magpie": "claude-* 模型要先在 magpie 裡新增 Claude 訂閱",
|
||||
"Pick": "挑選",
|
||||
"The Codex app's model menu shows only the first {max}: the last {n} models can't be picked there (the Codex CLI's /model lists them all). In Pick, turn off the ones you don't use, or drag the ones you use to the front under Order.": "Codex App 的模型選單只顯示前 {max} 個:後面 {n} 個模型在那裡選不到(Codex CLI 的 /model 能列出全部)。在「挑選」裡關掉不用的,或在「排序」裡把要用的拖到前面。",
|
||||
"Model list": "模型列表",
|
||||
"New sessions": "新會話預設",
|
||||
"The same choice as {agent}'s {cmd}: change it here or there": "與 {agent} 的 {cmd} 同步,兩邊均可修改",
|
||||
@@ -7851,6 +7853,7 @@ const I18N = {
|
||||
"Add a Claude subscription": "Claude サブスクリプションを追加",
|
||||
"claude-* models need a Claude subscription in magpie": "claude-* モデルには magpie の Claude サブスクリプションが必要です",
|
||||
"Pick": "選択",
|
||||
"The Codex app's model menu shows only the first {max}: the last {n} models can't be picked there (the Codex CLI's /model lists them all). In Pick, turn off the ones you don't use, or drag the ones you use to the front under Order.": "Codex アプリのモデルメニューには先頭の {max} 個しか表示されず、残りの {n} 個のモデルはそこで選べません(Codex CLI の /model にはすべて表示されます)。「選択」で使わないモデルをオフにするか、「並び順」で使うモデルを先頭へドラッグしてください。",
|
||||
"Model list": "モデル一覧",
|
||||
"New sessions": "新しいセッション",
|
||||
"The same choice as {agent}'s {cmd}: change it here or there": "{agent} の {cmd} と同じ選択です。ここでもそちらでも変更できます",
|
||||
@@ -11572,6 +11575,7 @@ const I18N = {
|
||||
"Add a Claude subscription": "Claude-Abo hinzufügen",
|
||||
"claude-* models need a Claude subscription in magpie": "claude-*-Modelle benötigen ein Claude-Abo in magpie",
|
||||
"Pick": "Wählen",
|
||||
"The Codex app's model menu shows only the first {max}: the last {n} models can't be picked there (the Codex CLI's /model lists them all). In Pick, turn off the ones you don't use, or drag the ones you use to the front under Order.": "Das Modellmenü der Codex-App zeigt nur die ersten {max}: Die letzten {n} Modelle lassen sich dort nicht wählen (/model in der Codex-CLI listet alle). Schalte unter „Wählen“ ungenutzte aus oder zieh unter „Reihenfolge“ die genutzten nach vorn.",
|
||||
"Model list": "Modellliste",
|
||||
"New sessions": "Neue Sitzungen",
|
||||
"The same choice as {agent}'s {cmd}: change it here or there": "Dieselbe Auswahl wie {cmd} von {agent}: hier oder dort ändern",
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Run with Node's test runner and Playwright on the module path; see README.md.
|
||||
// The Codex app's model menu reaches only the first 100 models Codex lists
|
||||
// (it asks model/list for 100 and never for the next page), so with more
|
||||
// than that the Codex row says how many it can't pick and where to choose
|
||||
// which ones it gets (#1262); with 100 or fewer it says nothing. In English
|
||||
// and Chinese, wide and narrow, with no line wider than the row.
|
||||
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");
|
||||
|
||||
function server(lang, shown) {
|
||||
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:${JSON.stringify(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({
|
||||
agents: [{
|
||||
id: "codex", name: "Codex", path: "/test/config.toml", icon: "codex-color", wired: true,
|
||||
fields: [{ key: "model", label: "model", value: "pqh/gpt-6-sol", options: [{ value: "pqh/gpt-6-sol", label: "6 Sol", ref: "pqh/gpt-6-sol" }] }],
|
||||
models: { shown, listed: shown + 3, by: [{ name: "Routing groups", n: 26 }, { name: "pqh", n: 19 }, { name: "ZCode", n: shown - 45 }] },
|
||||
}],
|
||||
profiles: [], settings: { lang, theme: "light" },
|
||||
});
|
||||
if (url.pathname === "/api/groups") return json({ groups: [] });
|
||||
if (url.pathname === "/api/providers") return json({ providers: [], gateway: { running: true, window: true } });
|
||||
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 said = {
|
||||
en: ["The Codex app's model menu shows only the first 100", "the last 107 models"],
|
||||
zh: ["Codex App 的模型菜单只显示前 100 个", "后面 107 个模型"],
|
||||
};
|
||||
|
||||
for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium", "webkit"])) {
|
||||
for (const lang of ["en", "zh"]) {
|
||||
for (const width of [1000, 560]) {
|
||||
test(`${engine} ${lang} ${width}px: the Codex row says what its app's menu can't reach`, async (t) => {
|
||||
const browser = await (engine === "webkit" ? webkit.launch() : chromium.launch({ channel: "chromium" }));
|
||||
t.after(() => browser.close());
|
||||
for (const [shown, warned] of [[207, true], [100, false]]) {
|
||||
const page = await browser.newPage({ viewport: { width, height: 800 } });
|
||||
page.setDefaultTimeout(5000);
|
||||
const errors = [];
|
||||
page.on("pageerror", (e) => errors.push(e.message));
|
||||
await page.route("**/*", server(lang, shown));
|
||||
await page.goto("http://magpie.test/");
|
||||
const row = page.locator('.row.agent[data-id="codex"]');
|
||||
await row.locator(".ag-link").click();
|
||||
await row.locator(".ag-exp .ag-chips").waitFor();
|
||||
const cap = row.locator(".ag-exp .ag-menu-cap");
|
||||
if (!warned) {
|
||||
assert.equal(await cap.count(), 0, `${shown} models`);
|
||||
} else {
|
||||
await cap.waitFor();
|
||||
const text = await cap.textContent();
|
||||
for (const s of said[lang]) assert(text.includes(s), text);
|
||||
// under the model list, in the panel, and no wider than it
|
||||
const fit = await cap.evaluate((w) => {
|
||||
const box = w.closest(".ag-exp").getBoundingClientRect(), b = w.getBoundingClientRect();
|
||||
return { inside: b.left >= box.left - 1 && b.right <= box.right + 1, list: !!w.closest(".ag-kv")?.querySelector(".ag-chips"), overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth };
|
||||
});
|
||||
assert.deepEqual(fit, { inside: true, list: true, overflow: 0 });
|
||||
if (process.env.ARTIFACT_DIR) {
|
||||
await fs.mkdir(process.env.ARTIFACT_DIR, { recursive: true });
|
||||
await row.screenshot({ path: path.join(process.env.ARTIFACT_DIR, `${engine}-${lang}-${width}-codex-cap.png`) });
|
||||
}
|
||||
}
|
||||
assert.deepEqual(errors, []);
|
||||
await page.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user