From 4be51a6aa29d1541cd6c89f7df7a817b8d4b1fb9 Mon Sep 17 00:00:00 2001
From: 0xTotoroX <191693694+0xTotoroX@users.noreply.github.com>
Date: Tue, 14 Jul 2026 15:41:53 +0800
Subject: [PATCH] feat: add Model Deck
---
index.json | 20 +-
scripts/model-deck.js | 1498 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 1517 insertions(+), 1 deletion(-)
create mode 100644 scripts/model-deck.js
diff --git a/index.json b/index.json
index 5fba5f4..ce8a931 100644
--- a/index.json
+++ b/index.json
@@ -1,6 +1,6 @@
{
"version": 1,
- "updated_at": "2026-07-06T08:45:38.039Z",
+ "updated_at": "2026-07-14T07:37:22.000Z",
"scripts": [
{
"id": "codex-context-used-meter",
@@ -34,6 +34,24 @@
"script_url": "https://raw.githubusercontent.com/BigPizzaV3/CodexPlusPlusScriptMarket/main/scripts/hide-usage-alert.js",
"sha256": "a120c224c9c7e1de25a3c2f0614fbd144842d75aa4569f9a40074da7795f74e5"
},
+ {
+ "id": "model-deck",
+ "name": "Model Deck",
+ "description": "悬停官方模型按钮打开模型矩阵,提供 Fast 切换和全局预设",
+ "version": "0.1.0",
+ "author": "0xTotoroX",
+ "tags": [
+ "codex",
+ "models",
+ "reasoning",
+ "fast",
+ "presets",
+ "ui"
+ ],
+ "homepage": "https://github.com/0xTotoroX/model-deck",
+ "script_url": "https://raw.githubusercontent.com/BigPizzaV3/CodexPlusPlusScriptMarket/main/scripts/model-deck.js",
+ "sha256": "6a085811d626bbe9ded8e73baf9fbaf719f30297e0ce58c35259a083a056ad2d"
+ },
{
"id": "codex-token-usage",
"name": "Codex Token Usage",
diff --git a/scripts/model-deck.js b/scripts/model-deck.js
new file mode 100644
index 0000000..24306a8
--- /dev/null
+++ b/scripts/model-deck.js
@@ -0,0 +1,1498 @@
+/* Model Deck v0.1.0 - Codex++ userscript */
+(() => {
+ // src/index.js
+ var API_KEY = "__codexPlusQuickModelPresets";
+ var VERSION = "0.1.0";
+ var STORAGE_KEY = "codexpp.quickModelPresets.v1";
+ var PREVIEW_ID = "codexpp-qmp-preset-preview";
+ var LOADING_MESSAGE = "\u7B49\u5F85\u5B98\u65B9\u6A21\u578B\u6570\u636E\uFF0C\u8BF7\u901A\u8FC7 Codex++ \u91CD\u542F ChatGPT";
+ var MENU_SELECTOR = '[role="menu"][data-state="open"], [role="listbox"], [data-radix-menu-content][data-state="open"]';
+ var TRIGGER_OWNER_SELECTOR = "[data-codex-composer-root],[data-codex-composer],footer,form";
+ var REASONING_LABELS = Object.freeze({
+ none: "None",
+ minimal: "Minimal",
+ low: "Low",
+ medium: "Medium",
+ high: "High",
+ xhigh: "Extra high",
+ max: "Max",
+ ultra: "Ultra"
+ });
+ var REASONING_RANK = new Map(
+ Object.keys(REASONING_LABELS).map((key, index) => [key, index])
+ );
+ var LEGACY_MODEL_KEYS = /* @__PURE__ */ new Set(["gpt-5.5", "gpt-5.4", "gpt-5.4-mini"]);
+ var REASONING_ALIASES = new Map(
+ Object.entries({
+ none: "none",
+ minimal: "minimal",
+ low: "low",
+ light: "low",
+ "\u8F7B\u5EA6": "low",
+ medium: "medium",
+ "\u4E2D": "medium",
+ high: "high",
+ "\u9AD8": "high",
+ xhigh: "xhigh",
+ "extra high": "xhigh",
+ "\u6781\u9AD8": "xhigh",
+ max: "max",
+ maximum: "max",
+ "\u6700\u5927": "max",
+ "\u6700\u9AD8": "max",
+ ultra: "ultra",
+ "\u8D85\u9AD8": "ultra"
+ })
+ );
+ var SECTION_PATTERNS = {
+ model: [/^model(?:\s|$)/i, /^模型(?:\s|$)/u],
+ reasoning: [/^reasoning(?: effort| level)?(?:\s|$)/i, /^推理强度(?:\s|$)/u],
+ speed: [/^speed(?:\s|$)/i, /^速度(?:\s|$)/u]
+ };
+ var inheritedModelList = null;
+ try {
+ inheritedModelList = window[API_KEY]?.getModelListCache?.() || null;
+ } catch {
+ }
+ window[API_KEY]?.destroy?.();
+ var documentRef = window.document;
+ var performanceRef = window.performance;
+ var lifetime = new window.AbortController();
+ var { signal } = lifetime;
+ var timers = /* @__PURE__ */ new Set();
+ var destroyed = false;
+ var root = null;
+ var preview = null;
+ var actionMenu = null;
+ var actionMenuAnchor = null;
+ var resizeObserver = null;
+ var mutationObserver = null;
+ var triggerLifetimes = /* @__PURE__ */ new Map();
+ var boundTrigger = null;
+ var openTimer = null;
+ var closeTimer = null;
+ var previewTimer = null;
+ var previewHideTimer = null;
+ var visible = false;
+ var disarmed = false;
+ var nativeSuspended = false;
+ var pointerOnTrigger = false;
+ var pointerOnPanel = false;
+ var holdOpen = false;
+ var probe = null;
+ var probeQueue = Promise.resolve();
+ var scanFrame = 0;
+ var operationSequence = 0;
+ var pendingIntent = null;
+ var pumping = false;
+ var activeOperation = null;
+ var activeIntentTrigger = null;
+ var pendingTarget = null;
+ var interaction = "idle";
+ var message = LOADING_MESSAGE;
+ var snapshotGeneration = 0;
+ var snapshot = unavailableSnapshot("loading", LOADING_MESSAGE);
+ var modelListCache = Array.isArray(inheritedModelList) ? inheritedModelList : [];
+ var inheritedModels = projectModels(modelListCache);
+ var capabilityGeneration = inheritedModels.length ? 1 : 0;
+ var capabilityFingerprint = inheritedModels.length ? JSON.stringify(inheritedModels) : "";
+ var capability = inheritedModels.length ? { status: "ready", generation: 1, models: inheritedModels } : { status: "loading", generation: 0, models: [] };
+ var store = readStore();
+ var renameId = null;
+ var finishRename = null;
+ var drag = null;
+ var presetClickSuppression = null;
+ var presetClickSuppressionTimer = null;
+ var legacyExpanded = false;
+ var autoExpandedLegacyKey = null;
+ function unavailableSnapshot(status, text) {
+ return {
+ status,
+ generation: snapshotGeneration,
+ current: null,
+ models: [],
+ reasoningOrder: [],
+ message: text
+ };
+ }
+ function later(fn, delay) {
+ const timer = window.setTimeout(() => {
+ timers.delete(timer);
+ fn();
+ }, delay);
+ timers.add(timer);
+ return timer;
+ }
+ function clearLater(timer) {
+ if (timer == null) return;
+ window.clearTimeout(timer);
+ timers.delete(timer);
+ }
+ function clearPresetClickSuppression() {
+ clearLater(presetClickSuppressionTimer);
+ presetClickSuppressionTimer = null;
+ presetClickSuppression = null;
+ }
+ function armPresetClickSuppression(id, taskBound = false, pointerId = null) {
+ clearPresetClickSuppression();
+ const token = { id, pointerId };
+ presetClickSuppression = token;
+ if (!taskBound) return;
+ const timer = later(() => {
+ if (presetClickSuppressionTimer === timer) presetClickSuppressionTimer = null;
+ if (presetClickSuppression === token) presetClickSuppression = null;
+ }, 0);
+ presetClickSuppressionTimer = timer;
+ }
+ function abortError() {
+ return new window.DOMException("Aborted", "AbortError");
+ }
+ function checkAbort(localSignal) {
+ if (destroyed || signal.aborted || localSignal?.aborted) throw abortError();
+ }
+ function wait(delay, localSignal) {
+ checkAbort(localSignal);
+ return new Promise((resolve, reject) => {
+ const finish = () => {
+ signal.removeEventListener("abort", cancel);
+ localSignal?.removeEventListener("abort", cancel);
+ resolve();
+ };
+ const cancel = () => {
+ clearLater(timer);
+ signal.removeEventListener("abort", cancel);
+ localSignal?.removeEventListener("abort", cancel);
+ reject(abortError());
+ };
+ const timer = later(finish, delay);
+ signal.addEventListener("abort", cancel, { once: true });
+ localSignal?.addEventListener("abort", cancel, { once: true });
+ });
+ }
+ async function waitFor(read, localSignal, timeout = 1500) {
+ const started = performanceRef.now();
+ while (performanceRef.now() - started < timeout) {
+ checkAbort(localSignal);
+ const value = read();
+ if (value) return value;
+ await wait(50, localSignal);
+ }
+ throw new Error("\u5B98\u65B9\u83DC\u5355\u72B6\u6001\u53D8\u66F4\u8D85\u65F6");
+ }
+ function canonicalReasoning(value) {
+ const raw = String(value ?? "").trim();
+ return REASONING_ALIASES.get(raw.toLowerCase()) || raw;
+ }
+ function reasoningOption(value) {
+ const raw = typeof value === "string" ? value : value?.reasoningEffort;
+ const key = canonicalReasoning(raw);
+ if (!key) return null;
+ const officialLabel = typeof value === "object" && value ? value.displayName || value.label || value.name : null;
+ return { key, label: REASONING_LABELS[key] || String(officialLabel || raw), enabled: true };
+ }
+ function tierSupportsFast(tier) {
+ if (typeof tier === "string") return ["priority", "fast"].includes(tier.toLowerCase());
+ const id = String(tier?.id ?? tier?.key ?? tier?.value ?? tier?.tier ?? "").toLowerCase();
+ const name = String(tier?.name ?? tier?.displayName ?? tier?.label ?? "").toLowerCase();
+ return id === "priority" || name === "fast";
+ }
+ function projectModels(records) {
+ if (!Array.isArray(records)) return [];
+ const used = /* @__PURE__ */ new Set();
+ const models = [];
+ records.forEach((record) => {
+ if (!record || !Array.isArray(record.supportedReasoningEfforts)) return;
+ if (!Array.isArray(record.serviceTiers)) return;
+ const key = String(record.model ?? "").trim();
+ const label = String(record.displayName ?? "").trim();
+ if (!key || !label || used.has(key)) return;
+ const reasoningOptions = record.supportedReasoningEfforts.map(reasoningOption).filter(Boolean);
+ if (!reasoningOptions.length || new Set(reasoningOptions.map((item) => item.key)).size !== reasoningOptions.length) return;
+ used.add(key);
+ models.push({
+ key,
+ label,
+ reasoningOptions,
+ defaultReasoningEffort: canonicalReasoning(record.defaultReasoningEffort),
+ fastSupported: record.serviceTiers.some(tierSupportsFast)
+ });
+ });
+ return models;
+ }
+ var requestIds = /* @__PURE__ */ new Set();
+ var originalDispatch = window.dispatchEvent;
+ function wrappedDispatch(event) {
+ try {
+ const detail = event?.detail;
+ const request = detail?.request;
+ if (event?.type === "codex-message-from-view" && detail?.type === "mcp-request" && request?.method === "model/list" && request?.id != null) {
+ requestIds.add(String(request.id));
+ }
+ } catch {
+ }
+ return originalDispatch.call(this, event);
+ }
+ window.dispatchEvent = wrappedDispatch;
+ function onBridgeMessage(event) {
+ try {
+ const data = event?.data;
+ if (data?.type !== "mcp-response") return;
+ const rpc = data.message ?? data.response;
+ const matchingId = String(rpc?.id ?? "");
+ if (!matchingId || !requestIds.has(matchingId)) return;
+ const records = rpc?.result?.data;
+ const models = projectModels(records);
+ requestIds.delete(matchingId);
+ if (!models.length) return;
+ modelListCache = JSON.parse(JSON.stringify(records));
+ const fingerprint = JSON.stringify(models);
+ if (fingerprint === capabilityFingerprint) return;
+ capabilityFingerprint = fingerprint;
+ capability = { status: "ready", generation: ++capabilityGeneration, models };
+ scheduleScan();
+ if (visible) void syncSnapshot();
+ } catch {
+ }
+ }
+ window.addEventListener("message", onBridgeMessage, { capture: true, signal });
+ function emptyStore() {
+ return { schemaVersion: 1, revision: 0, nextSequence: 1, updatedAt: 0, presets: [] };
+ }
+ function nextPresetName(presets) {
+ const occupied = /* @__PURE__ */ new Set();
+ presets.forEach((preset) => {
+ const name = String(preset?.name ?? "");
+ if (!/^\d+$/.test(name)) return;
+ const value = Number(name);
+ if (Number.isSafeInteger(value) && value > 0) occupied.add(value);
+ });
+ let candidate = 1;
+ while (occupied.has(candidate)) candidate += 1;
+ return String(candidate);
+ }
+ function uuid() {
+ return window.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(36).slice(2)}`;
+ }
+ function normalizePreset(value, index) {
+ if (!value || typeof value !== "object") return null;
+ const modelKey = String(value.modelKey ?? "").trim();
+ const reasoningKey = String(value.reasoningKey ?? "").trim();
+ if (!modelKey || !reasoningKey || typeof value.fastEnabled !== "boolean") return null;
+ const now = Date.now();
+ return {
+ id: String(value.id || uuid()),
+ name: String(value.name || index + 1).trim() || String(index + 1),
+ modelKey,
+ modelLabel: String(value.modelLabel || modelKey),
+ reasoningKey,
+ reasoningLabel: String(value.reasoningLabel || reasoningKey),
+ fastEnabled: value.fastEnabled,
+ createdAt: Number(value.createdAt) || now,
+ updatedAt: Number(value.updatedAt) || now
+ };
+ }
+ function readStore() {
+ try {
+ const parsed = JSON.parse(window.localStorage.getItem(STORAGE_KEY) || "null");
+ const source = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.presets) ? parsed.presets : [];
+ const presets = source.map(normalizePreset).filter(Boolean);
+ const largest = presets.reduce((max, item) => /^\d+$/.test(item.name) ? Math.max(max, Number(item.name)) : max, 0);
+ return {
+ schemaVersion: 1,
+ revision: Number(parsed?.revision) || 0,
+ nextSequence: Math.max(Number(parsed?.nextSequence) || 1, largest + 1),
+ updatedAt: Number(parsed?.updatedAt) || 0,
+ presets
+ };
+ } catch {
+ return emptyStore();
+ }
+ }
+ function updateStore(change) {
+ const next = { ...store, presets: store.presets.map((item) => ({ ...item })) };
+ change(next);
+ next.revision += 1;
+ next.updatedAt = Date.now();
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
+ store = next;
+ render();
+ }
+ function accessibleText(element) {
+ return String(element?.getAttribute?.("aria-label") || element?.getAttribute?.("title") || element?.textContent || "").replace(/\s+/g, " ").trim();
+ }
+ function isVisible(element, requireBox = true) {
+ if (!(element instanceof window.HTMLElement) || !element.isConnected) return false;
+ const style = window.getComputedStyle(element);
+ if (style.display === "none" || style.visibility === "hidden") return false;
+ if (!requireBox) return true;
+ const rect = element.getBoundingClientRect();
+ return rect.width > 0 && rect.height > 0;
+ }
+ function normalizeModelLabel(label) {
+ return String(label || "").trim().toLowerCase().replace(/^gpt[-\s]*/i, "").replace(/[_-]+/g, " ").replace(/\s+/g, " ");
+ }
+ function isAutoReviewModel(model) {
+ return model.key.toLowerCase() === "codex-auto-review" || normalizeModelLabel(model.label) === "codex auto review";
+ }
+ function isLegacyModel(model) {
+ return LEGACY_MODEL_KEYS.has(model.key.toLowerCase());
+ }
+ function compactModelLabel(label) {
+ return String(label || "").replace(/^GPT[-\s]*/i, "").replace(/-/g, " ").replace(/\b(sol|terra|luna|mini)\b/gi, (word) => word[0].toUpperCase() + word.slice(1).toLowerCase());
+ }
+ function modelLike(text) {
+ return /\b(?:gpt[-\s]*)?\d+(?:\.\d+)+(?:[-\s]+[a-z][\w.-]*)?/i.test(text);
+ }
+ function triggerScore(element) {
+ if (element.closest("[data-codexpp-qmp-root]") || !isVisible(element)) return -Infinity;
+ const text = accessibleText(element);
+ if (/codex\+\+|microphone|send|stop|attachment|麦克风|发送|停止|附件/i.test(text)) return -Infinity;
+ let score = element.getAttribute("aria-haspopup") === "menu" ? 100 : 0;
+ if (capability.models.some((model) => normalizeModelLabel(text).includes(normalizeModelLabel(model.label)))) score += 60;
+ else if (modelLike(text)) score += 45;
+ if ([...REASONING_ALIASES.keys()].some((label) => label && text.toLowerCase().includes(label))) score += 25;
+ if (element.closest("[data-codex-composer-root],[data-codex-composer],footer")) score += 20;
+ if (element.querySelector("svg")) score += 10;
+ return score;
+ }
+ function findTriggers() {
+ const candidates = [...documentRef.querySelectorAll('button[aria-haspopup="menu"],[role="button"][aria-haspopup="menu"]')].map((element) => ({ element, score: triggerScore(element) })).filter((item) => item.score >= 140);
+ const groups = /* @__PURE__ */ new Map();
+ const unowned = [];
+ candidates.forEach((candidate) => {
+ const owner = candidate.element.closest(TRIGGER_OWNER_SELECTOR);
+ if (!owner) {
+ unowned.push(candidate);
+ return;
+ }
+ if (!groups.has(owner)) groups.set(owner, []);
+ groups.get(owner).push(candidate);
+ });
+ const winners = [...groups.values()].map(uniqueTrigger).filter(Boolean);
+ if (winners.length) return winners;
+ const fallback = uniqueTrigger(unowned);
+ return fallback ? [fallback] : [];
+ }
+ function uniqueTrigger(candidates) {
+ const ranked = [...candidates].sort((left, right) => right.score - left.score);
+ if (!ranked.length || ranked[0].score === ranked[1]?.score) return null;
+ return ranked[0].element;
+ }
+ function isOwnedUi(element) {
+ return Boolean(element?.matches?.("[data-codexpp-qmp-root],[data-codexpp-qmp-preview],[data-codexpp-qmp-actions]") || element?.closest?.("[data-codexpp-qmp-root],[data-codexpp-qmp-preview],[data-codexpp-qmp-actions]"));
+ }
+ function openMenus() {
+ return [...documentRef.querySelectorAll(MENU_SELECTOR)].filter((menu) => isVisible(menu, false) && !isOwnedUi(menu));
+ }
+ function claimProbeRoots() {
+ if (!probe) return [];
+ openMenus().forEach((menu) => {
+ if (!probe.before.has(menu)) {
+ menu.setAttribute("data-codexpp-qmp-probe-root", "true");
+ probe.roots.add(menu);
+ }
+ });
+ return [...probe.roots].filter((menu) => menu.isConnected);
+ }
+ function nativeMenuOpen() {
+ return openMenus().some((menu) => menu.getAttribute("data-codexpp-qmp-probe-root") !== "true");
+ }
+ function yieldProbeToUser() {
+ if (!probe) return;
+ probe.roots.forEach((menu) => menu.removeAttribute("data-codexpp-qmp-probe-root"));
+ probe = null;
+ nativeTakeover();
+ }
+ function sectionRow(rootElement, section) {
+ return [...rootElement.querySelectorAll('[role="menuitem"][aria-haspopup="menu"]')].find((item) => {
+ const text = accessibleText(item);
+ return SECTION_PATTERNS[section].some((pattern) => pattern.test(text));
+ });
+ }
+ function menuItems(menu) {
+ return [...menu.querySelectorAll('[role="menuitem"],[role="option"]')].filter(
+ (item) => item.getAttribute("aria-disabled") !== "true" && item.getAttribute("aria-haspopup") !== "menu"
+ );
+ }
+ function itemLabel(item) {
+ const aria = item.getAttribute("aria-label");
+ if (aria) return aria.replace(/\s+/g, " ").trim();
+ const leaves = [...item.querySelectorAll("span")].filter((span) => !span.querySelector("span") && span.textContent.trim()).map((span) => span.textContent.replace(/\s+/g, " ").trim());
+ return leaves[0] || accessibleText(item);
+ }
+ function dispatchPointerActivation(element) {
+ const pointer = {
+ bubbles: true,
+ cancelable: true,
+ composed: true,
+ button: 0,
+ pointerType: "mouse",
+ isPrimary: true
+ };
+ element.dispatchEvent(new window.PointerEvent("pointerdown", { ...pointer, buttons: 1 }));
+ element.dispatchEvent(new window.PointerEvent("pointerup", { ...pointer, buttons: 0 }));
+ element.dispatchEvent(new window.MouseEvent("click", {
+ bubbles: true,
+ cancelable: true,
+ composed: true,
+ button: 0,
+ buttons: 0,
+ detail: 1
+ }));
+ }
+ async function closeProbe(localSignal) {
+ const current = probe;
+ if (!current) return;
+ try {
+ const stillOpen = () => openMenus().some((menu) => current.roots.has(menu));
+ if (stillOpen() && current.trigger?.isConnected) {
+ dispatchPointerActivation(current.trigger);
+ await waitFor(() => !stillOpen(), localSignal, 250).catch(() => {
+ });
+ }
+ checkAbort(localSignal);
+ if (stillOpen() && documentRef.body) {
+ dispatchPointerActivation(documentRef.body);
+ await waitFor(() => !stillOpen(), localSignal, 750).catch(() => {
+ });
+ }
+ } finally {
+ current.roots.forEach((menu) => menu.removeAttribute("data-codexpp-qmp-probe-root"));
+ if (probe === current) probe = null;
+ }
+ }
+ async function openMainMenu(localSignal, trigger) {
+ checkAbort(localSignal);
+ if (!trigger?.isConnected) throw new Error("\u672A\u627E\u5230\u5B98\u65B9\u6A21\u578B\u6309\u94AE");
+ if (nativeMenuOpen()) throw new Error("\u5B98\u65B9\u83DC\u5355\u6B63\u5728\u4F7F\u7528\u4E2D");
+ probe = { trigger, before: new Set(openMenus()), roots: /* @__PURE__ */ new Set() };
+ dispatchPointerActivation(trigger);
+ return waitFor(() => {
+ const roots = claimProbeRoots();
+ return roots.find((menu) => sectionRow(menu, "model"));
+ }, localSignal);
+ }
+ async function openSubmenu(row, main, localSignal) {
+ const before = new Set(openMenus());
+ const controlledId = row.getAttribute("aria-controls");
+ checkAbort(localSignal);
+ dispatchPointerActivation(row);
+ return waitFor(() => {
+ claimProbeRoots();
+ const menus = openMenus().filter((menu) => menu !== main && menuItems(menu).length);
+ const expectedId = row.getAttribute("aria-controls") || controlledId;
+ if (expectedId) return menus.find((menu) => menu.id === expectedId) || null;
+ return menus.find((menu) => !before.has(menu));
+ }, localSignal);
+ }
+ function rowValue(row, section) {
+ let value = accessibleText(row);
+ const prefixes = {
+ model: /^(?:model|模型)\s*/iu,
+ reasoning: /^(?:reasoning(?: effort| level)?|推理强度)\s*/iu,
+ speed: /^(?:speed|速度)\s*/iu
+ };
+ value = value.replace(prefixes[section], "").trim();
+ return value;
+ }
+ function matchModel(models, label) {
+ const wanted = normalizeModelLabel(label);
+ const matches = models.filter(
+ (model) => normalizeModelLabel(model.label) === wanted || normalizeModelLabel(compactModelLabel(model.label)) === wanted || normalizeModelLabel(model.key) === wanted
+ );
+ return matches.length === 1 ? matches[0] : null;
+ }
+ function reasoningFromRow(rowText, model) {
+ if (rowText.trim() === "\u6781\u9AD8" && model.reasoningOptions.filter((option) => ["xhigh", "max", "ultra"].includes(option.key)).length > 1) return null;
+ const key = canonicalReasoning(rowText);
+ const matches = model.reasoningOptions.filter(
+ (option) => option.key === key || option.label.toLowerCase() === rowText.toLowerCase()
+ );
+ return matches.length === 1 ? matches[0] : null;
+ }
+ function orderedReasoning(models) {
+ const seen = /* @__PURE__ */ new Set();
+ const order = [];
+ models.forEach((model) => model.reasoningOptions.forEach((option) => {
+ if (!seen.has(option.key)) {
+ seen.add(option.key);
+ order.push(option.key);
+ }
+ }));
+ return order.sort((left, right) => (REASONING_RANK.get(left) ?? Number.MAX_SAFE_INTEGER) - (REASONING_RANK.get(right) ?? Number.MAX_SAFE_INTEGER));
+ }
+ function queuedProbe(work) {
+ const run = probeQueue.then(work, work);
+ probeQueue = run.catch(() => {
+ });
+ return run;
+ }
+ async function readOfficialSnapshot(localSignal, trigger) {
+ checkAbort(localSignal);
+ if (capability.status !== "ready") return unavailableSnapshot("loading", LOADING_MESSAGE);
+ let main = null;
+ try {
+ main = await openMainMenu(localSignal, trigger);
+ const modelRow = sectionRow(main, "model");
+ const reasoningRow = sectionRow(main, "reasoning");
+ const speedRow = sectionRow(main, "speed");
+ if (!modelRow || !reasoningRow) throw new Error("\u5B98\u65B9\u6A21\u578B\u83DC\u5355\u7ED3\u6784\u4E0D\u53EF\u7528");
+ const modelText = rowValue(modelRow, "model");
+ const reasoningText = rowValue(reasoningRow, "reasoning");
+ const speedText = speedRow ? rowValue(speedRow, "speed") : "";
+ const modelMenu = await openSubmenu(modelRow, main, localSignal);
+ const visibleLabels = menuItems(modelMenu).map(itemLabel).filter(Boolean);
+ const visibleModels = capability.models.filter(
+ (model) => visibleLabels.some((label) => matchModel([model], label))
+ );
+ const currentModel = matchModel(visibleModels, modelText);
+ if (!visibleModels.length || !currentModel) throw new Error("\u5B98\u65B9\u6A21\u578B\u8EAB\u4EFD\u65E0\u6CD5\u7CBE\u786E\u5339\u914D");
+ const selectableModels = visibleModels.filter((model) => !isAutoReviewModel(model));
+ const triggerReasoning = trigger?.getAttribute("data-selected-reasoning-effort");
+ const triggerMatches = triggerReasoning == null ? [] : currentModel.reasoningOptions.filter(
+ (option) => option.key === canonicalReasoning(triggerReasoning)
+ );
+ const reasoning = triggerReasoning == null ? reasoningFromRow(reasoningText, currentModel) : triggerMatches.length === 1 ? triggerMatches[0] : null;
+ if (!reasoning) throw new Error("\u5B98\u65B9\u63A8\u7406\u5F3A\u5EA6\u65E0\u6CD5\u7CBE\u786E\u5339\u914D");
+ let fastEnabled = false;
+ if (speedRow) {
+ if (/^(?:fast|快速)(?:\s|$)/i.test(speedText)) fastEnabled = true;
+ else if (!/^(?:standard|标准)(?:\s|$)/i.test(speedText)) throw new Error("\u5B98\u65B9\u901F\u5EA6\u72B6\u6001\u4E0D\u53EF\u7528");
+ } else if (currentModel.fastSupported) {
+ throw new Error("\u5B98\u65B9\u901F\u5EA6\u83DC\u5355\u4E0D\u53EF\u7528");
+ }
+ return {
+ status: "ready",
+ generation: ++snapshotGeneration,
+ current: {
+ modelKey: currentModel.key,
+ modelLabel: compactModelLabel(currentModel.label),
+ reasoningKey: reasoning.key,
+ reasoningLabel: reasoning.label,
+ fastEnabled
+ },
+ models: selectableModels.map((model) => ({
+ ...model,
+ label: compactModelLabel(model.label),
+ reasoningOptions: model.reasoningOptions.map((option) => ({ ...option }))
+ })),
+ reasoningOrder: orderedReasoning(selectableModels),
+ message: ""
+ };
+ } catch (error) {
+ if (error?.name === "AbortError") throw error;
+ return unavailableSnapshot("unavailable", String(error?.message || error));
+ } finally {
+ await closeProbe(localSignal).catch(() => {
+ });
+ }
+ }
+ async function syncSnapshot(options = {}) {
+ if (!boundTrigger?.isConnected) bindTriggers(findTriggers());
+ const trigger = options.trigger || boundTrigger;
+ const result = await queuedProbe(() => readOfficialSnapshot(options.signal, trigger));
+ if (!destroyed && trigger === boundTrigger) {
+ snapshot = result;
+ if (result.status === "ready") {
+ const currentModel = result.models.find((model) => model.key === result.current.modelKey);
+ if (currentModel && isLegacyModel(currentModel)) {
+ if (autoExpandedLegacyKey !== currentModel.key) {
+ legacyExpanded = true;
+ autoExpandedLegacyKey = currentModel.key;
+ }
+ } else if (autoExpandedLegacyKey) {
+ legacyExpanded = false;
+ autoExpandedLegacyKey = null;
+ }
+ }
+ message = result.message;
+ render();
+ }
+ return result;
+ }
+ async function openMainMenuWhen(localSignal, trigger, matches, failureMessage) {
+ const started = performanceRef.now();
+ while (performanceRef.now() - started < 1500) {
+ try {
+ const main = await openMainMenu(localSignal, trigger);
+ if (matches(main)) return main;
+ } catch (error) {
+ if (error?.name === "AbortError") throw error;
+ }
+ await closeProbe(localSignal).catch(() => {
+ });
+ await wait(50, localSignal);
+ }
+ throw new Error(failureMessage);
+ }
+ function openMainMenuForModel(model, localSignal, trigger) {
+ return openMainMenuWhen(localSignal, trigger, (main) => {
+ const row = sectionRow(main, "model");
+ return row && matchModel([model], rowValue(row, "model"));
+ }, "\u5B98\u65B9\u6A21\u578B\u5207\u6362\u672A\u751F\u6548");
+ }
+ async function selectCombination(target, localSignal, trigger) {
+ return queuedProbe(async () => {
+ const model = capability.models.find((item) => item.key === target.modelKey);
+ if (!model) throw new Error("\u76EE\u6807\u6A21\u578B\u4E0D\u53EF\u7528");
+ let main = null;
+ try {
+ main = await openMainMenu(localSignal, trigger);
+ const modelRow = sectionRow(main, "model");
+ const modelMenu = await openSubmenu(modelRow, main, localSignal);
+ const modelItem = menuItems(modelMenu).find((item) => matchModel([model], itemLabel(item)));
+ if (!modelItem) throw new Error("\u5B98\u65B9\u6A21\u578B\u9009\u9879\u4E0D\u53EF\u7528");
+ checkAbort(localSignal);
+ dispatchPointerActivation(modelItem);
+ } finally {
+ await closeProbe(localSignal).catch(() => {
+ });
+ }
+ checkAbort(localSignal);
+ try {
+ main = await openMainMenuForModel(model, localSignal, trigger);
+ const row = sectionRow(main, "reasoning");
+ const menu = await openSubmenu(row, main, localSignal);
+ const index = model.reasoningOptions.findIndex((item2) => item2.key === target.reasoningKey);
+ const items = menuItems(menu);
+ if (items.length !== model.reasoningOptions.length) throw new Error("\u5B98\u65B9\u63A8\u7406\u83DC\u5355\u4E0E\u6A21\u578B\u80FD\u529B\u4E0D\u4E00\u81F4");
+ const item = items[index];
+ if (index < 0 || !item) throw new Error("\u5B98\u65B9\u63A8\u7406\u9009\u9879\u4E0D\u53EF\u7528");
+ checkAbort(localSignal);
+ dispatchPointerActivation(item);
+ await waitFor(
+ () => canonicalReasoning(trigger?.getAttribute("data-selected-reasoning-effort")) === target.reasoningKey,
+ localSignal
+ );
+ } finally {
+ await closeProbe(localSignal).catch(() => {
+ });
+ }
+ });
+ }
+ async function selectFast(enabled, localSignal, trigger) {
+ return queuedProbe(async () => {
+ let main = null;
+ const pattern = enabled ? /^(?:fast|快速)(?:\s|$)/i : /^(?:standard|标准)(?:\s|$)/i;
+ try {
+ main = await openMainMenu(localSignal, trigger);
+ const row = sectionRow(main, "speed");
+ if (!row) throw new Error("\u5F53\u524D\u6A21\u578B\u4E0D\u652F\u6301 Fast");
+ const menu = await openSubmenu(row, main, localSignal);
+ const item = menuItems(menu).find((entry) => pattern.test(itemLabel(entry)));
+ if (!item) throw new Error("\u5B98\u65B9\u901F\u5EA6\u9009\u9879\u4E0D\u53EF\u7528");
+ checkAbort(localSignal);
+ dispatchPointerActivation(item);
+ } finally {
+ await closeProbe(localSignal).catch(() => {
+ });
+ }
+ checkAbort(localSignal);
+ try {
+ await openMainMenuWhen(localSignal, trigger, (rootElement) => {
+ const row = sectionRow(rootElement, "speed");
+ return row && pattern.test(rowValue(row, "speed"));
+ }, "\u5B98\u65B9\u901F\u5EA6\u5207\u6362\u672A\u751F\u6548");
+ } finally {
+ await closeProbe(localSignal).catch(() => {
+ });
+ }
+ });
+ }
+ function configValid(target, source = snapshot) {
+ if (source.status !== "ready") return false;
+ const model = source.models.find((item) => item.key === target.modelKey);
+ return Boolean(
+ model && model.reasoningOptions.some((item) => item.key === target.reasoningKey) && (!target.fastEnabled || model.fastSupported)
+ );
+ }
+ function sameConfig(a, b) {
+ return Boolean(a && b && a.modelKey === b.modelKey && a.reasoningKey === b.reasoningKey && a.fastEnabled === b.fastEnabled);
+ }
+ function submitTarget(target) {
+ if (!configValid(target)) {
+ message = "\u6B64\u914D\u7F6E\u5DF2\u4E0D\u53EF\u7528";
+ render();
+ return;
+ }
+ const operationId = ++operationSequence;
+ const targetModel = snapshot.models.find((model) => model.key === target.modelKey);
+ if (targetModel && isLegacyModel(targetModel)) legacyExpanded = true;
+ pendingIntent = { operationId, target: { ...target }, trigger: boundTrigger };
+ pendingTarget = { ...target };
+ activeOperation?.abort();
+ void pumpIntents();
+ }
+ async function applyIntent(intent, localSignal) {
+ let current = await syncSnapshot({ signal: localSignal, trigger: intent.trigger });
+ if (!configValid(intent.target, current)) throw new Error("\u76EE\u6807\u914D\u7F6E\u5DF2\u4E0D\u53EF\u7528");
+ if (sameConfig(current.current, intent.target)) return current;
+ if (current.current.modelKey !== intent.target.modelKey || current.current.reasoningKey !== intent.target.reasoningKey) {
+ await selectCombination(intent.target, localSignal, intent.trigger);
+ current = await syncSnapshot({ signal: localSignal, trigger: intent.trigger });
+ if (current.current?.modelKey !== intent.target.modelKey || current.current?.reasoningKey !== intent.target.reasoningKey) {
+ throw new Error("\u6A21\u578B\u6216\u63A8\u7406\u5F3A\u5EA6\u672A\u751F\u6548");
+ }
+ }
+ checkAbort(localSignal);
+ if (current.current.fastEnabled !== intent.target.fastEnabled) {
+ await selectFast(intent.target.fastEnabled, localSignal, intent.trigger);
+ current = await syncSnapshot({ signal: localSignal, trigger: intent.trigger });
+ }
+ if (!sameConfig(current.current, intent.target)) throw new Error("\u5B98\u65B9\u914D\u7F6E\u6821\u9A8C\u5931\u8D25");
+ return current;
+ }
+ async function pumpIntents() {
+ if (pumping) return;
+ pumping = true;
+ try {
+ while (pendingIntent && !destroyed) {
+ const intent = pendingIntent;
+ pendingIntent = null;
+ const controller = new window.AbortController();
+ activeOperation = controller;
+ activeIntentTrigger = intent.trigger;
+ interaction = "applying";
+ message = "\u6B63\u5728\u5E94\u7528...";
+ render();
+ try {
+ await applyIntent(intent, controller.signal);
+ if (intent.operationId === operationSequence) message = "";
+ } catch (error) {
+ if (error?.name !== "AbortError" && intent.operationId === operationSequence) {
+ const failureMessage = String(error?.message || error);
+ await syncSnapshot({ trigger: intent.trigger }).catch(() => {
+ });
+ message = failureMessage;
+ render();
+ }
+ } finally {
+ if (activeOperation === controller) {
+ activeOperation = null;
+ activeIntentTrigger = null;
+ }
+ if (intent.operationId === operationSequence) {
+ pendingTarget = null;
+ interaction = "idle";
+ render();
+ }
+ }
+ }
+ } finally {
+ pumping = false;
+ if (pendingIntent) void pumpIntents();
+ }
+ }
+ function cancelIntents() {
+ operationSequence += 1;
+ pendingIntent = null;
+ pendingTarget = null;
+ activeOperation?.abort();
+ activeIntentTrigger = null;
+ interaction = "idle";
+ }
+ function escapeHtml(value) {
+ return String(value ?? "").replace(/[&<>"']/g, (char) => ({
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'"
+ })[char]);
+ }
+ function icon(name, size = 16) {
+ const paths = {
+ zap: '