minecodex 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +149 -0
- package/apps/dashboard/DESIGN.md +45 -0
- package/features/images/DESIGN.md +71 -0
- package/features/images/README.md +2 -0
- package/features/images/codex-feature.json +2 -1
- package/features/images/src/adapters/codex/cloud-import.mjs +52 -0
- package/features/images/src/adapters/codex/image-prompt.mjs +22 -0
- package/features/images/src/adapters/codex/library-api.mjs +55 -0
- package/features/images/src/adapters/codex/library-viewer.mjs +18 -0
- package/features/images/src/adapters/codex/prompt-resolver-source.mjs +8 -0
- package/features/images/src/backend/cloud-library.mjs +10 -0
- package/features/images/src/domain/library-prompt.mjs +7 -0
- package/features/images/src/library-prompt-page.mjs +7 -75
- package/features/images/src/library-prompt-source.mjs +10 -0
- package/features/images/web/app.js +9 -9
- package/features/images/web/styles.css +219 -219
- package/features/model-slider/DESIGN.md +34 -0
- package/features/model-slider/src/adapters/codex/controller.mjs +93 -0
- package/features/model-slider/src/domain/model-rules.mjs +143 -0
- package/features/model-slider/src/frontend/install.mjs +2110 -0
- package/features/model-slider/src/injection-source.mjs +10 -0
- package/features/notes/DESIGN.md +13 -15
- package/features/notes/README.md +7 -1
- package/features/notes/assets/icons/book-open-nav-fill.svg +1 -0
- package/features/notes/codex-feature.json +5 -1
- package/features/notes/src/{default-prompts.mjs → backend/default-prompts.mjs} +1 -1
- package/features/notes/src/{product.mjs → backend/product.mjs} +1 -1
- package/features/notes/src/{quick-prompts-server.mjs → backend/quick-prompts-server.mjs} +2 -2
- package/features/notes/src/{quick-prompts-page.mjs → frontend/entry.mjs} +64 -257
- package/features/notes/src/frontend/icons.json +13 -0
- package/features/notes/src/frontend/styles.css +110 -0
- package/features/notes/src/main.mjs +2 -2
- package/features/notes/vendor/phosphor-LICENSE +21 -0
- package/features/session-status/DESIGN.md +7 -7
- package/features/session-status/README.md +4 -0
- package/features/session-status/codex-feature.json +1 -1
- package/features/session-status/src/adapters/codex/context-usage.mjs +39 -0
- package/features/session-status/src/adapters/codex/session-log.mjs +81 -0
- package/features/session-status/src/adapters/codex/session-reader.mjs +106 -0
- package/features/session-status/src/domain/session-stats.mjs +141 -0
- package/features/session-status/src/injection-source.mjs +6 -0
- package/features/session-status/src/session-status.mjs +41 -101
- package/features/sidebar-focus/DESIGN.md +6 -5
- package/features/sidebar-focus/codex-feature.json +2 -1
- package/features/sidebar-focus/src/adapters/codex/renderer.mjs +28 -0
- package/features/sidebar-focus/src/injection-source.mjs +6 -0
- package/features/sidebar-focus/src/sidebar-focus.mjs +19 -66
- package/features/sites/DESIGN.md +60 -0
- package/features/sites/README.md +27 -0
- package/features/sites/codex-feature.json +28 -0
- package/features/sites/src/adapters/codex/catalog.mjs +26 -0
- package/features/sites/src/adapters/codex/renderer.mjs +83 -0
- package/features/sites/src/backend/discovery.mjs +73 -0
- package/features/sites/src/backend/project-sites.mjs +34 -0
- package/features/sites/src/backend/site-launcher.mjs +54 -0
- package/features/sites/src/backend/sites-preview.mjs +53 -0
- package/features/sites/src/backend/sites-service.mjs +162 -0
- package/features/sites/src/domain/site.mjs +40 -0
- package/features/sites/src/frontend/entry.mjs +383 -0
- package/features/sites/src/frontend/icons.json +12 -0
- package/features/sites/src/frontend/styles.css +72 -0
- package/features/sites/src/injection-source.mjs +7 -0
- package/features/sites/vendor/lucide-LICENSE +35 -0
- package/features/unread-inbox/DESIGN.md +6 -2
- package/features/unread-inbox/codex-feature.json +2 -1
- package/features/unread-inbox/src/adapters/codex/renderer.mjs +66 -0
- package/features/unread-inbox/src/injection-source.mjs +6 -0
- package/features/unread-inbox/src/unread-inbox.mjs +34 -89
- package/features/usage-meter/DESIGN.md +9 -9
- package/features/usage-meter/README.md +2 -1
- package/features/usage-meter/codex-feature.json +3 -3
- package/features/usage-meter/src/adapters/codex/renderer.mjs +61 -0
- package/features/usage-meter/src/injection-source.mjs +6 -0
- package/features/usage-meter/src/usage-meter.mjs +27 -66
- package/package.json +17 -4
- package/packages/cli/src/config.mjs +2 -1
- package/packages/codex-adapter/README.md +15 -0
- package/packages/codex-adapter/package.json +1 -0
- package/packages/codex-adapter/src/renderer/environment.mjs +53 -0
- package/packages/runtime-host/README.md +27 -0
- package/packages/runtime-host/src/codex-injection.mjs +32 -2393
- package/packages/runtime-host/src/codex-runtime.mjs +21 -3
- package/packages/runtime-host/src/feature-registry.mjs +28 -3
- package/packages/ui/DESIGN.md +31 -0
- package/packages/ui/package.json +8 -0
- package/packages/ui/src/components/button.mjs +21 -0
- package/packages/ui/src/components/composer-menu.mjs +18 -0
- package/packages/ui/src/components/dialog.mjs +55 -0
- package/packages/ui/src/components/navigation-rail.mjs +77 -0
- package/packages/ui/src/components/page.mjs +8 -0
- package/packages/ui/src/components/tooltip.mjs +96 -0
- package/packages/ui/src/foundation/layers.mjs +14 -0
- package/packages/ui/src/foundation/surfaces.mjs +34 -0
- package/packages/ui/src/install.mjs +22 -0
- package/packages/ui/src/native/navigation-rail.mjs +33 -0
- package/packages/ui/src/native/theme.mjs +35 -0
- package/packages/{runtime-host/src/codex-design-contract.mjs → ui/src/native/tokens.mjs} +67 -24
- package/packages/runtime-host/src/cloud-images.mjs +0 -123
- package/packages/runtime-host/src/feature-tooltips.mjs +0 -65
- package/packages/runtime-host/src/session-status-reader.mjs +0 -340
- /package/features/notes/src/{prompt-schema.mjs → backend/prompt-schema.mjs} +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { createCodexEnvironment } from "../../codex-adapter/src/renderer/environment.mjs";
|
|
1
2
|
// 注入源生成:createInjectionSource 产出注入到 Codex Renderer 的自包含脚本字符串。
|
|
2
3
|
// 该字符串经 CDP 注入后独立执行,因此 install 函数体必须自包含;
|
|
3
4
|
// CodexRuntime 与 CDP 客户端在 codex-runtime.mjs / codex-cdp.mjs。
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
+
import { createModelSliderInstallerSource } from "../../../features/model-slider/src/injection-source.mjs";
|
|
7
|
+
import { createUIInstallerSource } from "../../ui/src/install.mjs";
|
|
6
8
|
import { randomBytes } from "node:crypto";
|
|
7
|
-
import { CODEX_THEME_TOKENS, CODEX_THEME_TOKEN_ALIASES } from "
|
|
9
|
+
import { CODEX_THEME_TOKENS, CODEX_THEME_TOKEN_ALIASES, MINECODEX_THEME_BINDINGS } from "../../ui/src/native/tokens.mjs";
|
|
8
10
|
|
|
9
|
-
export const RUNTIME_VERSION =
|
|
11
|
+
export const RUNTIME_VERSION = 107;
|
|
10
12
|
export const HOST_BINDING_NAME = "__codexPersonalHostAction";
|
|
11
13
|
export const CSP_BOOTSTRAP_VERSION = 1;
|
|
12
14
|
export const CSP_BOOTSTRAP_KEY = "__mineCodexCspBootstrapVersion";
|
|
@@ -120,14 +122,14 @@ export function createInjectionSource(features, {
|
|
|
120
122
|
bindingToken = "test-binding-token",
|
|
121
123
|
runtimeSessionId = randomBytes(16).toString("hex"),
|
|
122
124
|
} = {}) {
|
|
123
|
-
function install(config,
|
|
125
|
+
function install(config, installUI, installModelSliderFeature, createCodexEnvironment) {
|
|
124
126
|
if (window.top !== window) return;
|
|
125
127
|
if (!document.documentElement) {
|
|
126
128
|
// The document bootstrap runs before the root element exists. Defer the
|
|
127
129
|
// whole install until the DOM is ready instead of crashing on a null
|
|
128
130
|
// documentElement (which previously left the runtime permanently absent
|
|
129
131
|
// after a renderer reload).
|
|
130
|
-
document.addEventListener("DOMContentLoaded", () => install(config,
|
|
132
|
+
document.addEventListener("DOMContentLoaded", () => install(config, installUI, installModelSliderFeature, createCodexEnvironment), { once: true });
|
|
131
133
|
return;
|
|
132
134
|
}
|
|
133
135
|
const currentRuntime = window.__codexPersonalRuntime;
|
|
@@ -139,7 +141,8 @@ export function createInjectionSource(features, {
|
|
|
139
141
|
currentRuntime?.dispose?.();
|
|
140
142
|
|
|
141
143
|
const lifetime = new AbortController();
|
|
142
|
-
|
|
144
|
+
const codex = createCodexEnvironment();
|
|
145
|
+
const featureUI = installUI(lifetime.signal, {codex,themeTokens:config.themeTokens,themeTokenAliases:config.themeTokenAliases,themeBindings:config.themeBindings});
|
|
143
146
|
const rightPanelRouteKinds = new Set(config.rightPanelRouteKinds);
|
|
144
147
|
|
|
145
148
|
const entryMarker = "data-codex-personal-feature";
|
|
@@ -159,8 +162,6 @@ export function createInjectionSource(features, {
|
|
|
159
162
|
sidePanel: ["Toggle side panel", "切换侧边面板"],
|
|
160
163
|
temporaryChat: ["temporary chat", "临时聊天", "临时对话"],
|
|
161
164
|
};
|
|
162
|
-
const themeTokenNames = config.themeTokens;
|
|
163
|
-
const themeTokenAliases = config.themeTokenAliases;
|
|
164
165
|
|
|
165
166
|
function normalizedLocale(value) {
|
|
166
167
|
return String(value ?? "").toLowerCase().startsWith("zh") ? "zh-CN" : "en";
|
|
@@ -240,141 +241,9 @@ export function createInjectionSource(features, {
|
|
|
240
241
|
let nativeTabCapabilityPromise = null;
|
|
241
242
|
let nativeTabCapability = null;
|
|
242
243
|
let nativeTabCapabilityError = null;
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const modelSelectorPositionObservers = new Map();
|
|
247
|
-
const modelSelectorMenuSizeFrames = new Map();
|
|
248
|
-
let modelSelectorReturnTimer = null;
|
|
249
|
-
let modelSelectorPendingReturn = false;
|
|
250
|
-
let modelSelectorEffortTransactionTimer = null;
|
|
251
|
-
let modelSelectorPendingOutsideDismiss = false;
|
|
252
|
-
let modelSelectorSuppressOutsidePointerSequence = false;
|
|
253
|
-
let modelSelectorTriggerObserver = null;
|
|
254
|
-
let modelSelectorObservedTrigger = null;
|
|
255
|
-
let modelSelectorControllerMenu = null;
|
|
256
|
-
let modelSelectorNativeFastIcons = null;
|
|
257
|
-
// 持久化脏标记:仅在图标实际变化时写一次 localStorage。
|
|
258
|
-
let modelSelectorNativeFastIconsDirty = false;
|
|
259
|
-
const modelSelectorFastIconStorageKey = "codex-model-slider:fast-icons:v1";
|
|
260
|
-
const modelSelectorStyle = document.createElement("style");
|
|
261
|
-
modelSelectorStyle.setAttribute("data-codex-model-slider-style", "");
|
|
262
|
-
modelSelectorStyle.textContent = `
|
|
263
|
-
[data-codex-model-slider-menu] { width: 264px !important; min-width: 264px; overflow-x: hidden; }
|
|
264
|
-
[data-codex-model-slider-controller-menu] {
|
|
265
|
-
position: absolute !important;
|
|
266
|
-
left: 50%;
|
|
267
|
-
bottom: 0;
|
|
268
|
-
transform: translateX(-50%) !important;
|
|
269
|
-
z-index: 10;
|
|
270
|
-
padding: 6px;
|
|
271
|
-
background-color: var(--color-surface-elevated-secondary);
|
|
272
|
-
-webkit-backdrop-filter: none;
|
|
273
|
-
backdrop-filter: none;
|
|
274
|
-
}
|
|
275
|
-
[data-codex-model-slider-menu][data-codex-model-slider-overflow] {
|
|
276
|
-
max-height: var(--codex-model-slider-menu-max-height) !important;
|
|
277
|
-
overflow-y: auto;
|
|
278
|
-
}
|
|
279
|
-
[data-codex-model-slider-trigger] [class*="_ModelPickerTriggerModelLabel_"] {
|
|
280
|
-
min-width: 0; max-width: 110px; flex-shrink: 1; overflow: hidden; text-overflow: ellipsis;
|
|
281
|
-
}
|
|
282
|
-
[data-codex-model-slider-trigger] [class*="_ModelPickerTriggerModelText_"] {
|
|
283
|
-
min-width: 0; max-width: 110px; overflow: hidden; text-overflow: ellipsis;
|
|
284
|
-
}
|
|
285
|
-
html[data-codex-model-slider-catalog-ready]
|
|
286
|
-
[data-codex-model-slider-trigger]
|
|
287
|
-
[class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label]) {
|
|
288
|
-
display: none !important;
|
|
289
|
-
}
|
|
290
|
-
[data-codex-model-slider-trigger-label] {
|
|
291
|
-
display: inline-block; min-width: 0; max-width: 110px;
|
|
292
|
-
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
293
|
-
}
|
|
294
|
-
/* 展示真实模型名/思考强度时:芯片 HUG 内容,长名称不被截断;
|
|
295
|
-
仅 "Select model / Select effort" 占位态沿用原生宽度。 */
|
|
296
|
-
[data-codex-model-slider-trigger]:not([data-codex-model-slider-trigger-placeholder]) [data-codex-model-slider-trigger-label] {
|
|
297
|
-
max-width: none; overflow: visible; text-overflow: clip; flex-shrink: 0;
|
|
298
|
-
}
|
|
299
|
-
[data-codex-model-slider-trigger]:not([data-codex-model-slider-trigger-placeholder]) [class*="_ModelPickerTriggerModelLabel_"] {
|
|
300
|
-
max-width: none; overflow: visible; flex-shrink: 0;
|
|
301
|
-
}
|
|
302
|
-
[data-codex-model-slider-trigger]:not([data-codex-model-slider-trigger-placeholder]) [class*="_ModelPickerTriggerContent_"] {
|
|
303
|
-
max-width: none !important;
|
|
304
|
-
}
|
|
305
|
-
[data-codex-model-slider-trigger][data-codex-model-slider-trigger-placeholder] [class*="_ModelPickerTriggerContent_"] {
|
|
306
|
-
width: 160px; min-width: 160px !important; max-width: 160px;
|
|
307
|
-
}
|
|
308
|
-
[data-codex-model-slider-trigger] [class*="_ModelPickerTriggerEffortLabel_"] {
|
|
309
|
-
display: inline-flex; align-items: center; align-self: center;
|
|
310
|
-
}
|
|
311
|
-
html[data-codex-model-slider-selecting-effort] [role="menu"][data-state="open"]:not(:has([data-reasoning-slider])) {
|
|
312
|
-
visibility: hidden !important; opacity: 0 !important; animation: none !important; pointer-events: none !important;
|
|
313
|
-
}
|
|
314
|
-
[data-codex-model-slider-row] { display: flex; width: 100%; min-width: 0; align-items: center; gap: 10px; }
|
|
315
|
-
[data-codex-model-slider-brand] { display: grid; width: 18px; height: 18px; flex: 0 0 18px; place-items: center; }
|
|
316
|
-
[data-codex-model-slider-brand] svg { width: 16px; height: 16px; display: block; }
|
|
317
|
-
[data-codex-model-slider-copy] { display: flex; min-width: 0; flex: 1; flex-direction: column; gap: 1px; }
|
|
318
|
-
[data-codex-model-slider-name] { overflow: hidden; color: var(--color-text-foreground); text-overflow: ellipsis; white-space: nowrap; }
|
|
319
|
-
[data-codex-model-slider-provider] { overflow: hidden; color: var(--color-text-foreground-tertiary); font-size: 11px; line-height: 14px; text-overflow: ellipsis; white-space: nowrap; }
|
|
320
|
-
[data-codex-model-slider-item] { position: relative; }
|
|
321
|
-
[data-codex-model-slider-item][data-codex-model-slider-selected] { background: var(--color-token-list-hover-background); }
|
|
322
|
-
[data-codex-model-slider-star] {
|
|
323
|
-
display: grid; width: 24px; height: 24px; flex: 0 0 24px; place-items: center;
|
|
324
|
-
border: 0; border-radius: var(--radius-md, 6px); padding: 0; background: transparent;
|
|
325
|
-
color: var(--color-text-foreground-tertiary); cursor: pointer; opacity: 0; pointer-events: none;
|
|
326
|
-
}
|
|
327
|
-
[data-codex-model-slider-item]:hover [data-codex-model-slider-star],
|
|
328
|
-
[data-codex-model-slider-item]:focus-within [data-codex-model-slider-star] { opacity: 1; pointer-events: auto; }
|
|
329
|
-
[data-codex-model-slider-star]:hover, [data-codex-model-slider-star]:focus-visible {
|
|
330
|
-
background: var(--color-token-list-hover-background); color: var(--color-text-foreground); outline: none;
|
|
331
|
-
}
|
|
332
|
-
[data-codex-model-slider-star]::after {
|
|
333
|
-
content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 40px; z-index: 1;
|
|
334
|
-
}
|
|
335
|
-
[data-codex-model-slider-star] svg { position: relative; z-index: 2; width: 16px; height: 16px; pointer-events: none; }
|
|
336
|
-
[data-codex-model-slider-divider] {
|
|
337
|
-
height: 0; border-top: 1px solid var(--color-token-border, var(--color-border));
|
|
338
|
-
margin: 6px 8px;
|
|
339
|
-
}
|
|
340
|
-
[data-codex-model-slider-generic-menu] {
|
|
341
|
-
overflow-x: hidden;
|
|
342
|
-
transform: translateX(var(--codex-model-slider-menu-center-shift, 0px)) !important;
|
|
343
|
-
}
|
|
344
|
-
[data-codex-model-slider-generic] { box-sizing: border-box; width: 100%; }
|
|
345
|
-
[data-codex-model-slider-fast] { border: 0; background: transparent; cursor: pointer; }
|
|
346
|
-
[data-codex-model-slider-fast]:not(:hover) [data-codex-model-slider-fast-content] { background: transparent; }
|
|
347
|
-
[data-codex-model-slider-fast]:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 1px; }
|
|
348
|
-
[data-codex-model-slider-rail][data-codex-model-slider-fallback] { position: relative; height: 38px; margin: 1px 8px 0; outline: none; cursor: pointer; }
|
|
349
|
-
[data-codex-model-slider-track] { position: absolute; top: 12px; right: 0; left: 0; height: 16px; border-radius: 999px; background: var(--color-background-control); }
|
|
350
|
-
[data-codex-model-slider-range] { position: absolute; top: 0; bottom: 0; left: 0; width: var(--codex-model-slider-progress); border-radius: inherit; background: var(--color-text-accent); }
|
|
351
|
-
[data-codex-model-slider-rail][data-max="true"] [data-codex-model-slider-range] { background: var(--color-chart-purple); }
|
|
352
|
-
[data-codex-model-slider-fallback] [data-codex-model-slider-tick] {
|
|
353
|
-
position: absolute; top: 16px; width: 6px; height: 6px; transform: translate(-50%, -50%); border: 0; border-radius: 50%;
|
|
354
|
-
padding: 0; background: var(--color-text-foreground-tertiary); pointer-events: none;
|
|
355
|
-
}
|
|
356
|
-
[data-codex-model-slider-fallback] [data-codex-model-slider-tick][data-selected="true"] { background: var(--color-text-on-accent); }
|
|
357
|
-
[data-codex-model-slider-thumb] {
|
|
358
|
-
position: absolute; top: 7px; left: var(--codex-model-slider-progress); width: 26px; height: 26px;
|
|
359
|
-
transform: translateX(-50%); border: 0.5px solid var(--color-token-border, var(--color-border)); border-radius: 50%;
|
|
360
|
-
background: var(--color-background-elevated-primary-opaque, var(--color-background-panel)); box-shadow: var(--shadow-md);
|
|
361
|
-
pointer-events: none;
|
|
362
|
-
}
|
|
363
|
-
[data-codex-model-slider-fallback]:focus-visible [data-codex-model-slider-thumb] { box-shadow: 0 0 0 2px var(--color-border-focus); }
|
|
364
|
-
@media (prefers-reduced-motion: reduce) {
|
|
365
|
-
[data-codex-model-slider-star] { transition: none; }
|
|
366
|
-
}
|
|
367
|
-
`;
|
|
368
|
-
|
|
369
|
-
if (modelSelectorFeature) {
|
|
370
|
-
// 启动阶段就解析 Fast 图标:先读持久化的原生克隆,再从 app bundle 静态提取兜底,
|
|
371
|
-
// 避免首次会话必须等用户点开弹窗后 DOM 里才出现图标。
|
|
372
|
-
const persistedIcons = loadPersistedNativeFastIcons(modelSelectorFastIconStorageKey);
|
|
373
|
-
if (persistedIcons) {
|
|
374
|
-
modelSelectorNativeFastIcons = persistedIcons;
|
|
375
|
-
}
|
|
376
|
-
void loadNativeFastIconFromBundle();
|
|
377
|
-
}
|
|
244
|
+
const modelSlider = installModelSliderFeature(lifetime.signal, {
|
|
245
|
+
feature: modelSelectorFeature, codex, ui: featureUI, createIcon: createMarkupIcon, requestRefresh: queueEnsure, locale,
|
|
246
|
+
});
|
|
378
247
|
|
|
379
248
|
// ========================================================================
|
|
380
249
|
// page-script 特征安装 — 执行各 feature 声明的页面脚本并登记清理
|
|
@@ -393,6 +262,8 @@ export function createInjectionSource(features, {
|
|
|
393
262
|
`"use strict";\n${source}`,
|
|
394
263
|
);
|
|
395
264
|
const pageConfig = feature.pageScript.config ??= {};
|
|
265
|
+
pageConfig.ui = featureUI;
|
|
266
|
+
pageConfig.codex = codex;
|
|
396
267
|
pageConfig.requestHostAction = (action, payload) => requestPageHostAction(feature.id, action, payload);
|
|
397
268
|
const cleanup = installer(
|
|
398
269
|
lifetime.signal,
|
|
@@ -664,7 +535,7 @@ export function createInjectionSource(features, {
|
|
|
664
535
|
}
|
|
665
536
|
|
|
666
537
|
// ========================================================================
|
|
667
|
-
//
|
|
538
|
+
// 通用图标工厂 — feature 入口与插件适配共享的 markup 图标
|
|
668
539
|
// ========================================================================
|
|
669
540
|
|
|
670
541
|
function createMarkupIcon(iconDefinition, className, size = 16) {
|
|
@@ -688,2207 +559,7 @@ export function createInjectionSource(features, {
|
|
|
688
559
|
return createMarkupIcon(feature.icon, className, size);
|
|
689
560
|
}
|
|
690
561
|
|
|
691
|
-
function createModelSelectorIcon(definition, { brand = false } = {}) {
|
|
692
|
-
const icon = createMarkupIcon(definition, "", 16);
|
|
693
|
-
if (!icon) return null;
|
|
694
|
-
if (brand) {
|
|
695
|
-
icon.setAttribute("fill", "currentColor");
|
|
696
|
-
icon.removeAttribute("stroke");
|
|
697
|
-
icon.removeAttribute("stroke-width");
|
|
698
|
-
icon.removeAttribute("stroke-linecap");
|
|
699
|
-
icon.removeAttribute("stroke-linejoin");
|
|
700
|
-
}
|
|
701
|
-
return icon;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
function nativeCssModuleClass(tokenName, root = document) {
|
|
705
|
-
const pattern = new RegExp(`^_${tokenName}_[A-Za-z0-9_-]+$`);
|
|
706
|
-
const live = Array.from(root.querySelectorAll(`[class*="_${tokenName}_"]`))
|
|
707
|
-
.flatMap((element) => Array.from(element.classList))
|
|
708
|
-
.find((name) => pattern.test(name));
|
|
709
|
-
if (live) return live;
|
|
710
|
-
const selectorPattern = new RegExp(`\\.(_${tokenName}_[A-Za-z0-9_-]+)`);
|
|
711
|
-
const find = (rules) => {
|
|
712
|
-
for (const rule of rules ?? []) {
|
|
713
|
-
const match = String(rule.selectorText ?? "").match(selectorPattern);
|
|
714
|
-
if (match?.[1]) return match[1];
|
|
715
|
-
try {
|
|
716
|
-
const nested = find(rule.cssRules);
|
|
717
|
-
if (nested) return nested;
|
|
718
|
-
} catch {
|
|
719
|
-
// Ignore inaccessible nested rules and continue with Codex's readable sheets.
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
return null;
|
|
723
|
-
};
|
|
724
|
-
for (const sheet of document.styleSheets) {
|
|
725
|
-
try {
|
|
726
|
-
const match = find(sheet.cssRules);
|
|
727
|
-
if (match) return match;
|
|
728
|
-
} catch {
|
|
729
|
-
// Ignore cross-origin stylesheets.
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
function persistNativeFastIcons() {
|
|
736
|
-
if (!modelSelectorNativeFastIcons || !modelSelectorNativeFastIconsDirty) return;
|
|
737
|
-
try {
|
|
738
|
-
const payload = {};
|
|
739
|
-
for (const [state, template] of Object.entries(modelSelectorNativeFastIcons)) {
|
|
740
|
-
payload[state] = template.outerHTML;
|
|
741
|
-
}
|
|
742
|
-
localStorage.setItem(modelSelectorFastIconStorageKey, JSON.stringify(payload));
|
|
743
|
-
modelSelectorNativeFastIconsDirty = false;
|
|
744
|
-
} catch {
|
|
745
|
-
// localStorage 不可用时静默降级,仅本次会话内生效。
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
function loadPersistedNativeFastIcons(storageKey) {
|
|
750
|
-
let stored;
|
|
751
|
-
try {
|
|
752
|
-
stored = localStorage.getItem(storageKey);
|
|
753
|
-
} catch {
|
|
754
|
-
return null;
|
|
755
|
-
}
|
|
756
|
-
let payload;
|
|
757
|
-
try {
|
|
758
|
-
payload = JSON.parse(stored ?? "null");
|
|
759
|
-
} catch {
|
|
760
|
-
return null;
|
|
761
|
-
}
|
|
762
|
-
if (!payload || typeof payload !== "object") return null;
|
|
763
|
-
const icons = {};
|
|
764
|
-
for (const [state, markup] of Object.entries(payload)) {
|
|
765
|
-
if (typeof markup !== "string") continue;
|
|
766
|
-
const container = document.createElement("template");
|
|
767
|
-
container.innerHTML = markup;
|
|
768
|
-
const svg = container.content.querySelector("svg");
|
|
769
|
-
if (svg) icons[state] = svg;
|
|
770
|
-
}
|
|
771
|
-
return Object.keys(icons).length ? icons : null;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
function extractFastIconPath(source, prefix) {
|
|
775
|
-
const match = String(source).match(new RegExp(`d:\`(${prefix}[^\`]*)\``));
|
|
776
|
-
return match?.[1] ?? null;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
async function loadNativeFastIconFromBundle() {
|
|
780
|
-
// 两个图标均已就绪(持久化或 DOM 克隆)时不再拉取整包 app-initial 资源。
|
|
781
|
-
if (modelSelectorNativeFastIcons?.active && modelSelectorNativeFastIcons?.inactive) return;
|
|
782
|
-
// Fast bolt 图标定义在新版随 app-primary 入口包加载(app-initial 里没有),
|
|
783
|
-
// 按 app-primary -> app-initial 顺序取第一个已加载的入口包。
|
|
784
|
-
const sourceLink = document.querySelector('link[href*="/assets/app-primary-"][href$=".js"]')
|
|
785
|
-
?? document.querySelector('link[href*="/assets/app-initial-"][href$=".js"]');
|
|
786
|
-
if (!sourceLink?.href) return;
|
|
787
|
-
try {
|
|
788
|
-
const source = await fetch(sourceLink.href).then((response) => response.text());
|
|
789
|
-
if (!modelSelectorNativeFastIcons) {
|
|
790
|
-
modelSelectorNativeFastIcons = {};
|
|
791
|
-
}
|
|
792
|
-
let gained = false;
|
|
793
|
-
const define = (state, prefix, definition) => {
|
|
794
|
-
if (modelSelectorNativeFastIcons[state]) return;
|
|
795
|
-
const path = extractFastIconPath(source, prefix);
|
|
796
|
-
if (!path) return;
|
|
797
|
-
const svg = createModelSelectorIcon({
|
|
798
|
-
viewBox: definition.viewBox,
|
|
799
|
-
markup: definition.transform
|
|
800
|
-
? `<g transform="${definition.transform}"><path d="${path}" fill="currentColor" /></g>`
|
|
801
|
-
: `<path d="${path}" fill="currentColor" />`,
|
|
802
|
-
}, { brand: true });
|
|
803
|
-
if (!svg) return;
|
|
804
|
-
svg.setAttribute("width", "20");
|
|
805
|
-
svg.setAttribute("height", "20");
|
|
806
|
-
modelSelectorNativeFastIcons[state] = svg;
|
|
807
|
-
gained = true;
|
|
808
|
-
};
|
|
809
|
-
define("active", "M11\\.9125 21\\.4125", { viewBox: "0 0 24 24" });
|
|
810
|
-
define("inactive", "M9\\.80999 17\\.8302", { viewBox: "0 0 20 20" });
|
|
811
|
-
if (gained) {
|
|
812
|
-
modelSelectorNativeFastIconsDirty = true;
|
|
813
|
-
persistNativeFastIcons();
|
|
814
|
-
applyNativeFastIconsToShells();
|
|
815
|
-
queueEnsure();
|
|
816
|
-
}
|
|
817
|
-
} catch {
|
|
818
|
-
// 私有资源移动时保持原生控件不变,等待 DOM 克隆兜底。
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
562
|
|
|
822
|
-
function captureNativeFastIcon() {
|
|
823
|
-
// 原生控件每次只渲染当前状态;状态切换后按控件本身更新缓存。
|
|
824
|
-
const nativeContent = Array.from(document.querySelectorAll('[class*="_FastModeToggleContent_"]'))
|
|
825
|
-
.find((content) => !content.closest("[data-codex-model-slider-generic]"));
|
|
826
|
-
const toggle = nativeContent?.closest('[class*="_FastModeToggle_"]');
|
|
827
|
-
const state = toggle?.getAttribute("data-fast-mode-enabled") === "true" ? "active" : "inactive";
|
|
828
|
-
const svg = nativeContent?.querySelector("svg");
|
|
829
|
-
const current = modelSelectorNativeFastIcons?.[state];
|
|
830
|
-
if (svg && (current?.innerHTML !== svg.innerHTML
|
|
831
|
-
|| current.getAttribute("viewBox") !== svg.getAttribute("viewBox")
|
|
832
|
-
|| current.getAttribute("width") !== svg.getAttribute("width"))) {
|
|
833
|
-
const template = svg.cloneNode(true);
|
|
834
|
-
template.removeAttribute("id");
|
|
835
|
-
if (!modelSelectorNativeFastIcons) {
|
|
836
|
-
modelSelectorNativeFastIcons = {};
|
|
837
|
-
}
|
|
838
|
-
modelSelectorNativeFastIcons[state] = template;
|
|
839
|
-
modelSelectorNativeFastIconsDirty = true;
|
|
840
|
-
}
|
|
841
|
-
const ready = Boolean(
|
|
842
|
-
modelSelectorNativeFastIcons?.active && modelSelectorNativeFastIcons?.inactive,
|
|
843
|
-
);
|
|
844
|
-
if (ready) {
|
|
845
|
-
persistNativeFastIcons();
|
|
846
|
-
applyNativeFastIconsToShells();
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function applyNativeFastIconsToShells() {
|
|
851
|
-
document.querySelectorAll("[data-codex-model-slider-fast]").forEach((button) => {
|
|
852
|
-
const content = button.querySelector("[data-codex-model-slider-fast-content]");
|
|
853
|
-
if (!content) return;
|
|
854
|
-
const icon = createNativeFastIcon(button.getAttribute("aria-pressed") === "true");
|
|
855
|
-
if (!icon) return;
|
|
856
|
-
const current = content.querySelector("svg");
|
|
857
|
-
if (!current || current.innerHTML !== icon.innerHTML
|
|
858
|
-
|| current.getAttribute("viewBox") !== icon.getAttribute("viewBox")
|
|
859
|
-
|| current.getAttribute("width") !== icon.getAttribute("width")) {
|
|
860
|
-
content.replaceChildren(icon);
|
|
861
|
-
}
|
|
862
|
-
});
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
function createNativeFastIcon(active) {
|
|
866
|
-
// 直接读取已就绪的图标存储(启动时由 bundle/持久化填充,运行期由
|
|
867
|
-
// captureNativeFastIcon 升级为 DOM 克隆),避免再次触发捕获造成递归。
|
|
868
|
-
// 优先克隆当前面板的图标;切换瞬间使用缓存时,也沿用原生图标尺寸。
|
|
869
|
-
const nativeContents = Array.from(document.querySelectorAll('[class*="_FastModeToggleContent_"]'))
|
|
870
|
-
.filter((content) => !content.closest("[data-codex-model-slider-generic]"));
|
|
871
|
-
const nativeContent = nativeContents.find((content) => (
|
|
872
|
-
(content.getAttribute("data-fast-mode-enabled")
|
|
873
|
-
?? content.closest('[class*="_FastModeToggle_"]')?.getAttribute("data-fast-mode-enabled")) === String(active)
|
|
874
|
-
));
|
|
875
|
-
const nativeSvg = nativeContent?.querySelector("svg") ?? nativeContents[0]?.querySelector("svg");
|
|
876
|
-
const template = nativeContent?.querySelector("svg")
|
|
877
|
-
?? modelSelectorNativeFastIcons?.[active ? "active" : "inactive"];
|
|
878
|
-
if (!template) return null;
|
|
879
|
-
const icon = template.cloneNode(true);
|
|
880
|
-
if (nativeSvg) {
|
|
881
|
-
icon.setAttribute("width", nativeSvg.getAttribute("width") ?? "16");
|
|
882
|
-
icon.setAttribute("height", nativeSvg.getAttribute("height") ?? "16");
|
|
883
|
-
}
|
|
884
|
-
icon.setAttribute("aria-hidden", "true");
|
|
885
|
-
return icon;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
// ========================================================================
|
|
889
|
-
// 模型 identity、目录与菜单增强 — 模型身份解析、品牌/别名、收藏与原生菜单增强
|
|
890
|
-
// ========================================================================
|
|
891
|
-
|
|
892
|
-
function modelIdentity(rawValue) {
|
|
893
|
-
const raw = String(rawValue ?? "").trim();
|
|
894
|
-
const slash = raw.indexOf("/");
|
|
895
|
-
return {
|
|
896
|
-
raw,
|
|
897
|
-
provider: slash > 0 ? raw.slice(0, slash).trim() : "",
|
|
898
|
-
backend: slash > 0 ? raw.slice(slash + 1).trim() : raw,
|
|
899
|
-
};
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
function formatIdentifier(value) {
|
|
903
|
-
const raw = String(value ?? "").trim();
|
|
904
|
-
if (!raw) return "";
|
|
905
|
-
if (/\s/.test(raw) && !raw.includes("/") && !raw.includes("_") && !raw.includes("-")) return raw;
|
|
906
|
-
const canonical = new Map([
|
|
907
|
-
["ai", "AI"], ["api", "API"], ["chatglm", "ChatGLM"], ["claude", "Claude"],
|
|
908
|
-
["codex", "Codex"], ["deepseek", "DeepSeek"], ["gemini", "Gemini"], ["gemma", "Gemma"],
|
|
909
|
-
["glm", "GLM"], ["gpt", "GPT"], ["grok", "Grok"], ["kimi", "Kimi"],
|
|
910
|
-
["minimax", "MiniMax"], ["mimo", "MiMo"], ["moonshot", "Moonshot"], ["qwen", "Qwen"],
|
|
911
|
-
["qwq", "QwQ"], ["xai", "xAI"], ["xiaomi", "Xiaomi"], ["zhipu", "Zhipu"],
|
|
912
|
-
]);
|
|
913
|
-
return raw
|
|
914
|
-
.replace(/[_-]+/g, " ")
|
|
915
|
-
.split(/\s+/)
|
|
916
|
-
.filter(Boolean)
|
|
917
|
-
.map((token) => {
|
|
918
|
-
const lower = token.toLowerCase();
|
|
919
|
-
if (canonical.has(lower)) return canonical.get(lower);
|
|
920
|
-
if (/^v\d/i.test(token)) return `V${token.slice(1)}`;
|
|
921
|
-
if (/^o\d/i.test(token)) return token.toUpperCase();
|
|
922
|
-
return token.charAt(0).toUpperCase() + token.slice(1);
|
|
923
|
-
})
|
|
924
|
-
.join(" ");
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
function providerLabel(provider, selector) {
|
|
928
|
-
if (!provider) return "";
|
|
929
|
-
return selector.providerAliases?.[provider.toLowerCase()] ?? formatIdentifier(provider);
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
function modelDisplayLabel(identity, selector) {
|
|
933
|
-
const model = catalogModelFor(identity, selector) ?? nativeCatalogModelFor(identity);
|
|
934
|
-
const source = model?.displayName || model?.slug || identity.backend;
|
|
935
|
-
return formatIdentifier(source.slice(source.lastIndexOf("/") + 1));
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
function compactModelLabel(rawValue) {
|
|
939
|
-
const raw = String(rawValue ?? "").trim();
|
|
940
|
-
return formatIdentifier(raw.slice(raw.lastIndexOf("/") + 1));
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
function brandForModel(identity, selector) {
|
|
944
|
-
const haystack = `${identity.backend} ${identity.raw}`.toLowerCase();
|
|
945
|
-
return selector.brands?.find((brand) => (
|
|
946
|
-
brand.keywords.some((keyword) => haystack.includes(keyword))
|
|
947
|
-
)) ?? null;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
function comparableModelLabel(value) {
|
|
951
|
-
return formatIdentifier(modelIdentity(value).backend)
|
|
952
|
-
.toLowerCase()
|
|
953
|
-
.replace(/^gpt\s+/, "")
|
|
954
|
-
.trim();
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
function currentReactFiber(element) {
|
|
958
|
-
const fiberKey = Object.getOwnPropertyNames(element ?? {}).find((key) => key.startsWith("__reactFiber$"));
|
|
959
|
-
const propsKey = Object.getOwnPropertyNames(element ?? {}).find((key) => key.startsWith("__reactProps$"));
|
|
960
|
-
let fiber = fiberKey ? element[fiberKey] : null;
|
|
961
|
-
if (
|
|
962
|
-
propsKey
|
|
963
|
-
&& fiber?.alternate?.memoizedProps === element[propsKey]
|
|
964
|
-
&& fiber.memoizedProps !== element[propsKey]
|
|
965
|
-
) return fiber.alternate;
|
|
966
|
-
return fiber;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
function nativeComposerTrigger() {
|
|
970
|
-
const triggers = Array.from(document.querySelectorAll("[data-codex-intelligence-trigger]"))
|
|
971
|
-
.filter((trigger) => trigger.checkVisibility({ checkVisibilityCSS: true, checkOpacity: true }));
|
|
972
|
-
return triggers.find((trigger) => trigger.getAttribute("data-state") === "open")
|
|
973
|
-
?? triggers[0] ?? null;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
function nativeComposerModelController() {
|
|
977
|
-
const trigger = nativeComposerTrigger();
|
|
978
|
-
let fiber = currentReactFiber(trigger);
|
|
979
|
-
let powerController = null;
|
|
980
|
-
for (let depth = 0; fiber && depth < 40; depth += 1, fiber = fiber.return) {
|
|
981
|
-
const props = fiber.memoizedProps;
|
|
982
|
-
if (
|
|
983
|
-
Array.isArray(props?.powerSelections)
|
|
984
|
-
&& typeof props.onCommitPower === "function"
|
|
985
|
-
) powerController ??= props;
|
|
986
|
-
if (Array.isArray(props?.models) && typeof props.onSelectReasoningEffort === "function") return props;
|
|
987
|
-
if (Array.isArray(props?.models) && typeof props.onCommitPower === "function") {
|
|
988
|
-
const power = powerController ?? props;
|
|
989
|
-
return {
|
|
990
|
-
...props,
|
|
991
|
-
powerSelections: power.powerSelections ?? props.powerSelections,
|
|
992
|
-
selectedPowerSelection: power.selectedPowerSelection ?? props.selectedPowerSelection,
|
|
993
|
-
fallbackPowerSelection: power.fallbackPowerSelection ?? props.fallbackPowerSelection,
|
|
994
|
-
onSelectPower: power.onSelectPower ?? props.onSelectPower,
|
|
995
|
-
onCommitPower: power.onCommitPower,
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
// Chat/Temporary Chat 使用原生模型选择器,不进入任务模型适配。
|
|
999
|
-
if (props?.selectedModel && typeof props.onModelChange === "function") return null;
|
|
1000
|
-
}
|
|
1001
|
-
return null;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
function nativeCatalogModelFor(identity, selector = modelSelectorFeature?.modelSelector) {
|
|
1005
|
-
const controller = nativeComposerModelController();
|
|
1006
|
-
const models = controller?.models ?? [];
|
|
1007
|
-
const exact = models.find((model) => String(model.model ?? "").toLowerCase() === identity.raw.toLowerCase());
|
|
1008
|
-
const target = comparableModelLabel(identity.backend);
|
|
1009
|
-
const aliases = exact ? [] : models.filter((model) => (
|
|
1010
|
-
comparableModelLabel(model.model) === target || comparableModelLabel(model.displayName) === target
|
|
1011
|
-
));
|
|
1012
|
-
const model = exact ?? (aliases.length === 1 ? aliases[0] : null);
|
|
1013
|
-
if (!model) return null;
|
|
1014
|
-
const nativeReasoningLevels = (model.supportedReasoningEfforts ?? []).map((level) => ({
|
|
1015
|
-
effort: String(level?.reasoningEffort ?? level?.effort ?? level ?? "").toLowerCase(),
|
|
1016
|
-
})).filter((level) => level.effort);
|
|
1017
|
-
const supportedReasoningLevels = nativeReasoningLevels;
|
|
1018
|
-
const currentReasoningLevel = String(controller?.reasoningEffort ?? "").toLowerCase();
|
|
1019
|
-
return {
|
|
1020
|
-
slug: String(model.model ?? ""),
|
|
1021
|
-
displayName: String(model.displayName ?? model.model ?? ""),
|
|
1022
|
-
currentReasoningLevel: nativeReasoningLevels.some(
|
|
1023
|
-
(level) => level.effort === currentReasoningLevel,
|
|
1024
|
-
) ? currentReasoningLevel : null,
|
|
1025
|
-
currentReasoningLevelVisible: supportedReasoningLevels.some(
|
|
1026
|
-
(level) => level.effort === currentReasoningLevel,
|
|
1027
|
-
),
|
|
1028
|
-
defaultReasoningLevel: String(model.defaultReasoningEffort ?? "").toLowerCase(),
|
|
1029
|
-
supportedReasoningLevels,
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
function modelDefinitionFor(identity, selector) {
|
|
1034
|
-
return nativeCatalogModelFor(identity) ?? catalogModelFor(identity, selector);
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
// 用 OpenCodex 配置声明的真实档位对滑条做精确交集,过滤 catalog 为支持
|
|
1038
|
-
// mock 顶档而额外广告的 max/ultra。键匹配顺序:模型自带的
|
|
1039
|
-
// opencodex_capability_provenance → catalog 模型 → 菜单原始 identity;
|
|
1040
|
-
// 只做过滤不扩展,配置未声明的模型保持 catalog 原样。
|
|
1041
|
-
function modelWithOpenCodexEfforts(identity, selector, model) {
|
|
1042
|
-
if (!model || !selector?.openCodexRealEfforts) return model;
|
|
1043
|
-
const provenance = model.opencodex_capability_provenance;
|
|
1044
|
-
const catalogModel = provenance?.provider && provenance?.model_id
|
|
1045
|
-
? null
|
|
1046
|
-
: catalogModelFor(identity, selector);
|
|
1047
|
-
const source = catalogModel ?? model;
|
|
1048
|
-
const sourceProvenance = source.opencodex_capability_provenance;
|
|
1049
|
-
const key = sourceProvenance?.provider && sourceProvenance?.model_id
|
|
1050
|
-
? `${sourceProvenance.provider}/${sourceProvenance.model_id}`.toLowerCase()
|
|
1051
|
-
: identity.raw.toLowerCase();
|
|
1052
|
-
const realEfforts = selector.openCodexRealEfforts[key];
|
|
1053
|
-
if (!realEfforts) return model;
|
|
1054
|
-
const allowed = new Set(realEfforts);
|
|
1055
|
-
const levels = (model.supportedReasoningLevels ?? []).filter((level) => {
|
|
1056
|
-
const effort = String(level?.effort ?? level ?? "").toLowerCase();
|
|
1057
|
-
return allowed.has(effort);
|
|
1058
|
-
});
|
|
1059
|
-
return { ...model, supportedReasoningLevels: levels };
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
function nativeModelDescriptorForItem(item, observedRaw) {
|
|
1063
|
-
const target = comparableModelLabel(observedRaw);
|
|
1064
|
-
if (!target) return null;
|
|
1065
|
-
const fiberKey = Object.getOwnPropertyNames(item).find((key) => key.startsWith("__reactFiber$"));
|
|
1066
|
-
let fiber = fiberKey ? item[fiberKey] : null;
|
|
1067
|
-
for (let depth = 0; fiber && depth < 35; depth += 1, fiber = fiber.return) {
|
|
1068
|
-
if (!Array.isArray(fiber.memoizedProps)) continue;
|
|
1069
|
-
const matches = fiber.memoizedProps.map((element) => {
|
|
1070
|
-
const props = element?.props?.children?.props;
|
|
1071
|
-
const slug = String(props?.model ?? element?.key ?? "").trim();
|
|
1072
|
-
const displayName = String(props?.displayName ?? "").trim();
|
|
1073
|
-
return slug && (
|
|
1074
|
-
comparableModelLabel(slug) === target || comparableModelLabel(displayName) === target
|
|
1075
|
-
) ? { slug, displayName } : null;
|
|
1076
|
-
}).filter(Boolean);
|
|
1077
|
-
if (matches.length === 1) return matches[0];
|
|
1078
|
-
}
|
|
1079
|
-
return null;
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
function catalogModelFor(identity, selector) {
|
|
1083
|
-
const raw = identity.raw.toLowerCase();
|
|
1084
|
-
const backend = identity.backend.toLowerCase();
|
|
1085
|
-
const exact = selector.models?.find((model) => (
|
|
1086
|
-
model.slug.toLowerCase() === raw
|
|
1087
|
-
|| model.slug.toLowerCase() === backend
|
|
1088
|
-
|| model.displayName.toLowerCase() === raw
|
|
1089
|
-
)) ?? null;
|
|
1090
|
-
if (exact) return exact;
|
|
1091
|
-
const target = comparableModelLabel(identity.backend);
|
|
1092
|
-
if (!target) return null;
|
|
1093
|
-
const aliases = (selector.models ?? []).filter((model) => (
|
|
1094
|
-
comparableModelLabel(model.slug) === target || comparableModelLabel(model.displayName) === target
|
|
1095
|
-
));
|
|
1096
|
-
return resolveModelAlias(aliases, identity);
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
function resolveModelAlias(aliases, identity) {
|
|
1100
|
-
if (aliases.length === 1) return aliases[0];
|
|
1101
|
-
if (aliases.length < 2 || identity.provider) return null;
|
|
1102
|
-
const native = aliases.find((model) => !model.slug.includes("/"));
|
|
1103
|
-
const thirdParty = aliases.find((model) => model.slug.includes("/"));
|
|
1104
|
-
if (!native || !thirdParty) return null;
|
|
1105
|
-
if (!modelIdentity(native.slug).backend.toLowerCase().startsWith("gpt")) return null;
|
|
1106
|
-
const nativeLabel = formatIdentifier(
|
|
1107
|
-
modelIdentity(String(native.displayName ?? native.slug)).backend,
|
|
1108
|
-
).toLowerCase();
|
|
1109
|
-
return formatIdentifier(identity.backend).toLowerCase() === nativeLabel ? native : thirdParty;
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
function loadModelFavorites(selector) {
|
|
1113
|
-
try {
|
|
1114
|
-
const value = JSON.parse(localStorage.getItem(selector.favoriteStorageKey) ?? "[]");
|
|
1115
|
-
return new Set(Array.isArray(value) ? value.map(String) : []);
|
|
1116
|
-
} catch {
|
|
1117
|
-
return new Set();
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
function saveModelFavorites(selector, favorites) {
|
|
1122
|
-
try {
|
|
1123
|
-
localStorage.setItem(selector.favoriteStorageKey, JSON.stringify(Array.from(favorites)));
|
|
1124
|
-
} catch {
|
|
1125
|
-
// A disabled localStorage must not break the native picker.
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
function nativeModelPickerMenu() {
|
|
1130
|
-
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]')).find(
|
|
1131
|
-
(menu) => (
|
|
1132
|
-
menu.querySelector("[data-reasoning-slider]")
|
|
1133
|
-
&& (menu.querySelector("[data-model-picker-view-toggle]") || nativeModelPickerRow(menu))
|
|
1134
|
-
),
|
|
1135
|
-
) ?? null;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
function nativeModelPickerRow(menu) {
|
|
1139
|
-
const rows = Array.from(menu?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
1140
|
-
return rows.find((row) => (
|
|
1141
|
-
row.getAttribute("aria-haspopup") === "menu"
|
|
1142
|
-
&& !row.hasAttribute("data-model-picker-view-toggle")
|
|
1143
|
-
)) ?? rows.find((row) => {
|
|
1144
|
-
const aria = String(row.getAttribute("aria-label") ?? "").trim();
|
|
1145
|
-
const firstLine = String(row.innerText ?? "").split("\n", 1)[0].trim();
|
|
1146
|
-
return /^model(?:\s|$)/i.test(aria) || /^model$/i.test(firstLine);
|
|
1147
|
-
}) ?? null;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
function nativeModelValue(menu) {
|
|
1151
|
-
const toggle = menu?.querySelector("[data-model-picker-view-toggle]");
|
|
1152
|
-
const toggleModelLabel = toggle?.querySelector('[class*="_ViewToggleModelLabel_"]');
|
|
1153
|
-
const toggleValues = String(
|
|
1154
|
-
toggle?.hasAttribute("data-model-picker-view-toggle")
|
|
1155
|
-
? (toggleModelLabel?.textContent ?? toggle.innerText)
|
|
1156
|
-
: "",
|
|
1157
|
-
).split("\n").map((value) => value.trim()).filter(Boolean);
|
|
1158
|
-
if (toggleValues.length) return toggleValues[0];
|
|
1159
|
-
const row = nativeModelPickerRow(menu);
|
|
1160
|
-
const values = String(row?.innerText ?? "").split("\n").map((value) => value.trim()).filter(Boolean);
|
|
1161
|
-
if (values.length > 1) return values.at(-1);
|
|
1162
|
-
const aria = String(row?.getAttribute("aria-label") ?? "").trim();
|
|
1163
|
-
return aria.replace(/^\S+\s+/, "").trim();
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
function firstVisibleTextNode(element) {
|
|
1167
|
-
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
1168
|
-
let node = walker.nextNode();
|
|
1169
|
-
while (node) {
|
|
1170
|
-
if (node.textContent.trim() && !node.parentElement?.closest("svg")) return node;
|
|
1171
|
-
node = walker.nextNode();
|
|
1172
|
-
}
|
|
1173
|
-
return null;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
function modelMenuItems(menu) {
|
|
1177
|
-
return Array.from(menu?.querySelectorAll(
|
|
1178
|
-
':scope [role="menuitemradio"], :scope [role="menuitemcheckbox"], :scope [role="menuitem"]',
|
|
1179
|
-
) ?? []).filter((item) => item.closest('[role="menu"]') === menu && !item.hasAttribute("aria-haspopup"));
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
function nativeMenuItemLabel(item) {
|
|
1183
|
-
return Array.from(item?.querySelectorAll("span") ?? [])
|
|
1184
|
-
.map((span) => String(span.textContent ?? "").trim())
|
|
1185
|
-
.find(Boolean) ?? String(item?.textContent ?? "").trim();
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
function nativeModelSubmenu(parentMenu) {
|
|
1189
|
-
if (nativeModelPickerRow(parentMenu)?.getAttribute("aria-expanded") !== "true") return null;
|
|
1190
|
-
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]')).find((menu) => (
|
|
1191
|
-
!menu.querySelector("[data-reasoning-slider]") && modelMenuItems(menu).length > 1
|
|
1192
|
-
)) ?? null;
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
function isNativeEffortSubmenu(menu) {
|
|
1196
|
-
const effortLabels = new Set([
|
|
1197
|
-
"none", "minimal", "light", "low", "medium", "high", "extra high", "max", "ultra",
|
|
1198
|
-
]);
|
|
1199
|
-
if (!menu || menu.querySelector("[data-reasoning-slider]")) return false;
|
|
1200
|
-
const labels = modelMenuItems(menu).map((item) => (
|
|
1201
|
-
nativeMenuItemLabel(item).toLowerCase()
|
|
1202
|
-
)).filter(Boolean);
|
|
1203
|
-
return labels.length > 1 && labels.every((label) => effortLabels.has(label));
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
function nativeEffortSubmenu() {
|
|
1207
|
-
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]'))
|
|
1208
|
-
.find((menu) => isNativeEffortSubmenu(menu)) ?? null;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
function nativeModelPickerSubmenus() {
|
|
1212
|
-
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]')).filter((menu) => {
|
|
1213
|
-
if (menu.querySelector("[data-reasoning-slider]")) return false;
|
|
1214
|
-
if (menu.hasAttribute("data-codex-model-slider-menu")) return true;
|
|
1215
|
-
return isNativeEffortSubmenu(menu);
|
|
1216
|
-
});
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
function setReactControlledMenuOpen(element, open) {
|
|
1220
|
-
if (!element) return false;
|
|
1221
|
-
const fiberKey = Object.getOwnPropertyNames(element).find((key) => key.startsWith("__reactFiber$"));
|
|
1222
|
-
let fiber = fiberKey ? element[fiberKey] : null;
|
|
1223
|
-
for (let depth = 0; fiber && depth < 40; depth += 1, fiber = fiber.return) {
|
|
1224
|
-
const props = fiber.memoizedProps;
|
|
1225
|
-
if (typeof props?.open === "boolean" && props.open !== open && typeof props.onOpenChange === "function") {
|
|
1226
|
-
props.onOpenChange(open);
|
|
1227
|
-
return true;
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
return false;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
function closeReactControlledMenu(element) {
|
|
1234
|
-
return setReactControlledMenuOpen(element, false);
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
function invokeNativeMenuItemSelect(item) {
|
|
1238
|
-
if (!item) return false;
|
|
1239
|
-
const fiberKey = Object.getOwnPropertyNames(item).find((key) => key.startsWith("__reactFiber$"));
|
|
1240
|
-
let fiber = fiberKey ? item[fiberKey] : null;
|
|
1241
|
-
for (let depth = 0; fiber && depth < 24; depth += 1, fiber = fiber.return) {
|
|
1242
|
-
const onSelect = fiber.memoizedProps?.onSelect;
|
|
1243
|
-
if (typeof onSelect !== "function") continue;
|
|
1244
|
-
onSelect({ preventDefault() {}, defaultPrevented: false });
|
|
1245
|
-
return true;
|
|
1246
|
-
}
|
|
1247
|
-
return false;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
function closeNativeModelPickerSubmenus() {
|
|
1251
|
-
const submenus = nativeModelPickerSubmenus();
|
|
1252
|
-
const parentMenu = nativeModelPickerMenu();
|
|
1253
|
-
const expandedRows = Array.from(
|
|
1254
|
-
parentMenu?.querySelectorAll('[role="menuitem"][aria-haspopup="menu"][aria-expanded="true"]') ?? [],
|
|
1255
|
-
);
|
|
1256
|
-
let controlledMenuClosed = false;
|
|
1257
|
-
for (const row of expandedRows) {
|
|
1258
|
-
controlledMenuClosed = closeReactControlledMenu(row) || controlledMenuClosed;
|
|
1259
|
-
}
|
|
1260
|
-
for (const submenu of submenus.reverse()) {
|
|
1261
|
-
if (controlledMenuClosed || closeReactControlledMenu(submenu)) continue;
|
|
1262
|
-
submenu.dispatchEvent(new KeyboardEvent("keydown", {
|
|
1263
|
-
key: "Escape",
|
|
1264
|
-
code: "Escape",
|
|
1265
|
-
bubbles: true,
|
|
1266
|
-
cancelable: true,
|
|
1267
|
-
}));
|
|
1268
|
-
}
|
|
1269
|
-
return submenus.length > 0;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
function endNativeEffortTransaction() {
|
|
1273
|
-
clearTimeout(modelSelectorEffortTransactionTimer);
|
|
1274
|
-
modelSelectorEffortTransactionTimer = null;
|
|
1275
|
-
modelSelectorPendingOutsideDismiss = false;
|
|
1276
|
-
document.documentElement.removeAttribute("data-codex-model-slider-selecting-effort");
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
function beginNativeEffortTransaction() {
|
|
1280
|
-
clearTimeout(modelSelectorEffortTransactionTimer);
|
|
1281
|
-
document.documentElement.setAttribute("data-codex-model-slider-selecting-effort", "");
|
|
1282
|
-
modelSelectorEffortTransactionTimer = setTimeout(() => {
|
|
1283
|
-
closeNativeModelPickerSubmenus();
|
|
1284
|
-
endNativeEffortTransaction();
|
|
1285
|
-
}, 2_000);
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
function deferNativeEffortOutsideDismiss(event) {
|
|
1289
|
-
const insideMenu = event.target.closest?.('[role="menu"]');
|
|
1290
|
-
if (insideMenu) return;
|
|
1291
|
-
if (event.type === "pointerdown" && modelSelectorEffortTransactionTimer != null) {
|
|
1292
|
-
modelSelectorPendingOutsideDismiss = true;
|
|
1293
|
-
modelSelectorSuppressOutsidePointerSequence = true;
|
|
1294
|
-
}
|
|
1295
|
-
if (!modelSelectorSuppressOutsidePointerSequence) return;
|
|
1296
|
-
event.preventDefault();
|
|
1297
|
-
event.stopPropagation();
|
|
1298
|
-
event.stopImmediatePropagation();
|
|
1299
|
-
if (event.type === "click") modelSelectorSuppressOutsidePointerSequence = false;
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
for (const type of ["pointerdown", "mousedown", "pointerup", "mouseup", "click"]) {
|
|
1303
|
-
document.addEventListener(type, deferNativeEffortOutsideDismiss, {
|
|
1304
|
-
capture: true,
|
|
1305
|
-
signal: lifetime.signal,
|
|
1306
|
-
});
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
function modelItemRawValue(item) {
|
|
1310
|
-
if (item.dataset.codexModelSliderRaw) return item.dataset.codexModelSliderRaw;
|
|
1311
|
-
return String(item.innerText ?? "").split("\n").map((value) => value.trim()).filter(Boolean).join(" ");
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
function visibleBlockingDialog() {
|
|
1315
|
-
return Array.from(document.querySelectorAll('[role="dialog"]')).some((dialog) => {
|
|
1316
|
-
const rect = dialog.getBoundingClientRect();
|
|
1317
|
-
const style = getComputedStyle(dialog);
|
|
1318
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
1319
|
-
});
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
function dispatchPointerClick(element) {
|
|
1323
|
-
if (!element) return;
|
|
1324
|
-
const rect = element.getBoundingClientRect();
|
|
1325
|
-
const init = {
|
|
1326
|
-
bubbles: true,
|
|
1327
|
-
cancelable: true,
|
|
1328
|
-
view: window,
|
|
1329
|
-
pointerType: "mouse",
|
|
1330
|
-
button: 0,
|
|
1331
|
-
clientX: rect.left + rect.width / 2,
|
|
1332
|
-
clientY: rect.top + rect.height / 2,
|
|
1333
|
-
};
|
|
1334
|
-
for (const type of [
|
|
1335
|
-
"pointermove", "pointerover", "mouseover", "mousemove",
|
|
1336
|
-
"pointerdown", "mousedown", "pointerup", "mouseup", "click",
|
|
1337
|
-
]) {
|
|
1338
|
-
const EventType = type.startsWith("pointer") ? PointerEvent : MouseEvent;
|
|
1339
|
-
const pressed = type === "pointerdown" || type === "mousedown";
|
|
1340
|
-
element.dispatchEvent(new EventType(type, {
|
|
1341
|
-
...init,
|
|
1342
|
-
buttons: pressed ? 1 : 0,
|
|
1343
|
-
detail: type === "click" ? 1 : 0,
|
|
1344
|
-
}));
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
function returnToNativeModelSlider(attempt = 0) {
|
|
1349
|
-
if (visibleBlockingDialog()) {
|
|
1350
|
-
closeNativeModelPickerSubmenus();
|
|
1351
|
-
setTimeout(endNativeEffortTransaction, 120);
|
|
1352
|
-
return;
|
|
1353
|
-
}
|
|
1354
|
-
if (closeNativeModelPickerSubmenus()) {
|
|
1355
|
-
setTimeout(() => returnToNativeModelSlider(attempt), 100);
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1358
|
-
let parentMenu = nativeModelPickerMenu();
|
|
1359
|
-
if (parentMenu) {
|
|
1360
|
-
const toggle = parentMenu.querySelector("[data-model-picker-view-toggle]");
|
|
1361
|
-
if (toggle?.getAttribute("aria-expanded") === "true") toggle.click();
|
|
1362
|
-
setTimeout(() => {
|
|
1363
|
-
if (!nativeModelPickerMenu() && attempt < 3) {
|
|
1364
|
-
returnToNativeModelSlider(attempt + 1);
|
|
1365
|
-
return;
|
|
1366
|
-
}
|
|
1367
|
-
modelSelectorPendingReturn = false;
|
|
1368
|
-
if (nativeModelPickerSubmenus().length === 0) endNativeEffortTransaction();
|
|
1369
|
-
queueEnsure();
|
|
1370
|
-
}, 140);
|
|
1371
|
-
return;
|
|
1372
|
-
}
|
|
1373
|
-
const trigger = nativeComposerTrigger();
|
|
1374
|
-
if (!trigger) return;
|
|
1375
|
-
dispatchPointerClick(trigger);
|
|
1376
|
-
setTimeout(() => {
|
|
1377
|
-
if (!nativeModelPickerMenu() && attempt < 3) {
|
|
1378
|
-
returnToNativeModelSlider(attempt + 1);
|
|
1379
|
-
return;
|
|
1380
|
-
}
|
|
1381
|
-
modelSelectorPendingReturn = false;
|
|
1382
|
-
if (nativeModelPickerSubmenus().length === 0) endNativeEffortTransaction();
|
|
1383
|
-
queueEnsure();
|
|
1384
|
-
}, 140);
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
function scheduleModelSliderReturn() {
|
|
1388
|
-
modelSelectorPendingReturn = true;
|
|
1389
|
-
clearTimeout(modelSelectorReturnTimer);
|
|
1390
|
-
modelSelectorReturnTimer = setTimeout(() => {
|
|
1391
|
-
modelSelectorReturnTimer = null;
|
|
1392
|
-
returnToNativeModelSlider();
|
|
1393
|
-
}, 260);
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
function nativeGptSortRank(rawValue) {
|
|
1397
|
-
const known = new Map([
|
|
1398
|
-
["gpt-6-astra", 1], ["gpt-5.6-sol", 2], ["gpt-5.6-terra", 3], ["gpt-5.6-luna", 4],
|
|
1399
|
-
["gpt-5.5", 5], ["gpt-5.4", 6], ["gpt-5.4-mini", 7], ["gpt-5.3-codex-spark", 8],
|
|
1400
|
-
]);
|
|
1401
|
-
const raw = String(rawValue ?? "").trim();
|
|
1402
|
-
const knownRank = known.get(raw);
|
|
1403
|
-
if (knownRank != null) return { official: true, rank: knownRank };
|
|
1404
|
-
const identity = modelIdentity(raw);
|
|
1405
|
-
return identity.provider ? null : { official: true, rank: 100 };
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
function compareEnhancedModelItems(a, b, favorites) {
|
|
1409
|
-
const favA = Number(favorites.has(a.dataset.codexModelSliderRaw));
|
|
1410
|
-
const favB = Number(favorites.has(b.dataset.codexModelSliderRaw));
|
|
1411
|
-
if (favA !== favB) return favB - favA;
|
|
1412
|
-
const rankA = nativeGptSortRank(a.dataset.codexModelSliderRaw);
|
|
1413
|
-
const rankB = nativeGptSortRank(b.dataset.codexModelSliderRaw);
|
|
1414
|
-
if (Boolean(rankA) !== Boolean(rankB)) return rankA ? -1 : 1;
|
|
1415
|
-
if (rankA && rankB) return rankA.rank - rankB.rank;
|
|
1416
|
-
return 0;
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
function renderEnhancedModelMenu(menu, selector) {
|
|
1420
|
-
const items = modelMenuItems(menu).filter((item) => item.dataset.codexModelSliderRaw);
|
|
1421
|
-
if (!items.length) return;
|
|
1422
|
-
const favorites = loadModelFavorites(selector);
|
|
1423
|
-
items.forEach((item) => { item.style.display = ""; });
|
|
1424
|
-
if (!menu.hasAttribute("data-codex-model-slider-controller-menu")) {
|
|
1425
|
-
if (!modelSelectorMenuSizeFrames.has(menu)) {
|
|
1426
|
-
const frame = requestAnimationFrame(() => {
|
|
1427
|
-
modelSelectorMenuSizeFrames.delete(menu);
|
|
1428
|
-
sizeModelMenuViewport(menu, selector.maxVisibleItems);
|
|
1429
|
-
});
|
|
1430
|
-
modelSelectorMenuSizeFrames.set(menu, frame);
|
|
1431
|
-
}
|
|
1432
|
-
return;
|
|
1433
|
-
}
|
|
1434
|
-
const starred = items.filter((item) => favorites.has(item.dataset.codexModelSliderRaw));
|
|
1435
|
-
const ordinary = items.filter((item) => !favorites.has(item.dataset.codexModelSliderRaw));
|
|
1436
|
-
const desired = items.slice().sort((a, b) => compareEnhancedModelItems(a, b, favorites));
|
|
1437
|
-
const parent = items[0].parentElement;
|
|
1438
|
-
if (!parent || !desired.every((item) => item.parentElement === parent)) return;
|
|
1439
|
-
const current = Array.from(parent.children).filter((child) => child.hasAttribute?.("data-codex-model-slider-item"));
|
|
1440
|
-
if (desired.some((item, index) => current[index] !== item)) desired.forEach((item) => parent.append(item));
|
|
1441
|
-
|
|
1442
|
-
let divider = parent.querySelector(":scope > [data-codex-model-slider-divider]");
|
|
1443
|
-
if (starred.length && ordinary.length) {
|
|
1444
|
-
if (!divider) {
|
|
1445
|
-
divider = document.createElement("div");
|
|
1446
|
-
divider.setAttribute("data-codex-model-slider-divider", "");
|
|
1447
|
-
divider.setAttribute("role", "separator");
|
|
1448
|
-
}
|
|
1449
|
-
const firstOrdinary = desired.find((item) => !favorites.has(item.dataset.codexModelSliderRaw));
|
|
1450
|
-
if (firstOrdinary && divider.nextElementSibling !== firstOrdinary) {
|
|
1451
|
-
parent.insertBefore(divider, firstOrdinary);
|
|
1452
|
-
}
|
|
1453
|
-
} else {
|
|
1454
|
-
divider?.remove();
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
if (!modelSelectorMenuSizeFrames.has(menu)) {
|
|
1458
|
-
const frame = requestAnimationFrame(() => {
|
|
1459
|
-
modelSelectorMenuSizeFrames.delete(menu);
|
|
1460
|
-
sizeModelMenuViewport(menu, selector.maxVisibleItems);
|
|
1461
|
-
});
|
|
1462
|
-
modelSelectorMenuSizeFrames.set(menu, frame);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
function sizeModelMenuViewport(menu, maxVisibleItems) {
|
|
1467
|
-
if (!menu.isConnected) return;
|
|
1468
|
-
const items = modelMenuItems(menu).filter((item) => item.dataset.codexModelSliderRaw);
|
|
1469
|
-
if (items.length <= maxVisibleItems) {
|
|
1470
|
-
menu.removeAttribute("data-codex-model-slider-overflow");
|
|
1471
|
-
menu.style.removeProperty("--codex-model-slider-menu-max-height");
|
|
1472
|
-
menu.style.removeProperty("max-height");
|
|
1473
|
-
return;
|
|
1474
|
-
}
|
|
1475
|
-
const lastVisibleItem = items[maxVisibleItems - 1];
|
|
1476
|
-
const menuRect = menu.getBoundingClientRect();
|
|
1477
|
-
const itemRect = lastVisibleItem.getBoundingClientRect();
|
|
1478
|
-
const paddingBottom = Number.parseFloat(getComputedStyle(menu).paddingBottom) || 0;
|
|
1479
|
-
const maxHeight = Math.ceil(itemRect.bottom - menuRect.top + menu.scrollTop + paddingBottom);
|
|
1480
|
-
menu.setAttribute("data-codex-model-slider-overflow", "");
|
|
1481
|
-
menu.style.setProperty("--codex-model-slider-menu-max-height", `${maxHeight}px`);
|
|
1482
|
-
menu.style.overflowY = "auto";
|
|
1483
|
-
positionModelSubmenu(menu);
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
function toggleModelFavorite(event, menu, selector, raw, label) {
|
|
1487
|
-
event.preventDefault();
|
|
1488
|
-
event.stopPropagation();
|
|
1489
|
-
event.stopImmediatePropagation();
|
|
1490
|
-
const favorites = loadModelFavorites(selector);
|
|
1491
|
-
if (favorites.has(raw)) favorites.delete(raw);
|
|
1492
|
-
else favorites.add(raw);
|
|
1493
|
-
saveModelFavorites(selector, favorites);
|
|
1494
|
-
for (const item of modelMenuItems(menu)) {
|
|
1495
|
-
if (!item.dataset.codexModelSliderRaw) continue;
|
|
1496
|
-
const starred = favorites.has(item.dataset.codexModelSliderRaw);
|
|
1497
|
-
const button = item.querySelector("[data-codex-model-slider-star]");
|
|
1498
|
-
if (!button) continue;
|
|
1499
|
-
button.replaceChildren(createModelSelectorIcon(starred ? selector.icons.unstar : selector.icons.star));
|
|
1500
|
-
const itemLabel = item.querySelector("[data-codex-model-slider-name]")?.textContent ?? label;
|
|
1501
|
-
button.setAttribute("aria-label", selector.strings[starred ? "unstar" : "star"].replace("{model}", itemLabel));
|
|
1502
|
-
button.setAttribute("data-minecodex-tooltip", selector.strings[starred ? "unstar" : "star"].replace("{model}", itemLabel));
|
|
1503
|
-
}
|
|
1504
|
-
renderEnhancedModelMenu(menu, selector);
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
function enhanceModelItem(item, menu, selector, index, {
|
|
1508
|
-
rawValue = null,
|
|
1509
|
-
selected = null,
|
|
1510
|
-
scheduleReturn = true,
|
|
1511
|
-
owned = false,
|
|
1512
|
-
} = {}) {
|
|
1513
|
-
const observedRaw = rawValue ?? modelItemRawValue(item);
|
|
1514
|
-
if (!observedRaw) return;
|
|
1515
|
-
const observedIdentity = modelIdentity(observedRaw);
|
|
1516
|
-
const nativeDescriptor = nativeModelDescriptorForItem(item, observedRaw);
|
|
1517
|
-
const catalogModel = catalogModelFor(observedIdentity, selector);
|
|
1518
|
-
const raw = catalogModel?.slug ?? nativeDescriptor?.slug ?? observedRaw;
|
|
1519
|
-
// 先刷新 dataset:React 原地复用菜单节点时,raw/排序/收藏键不能基于过期值。
|
|
1520
|
-
item.dataset.codexModelSliderRaw = raw;
|
|
1521
|
-
item.dataset.codexModelSliderItem = "";
|
|
1522
|
-
item.dataset.codexModelSliderOrder = String(index);
|
|
1523
|
-
item.dataset.codexModelSliderIdentitySource = catalogModel ? "catalog" : nativeDescriptor ? "native" : "visible";
|
|
1524
|
-
if (item.querySelector("[data-codex-model-slider-row]")) return;
|
|
1525
|
-
item.toggleAttribute(
|
|
1526
|
-
"data-codex-model-slider-selected",
|
|
1527
|
-
selected ?? Boolean(item.querySelector("svg")),
|
|
1528
|
-
);
|
|
1529
|
-
if (!owned) {
|
|
1530
|
-
if (scheduleReturn && !item.hasAttribute("data-codex-model-slider-return-listener")) {
|
|
1531
|
-
item.setAttribute("data-codex-model-slider-return-listener", "");
|
|
1532
|
-
item.addEventListener("click", scheduleModelSliderReturn);
|
|
1533
|
-
}
|
|
1534
|
-
return;
|
|
1535
|
-
}
|
|
1536
|
-
const identity = modelIdentity(raw);
|
|
1537
|
-
const display = modelDisplayLabel(identity, selector);
|
|
1538
|
-
const provider = providerLabel(identity.provider, selector);
|
|
1539
|
-
const brand = brandForModel(identity, selector);
|
|
1540
|
-
const favorites = loadModelFavorites(selector);
|
|
1541
|
-
const starred = favorites.has(raw);
|
|
1542
|
-
const row = document.createElement("span");
|
|
1543
|
-
row.setAttribute("data-codex-model-slider-row", "");
|
|
1544
|
-
const brandRoot = document.createElement("span");
|
|
1545
|
-
brandRoot.setAttribute("data-codex-model-slider-brand", brand?.id ?? "fallback");
|
|
1546
|
-
brandRoot.append(createModelSelectorIcon(brand?.icon ?? selector.icons.fallback, { brand: Boolean(brand) }));
|
|
1547
|
-
const copy = document.createElement("span");
|
|
1548
|
-
copy.setAttribute("data-codex-model-slider-copy", "");
|
|
1549
|
-
const name = document.createElement("span");
|
|
1550
|
-
name.setAttribute("data-codex-model-slider-name", "");
|
|
1551
|
-
name.textContent = display;
|
|
1552
|
-
copy.append(name);
|
|
1553
|
-
if (provider) {
|
|
1554
|
-
const secondary = document.createElement("span");
|
|
1555
|
-
secondary.setAttribute("data-codex-model-slider-provider", "");
|
|
1556
|
-
secondary.textContent = provider;
|
|
1557
|
-
copy.append(secondary);
|
|
1558
|
-
}
|
|
1559
|
-
row.append(brandRoot, copy);
|
|
1560
|
-
const star = document.createElement("button");
|
|
1561
|
-
star.type = "button";
|
|
1562
|
-
star.setAttribute("data-codex-model-slider-star", starred ? "unstar" : "star");
|
|
1563
|
-
star.setAttribute("aria-label", selector.strings[starred ? "unstar" : "star"].replace("{model}", display));
|
|
1564
|
-
star.setAttribute("data-minecodex-tooltip", selector.strings[starred ? "unstar" : "star"].replace("{model}", display));
|
|
1565
|
-
star.append(createModelSelectorIcon(starred ? selector.icons.unstar : selector.icons.star));
|
|
1566
|
-
row.append(star);
|
|
1567
|
-
item.replaceChildren(row);
|
|
1568
|
-
if (scheduleReturn && !item.hasAttribute("data-codex-model-slider-return-listener")) {
|
|
1569
|
-
item.setAttribute("data-codex-model-slider-return-listener", "");
|
|
1570
|
-
item.addEventListener("click", (event) => {
|
|
1571
|
-
if (!event.target.closest?.("[data-codex-model-slider-star]")) scheduleModelSliderReturn();
|
|
1572
|
-
});
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
function enhanceNativeModelSubmenu(menu, selector) {
|
|
1577
|
-
menu.setAttribute("data-codex-model-slider-menu", "");
|
|
1578
|
-
observeModelSubmenuPosition(menu);
|
|
1579
|
-
positionModelSubmenu(menu);
|
|
1580
|
-
const items = modelMenuItems(menu);
|
|
1581
|
-
items.forEach((item, index) => enhanceModelItem(item, menu, selector, index));
|
|
1582
|
-
if (!menu.hasAttribute("data-codex-model-slider-events")) {
|
|
1583
|
-
menu.setAttribute("data-codex-model-slider-events", "");
|
|
1584
|
-
menu.addEventListener("click", (event) => {
|
|
1585
|
-
const star = event.target.closest?.("[data-codex-model-slider-star]");
|
|
1586
|
-
if (star) {
|
|
1587
|
-
const item = star.closest("[data-codex-model-slider-item]");
|
|
1588
|
-
const raw = item?.dataset.codexModelSliderRaw;
|
|
1589
|
-
if (!raw) return;
|
|
1590
|
-
const label = item.querySelector("[data-codex-model-slider-name]")?.textContent ?? raw;
|
|
1591
|
-
toggleModelFavorite(event, menu, selector, raw, label);
|
|
1592
|
-
event.stopImmediatePropagation();
|
|
1593
|
-
return;
|
|
1594
|
-
}
|
|
1595
|
-
if (event.target.closest?.("[data-codex-model-slider-item]")) scheduleModelSliderReturn();
|
|
1596
|
-
}, { capture: true });
|
|
1597
|
-
menu.addEventListener("pointerdown", (event) => {
|
|
1598
|
-
const star = event.target.closest?.("[data-codex-model-slider-star]");
|
|
1599
|
-
if (star) {
|
|
1600
|
-
event.stopPropagation();
|
|
1601
|
-
event.stopImmediatePropagation();
|
|
1602
|
-
return;
|
|
1603
|
-
}
|
|
1604
|
-
if (event.target.closest?.("[data-codex-model-slider-item]")) scheduleModelSliderReturn();
|
|
1605
|
-
}, { capture: true });
|
|
1606
|
-
for (const type of ["mousedown", "pointerup", "mouseup"]) {
|
|
1607
|
-
menu.addEventListener(type, (event) => {
|
|
1608
|
-
if (!event.target.closest?.("[data-codex-model-slider-star]")) return;
|
|
1609
|
-
event.stopPropagation();
|
|
1610
|
-
event.stopImmediatePropagation();
|
|
1611
|
-
}, { capture: true });
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
renderEnhancedModelMenu(menu, selector);
|
|
1615
|
-
if (!menu.hasAttribute("data-codex-model-slider-positioned")) menu.style.visibility = "hidden";
|
|
1616
|
-
positionModelSubmenu(menu);
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
function positionModelSubmenu(menu) {
|
|
1620
|
-
const trigger = document.getElementById(menu?.getAttribute("aria-labelledby") ?? "");
|
|
1621
|
-
const parentMenu = trigger?.closest('[role="menu"]') ?? nativeModelPickerMenu();
|
|
1622
|
-
const wrapper = menu?.parentElement;
|
|
1623
|
-
if (
|
|
1624
|
-
!parentMenu?.hasAttribute("data-codex-model-slider-generic-menu")
|
|
1625
|
-
|| !wrapper?.hasAttribute("data-radix-popper-content-wrapper")
|
|
1626
|
-
) return;
|
|
1627
|
-
const parentRect = parentMenu.getBoundingClientRect();
|
|
1628
|
-
const wrapperRect = wrapper.getBoundingClientRect();
|
|
1629
|
-
if (!(parentRect.width > 0 && wrapperRect.width > 0 && wrapperRect.height > 0)) return;
|
|
1630
|
-
const inset = 8;
|
|
1631
|
-
const left = Math.min(
|
|
1632
|
-
innerWidth - wrapperRect.width - inset,
|
|
1633
|
-
Math.max(inset, parentRect.right - wrapperRect.width),
|
|
1634
|
-
);
|
|
1635
|
-
const top = Math.min(
|
|
1636
|
-
innerHeight - wrapperRect.height - inset,
|
|
1637
|
-
Math.max(inset, parentRect.bottom - wrapperRect.height),
|
|
1638
|
-
);
|
|
1639
|
-
const transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;
|
|
1640
|
-
if (wrapper.style.transform !== transform) wrapper.style.transform = transform;
|
|
1641
|
-
if (wrapper.style.getPropertyValue("--radix-popper-transform-origin") !== "100% 100%") {
|
|
1642
|
-
wrapper.style.setProperty("--radix-popper-transform-origin", "100% 100%");
|
|
1643
|
-
}
|
|
1644
|
-
const record = modelSelectorPositionObservers.get(wrapper);
|
|
1645
|
-
if (record) record.lastAppliedStyle = wrapper.getAttribute("style") ?? "";
|
|
1646
|
-
requestAnimationFrame(() => {
|
|
1647
|
-
if (!menu.isConnected) return;
|
|
1648
|
-
const positionedRect = wrapper.getBoundingClientRect();
|
|
1649
|
-
if (
|
|
1650
|
-
Math.abs(positionedRect.right - parentRect.right) <= 2
|
|
1651
|
-
&& Math.abs(positionedRect.bottom - parentRect.bottom) <= 2
|
|
1652
|
-
) {
|
|
1653
|
-
menu.setAttribute("data-codex-model-slider-positioned", "");
|
|
1654
|
-
menu.style.removeProperty("visibility");
|
|
1655
|
-
}
|
|
1656
|
-
});
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
function observeModelSubmenuPosition(menu) {
|
|
1660
|
-
const wrapper = menu?.parentElement;
|
|
1661
|
-
if (!wrapper?.hasAttribute("data-radix-popper-content-wrapper")) return;
|
|
1662
|
-
const existing = modelSelectorPositionObservers.get(wrapper);
|
|
1663
|
-
if (existing) {
|
|
1664
|
-
if (existing.menu !== menu) {
|
|
1665
|
-
existing.resizeObserver.unobserve(existing.menu);
|
|
1666
|
-
existing.resizeObserver.observe(menu);
|
|
1667
|
-
existing.menu = menu;
|
|
1668
|
-
}
|
|
1669
|
-
return;
|
|
1670
|
-
}
|
|
1671
|
-
const record = {
|
|
1672
|
-
menu,
|
|
1673
|
-
queued: false,
|
|
1674
|
-
observer: null,
|
|
1675
|
-
resizeObserver: null,
|
|
1676
|
-
lastAppliedStyle: null,
|
|
1677
|
-
};
|
|
1678
|
-
const schedule = (mutations = []) => {
|
|
1679
|
-
if (
|
|
1680
|
-
mutations.length
|
|
1681
|
-
&& wrapper.getAttribute("style") === record.lastAppliedStyle
|
|
1682
|
-
) return;
|
|
1683
|
-
if (record.queued) return;
|
|
1684
|
-
record.queued = true;
|
|
1685
|
-
requestAnimationFrame(() => {
|
|
1686
|
-
record.queued = false;
|
|
1687
|
-
if (record.menu.isConnected) positionModelSubmenu(record.menu);
|
|
1688
|
-
});
|
|
1689
|
-
};
|
|
1690
|
-
record.observer = new MutationObserver(schedule);
|
|
1691
|
-
record.observer.observe(wrapper, { attributes: true, attributeFilter: ["style"] });
|
|
1692
|
-
record.resizeObserver = new ResizeObserver(schedule);
|
|
1693
|
-
record.resizeObserver.observe(menu);
|
|
1694
|
-
const trigger = document.getElementById(menu.getAttribute("aria-labelledby") ?? "");
|
|
1695
|
-
const parentMenu = trigger?.closest('[role="menu"]');
|
|
1696
|
-
if (parentMenu) record.resizeObserver.observe(parentMenu);
|
|
1697
|
-
modelSelectorPositionObservers.set(wrapper, record);
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
function pruneModelSubmenuPositionObservers() {
|
|
1701
|
-
for (const [wrapper, record] of modelSelectorPositionObservers) {
|
|
1702
|
-
if (wrapper.isConnected) continue;
|
|
1703
|
-
record.observer.disconnect();
|
|
1704
|
-
record.resizeObserver.disconnect();
|
|
1705
|
-
modelSelectorPositionObservers.delete(wrapper);
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
function pruneNativeModelToggleHandlers() {
|
|
1710
|
-
for (const [toggle, handlers] of modelSelectorNativeToggleHandlers) {
|
|
1711
|
-
if (toggle.isConnected) continue;
|
|
1712
|
-
toggle.removeEventListener("click", handlers.openModels);
|
|
1713
|
-
toggle.removeEventListener("keydown", handlers.onKeyDown);
|
|
1714
|
-
modelSelectorNativeToggleHandlers.delete(toggle);
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
function restoreGenericModelPicker(container) {
|
|
1719
|
-
const original = modelSelectorGenericContainers.get(container);
|
|
1720
|
-
if (!original) return;
|
|
1721
|
-
original.effectMirror?.disconnect();
|
|
1722
|
-
if (container.isConnected) {
|
|
1723
|
-
const menu = container.parentElement;
|
|
1724
|
-
menu?.querySelector(":scope > [data-codex-model-slider-generic]")?.remove();
|
|
1725
|
-
menu?.removeAttribute("data-codex-model-slider-generic-menu");
|
|
1726
|
-
menu?.style.removeProperty("--codex-model-slider-menu-center-shift");
|
|
1727
|
-
container.style.position = original.position;
|
|
1728
|
-
container.style.inset = original.inset;
|
|
1729
|
-
container.style.opacity = original.opacity;
|
|
1730
|
-
container.style.pointerEvents = original.pointerEvents;
|
|
1731
|
-
if (original.ariaHidden == null) container.removeAttribute("aria-hidden");
|
|
1732
|
-
else container.setAttribute("aria-hidden", original.ariaHidden);
|
|
1733
|
-
container.inert = original.inert;
|
|
1734
|
-
}
|
|
1735
|
-
modelSelectorGenericContainers.delete(container);
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
function pruneGenericModelPickerContainers() {
|
|
1739
|
-
for (const container of modelSelectorGenericContainers.keys()) {
|
|
1740
|
-
if (!container.isConnected) restoreGenericModelPicker(container);
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
|
-
function closeControllerModelMenu() {
|
|
1745
|
-
const record = modelSelectorControllerMenu;
|
|
1746
|
-
if (!record) return false;
|
|
1747
|
-
const placeholder = document.querySelector(
|
|
1748
|
-
'[data-codex-intelligence-trigger] [class*="_ModelPickerTriggerPlaceholder_"]',
|
|
1749
|
-
);
|
|
1750
|
-
if (placeholder?.firstChild && record.placeholderText != null) {
|
|
1751
|
-
placeholder.firstChild.nodeValue = record.placeholderText;
|
|
1752
|
-
}
|
|
1753
|
-
record.button.setAttribute("aria-expanded", "false");
|
|
1754
|
-
record.parentMenu.style.position = record.position;
|
|
1755
|
-
record.parentMenu.style.overflow = record.overflow;
|
|
1756
|
-
record.menu.remove();
|
|
1757
|
-
modelSelectorControllerMenu = null;
|
|
1758
|
-
return true;
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
function controllerModelReasoningEffort(controller, model) {
|
|
1762
|
-
const current = String(controller?.reasoningEffort ?? "").toLowerCase();
|
|
1763
|
-
const supported = (model?.supportedReasoningEfforts ?? []).map((entry) => (
|
|
1764
|
-
String(entry?.reasoningEffort ?? entry ?? "").toLowerCase()
|
|
1765
|
-
)).filter(Boolean);
|
|
1766
|
-
return supported.includes(current)
|
|
1767
|
-
? current
|
|
1768
|
-
: String(model?.defaultReasoningEffort ?? supported[0] ?? "none").toLowerCase();
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
function visibleControllerModels(controller) {
|
|
1772
|
-
const models = controller.models.filter((model) => !model.hidden && String(model.model ?? "").trim());
|
|
1773
|
-
const nativeGptIds = new Set(models
|
|
1774
|
-
.filter((model) => !String(model.model).includes("/") && /^gpt-/i.test(model.model))
|
|
1775
|
-
.map((model) => String(model.model).toLowerCase()));
|
|
1776
|
-
// OCX 自动发现的同名路由若没有独立档位,会与原生 GPT 显示成两项。
|
|
1777
|
-
return models.filter((model) => {
|
|
1778
|
-
const slug = String(model.model);
|
|
1779
|
-
const backend = modelIdentity(slug).backend.toLowerCase();
|
|
1780
|
-
return slug === controller.model
|
|
1781
|
-
|| !(
|
|
1782
|
-
slug.includes("/")
|
|
1783
|
-
&& nativeGptIds.has(backend)
|
|
1784
|
-
&& !(model.supportedReasoningEfforts ?? []).length
|
|
1785
|
-
);
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
function openControllerModelMenu(parentMenu, modelButton, selector) {
|
|
1790
|
-
if (modelSelectorControllerMenu) {
|
|
1791
|
-
closeControllerModelMenu();
|
|
1792
|
-
return;
|
|
1793
|
-
}
|
|
1794
|
-
const controller = nativeComposerModelController();
|
|
1795
|
-
if (!Array.isArray(controller?.models) || typeof controller.onSelectModel !== "function") return;
|
|
1796
|
-
const models = visibleControllerModels(controller);
|
|
1797
|
-
if (!models.length) return;
|
|
1798
|
-
|
|
1799
|
-
const menu = document.createElement("div");
|
|
1800
|
-
menu.className = parentMenu.className;
|
|
1801
|
-
menu.setAttribute("role", "menu");
|
|
1802
|
-
menu.setAttribute("data-state", "open");
|
|
1803
|
-
menu.setAttribute("data-codex-model-slider-menu", "");
|
|
1804
|
-
menu.setAttribute("data-codex-model-slider-controller-menu", "");
|
|
1805
|
-
models.forEach((model, index) => {
|
|
1806
|
-
const item = document.createElement("div");
|
|
1807
|
-
item.className = "no-drag outline-hidden rounded-lg px-[var(--padding-row-x)] py-[var(--padding-row-y)] text-sm text-default group hover:bg-primary-ghost-hover focus:bg-primary-ghost-hover cursor-interaction flex flex-col";
|
|
1808
|
-
item.setAttribute("role", "menuitem");
|
|
1809
|
-
item.setAttribute("tabindex", "-1");
|
|
1810
|
-
enhanceModelItem(item, menu, selector, index, {
|
|
1811
|
-
rawValue: String(model.model),
|
|
1812
|
-
selected: String(model.model) === String(controller.model),
|
|
1813
|
-
scheduleReturn: false,
|
|
1814
|
-
owned: true,
|
|
1815
|
-
});
|
|
1816
|
-
menu.append(item);
|
|
1817
|
-
});
|
|
1818
|
-
menu.addEventListener("click", (event) => {
|
|
1819
|
-
const star = event.target.closest?.("[data-codex-model-slider-star]");
|
|
1820
|
-
const item = event.target.closest?.("[data-codex-model-slider-item]");
|
|
1821
|
-
if (!item) return;
|
|
1822
|
-
if (star) {
|
|
1823
|
-
const raw = item.dataset.codexModelSliderRaw;
|
|
1824
|
-
const label = item.querySelector("[data-codex-model-slider-name]")?.textContent ?? raw;
|
|
1825
|
-
toggleModelFavorite(event, menu, selector, raw, label);
|
|
1826
|
-
return;
|
|
1827
|
-
}
|
|
1828
|
-
const currentController = nativeComposerModelController();
|
|
1829
|
-
const raw = item.dataset.codexModelSliderRaw;
|
|
1830
|
-
const model = currentController?.models?.find((candidate) => String(candidate.model) === raw);
|
|
1831
|
-
if (!model || typeof currentController.onSelectModel !== "function") return;
|
|
1832
|
-
const reasoningEffort = controllerModelReasoningEffort(currentController, model);
|
|
1833
|
-
currentController.onSelectModel(raw, reasoningEffort);
|
|
1834
|
-
closeControllerModelMenu();
|
|
1835
|
-
queueEnsure();
|
|
1836
|
-
});
|
|
1837
|
-
menu.addEventListener("keydown", (event) => {
|
|
1838
|
-
if (event.key === "Escape") {
|
|
1839
|
-
event.preventDefault();
|
|
1840
|
-
closeControllerModelMenu();
|
|
1841
|
-
modelButton.focus();
|
|
1842
|
-
return;
|
|
1843
|
-
}
|
|
1844
|
-
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1845
|
-
const item = event.target.closest?.("[data-codex-model-slider-item]");
|
|
1846
|
-
if (!item) return;
|
|
1847
|
-
event.preventDefault();
|
|
1848
|
-
item.click();
|
|
1849
|
-
});
|
|
1850
|
-
renderEnhancedModelMenu(menu, selector);
|
|
1851
|
-
|
|
1852
|
-
const placeholder = document.querySelector(
|
|
1853
|
-
'[data-codex-intelligence-trigger] [class*="_ModelPickerTriggerPlaceholder_"]',
|
|
1854
|
-
);
|
|
1855
|
-
const placeholderText = placeholder?.firstChild?.nodeValue ?? null;
|
|
1856
|
-
// 保留产品行为:模型列表打开时 chip 显示 Select model。只能原地更新
|
|
1857
|
-
// 同一个文本节点(nodeValue),不能 textContent 替换整个节点,否则
|
|
1858
|
-
// React 卸载菜单时 removeChild 找不到旧文本节点会触发错误边界。
|
|
1859
|
-
if (placeholder?.firstChild) {
|
|
1860
|
-
placeholder.firstChild.nodeValue = locale === "zh-CN" ? "选择模型" : "Select model";
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
modelSelectorControllerMenu = {
|
|
1864
|
-
menu,
|
|
1865
|
-
parentMenu,
|
|
1866
|
-
button: modelButton,
|
|
1867
|
-
placeholderText,
|
|
1868
|
-
position: parentMenu.style.position,
|
|
1869
|
-
overflow: parentMenu.style.overflow,
|
|
1870
|
-
};
|
|
1871
|
-
parentMenu.style.position = "relative";
|
|
1872
|
-
parentMenu.style.overflow = "visible";
|
|
1873
|
-
parentMenu.append(menu);
|
|
1874
|
-
sizeModelMenuViewport(menu, selector.maxVisibleItems);
|
|
1875
|
-
modelButton.setAttribute("aria-expanded", "true");
|
|
1876
|
-
(menu.querySelector('[data-codex-model-slider-selected]')
|
|
1877
|
-
?? menu.querySelector('[role="menuitem"]'))?.focus();
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
// ========================================================================
|
|
1881
|
-
// 原生 effort 子菜单与滑条视觉 — effort 事务、Fast/通用滑条视觉与状态同步
|
|
1882
|
-
// ========================================================================
|
|
1883
|
-
|
|
1884
|
-
function nativeEffortRow(parentMenu) {
|
|
1885
|
-
return Array.from(parentMenu?.querySelectorAll('[role="menuitem"][aria-haspopup="menu"]') ?? [])
|
|
1886
|
-
.filter((row) => !row.hasAttribute("data-model-picker-view-toggle"))[1] ?? null;
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
function effortLabel(effort) {
|
|
1890
|
-
return new Map([
|
|
1891
|
-
["none", "None"], ["minimal", "Minimal"], ["low", "Light"], ["medium", "Medium"],
|
|
1892
|
-
["high", "High"], ["xhigh", "Extra High"], ["max", "Max"], ["ultra", "Ultra"],
|
|
1893
|
-
]).get(String(effort).toLowerCase()) ?? formatIdentifier(effort);
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
function selectNativeEffort(parentMenu, effort) {
|
|
1897
|
-
const identity = modelIdentity(nativeModelValue(parentMenu));
|
|
1898
|
-
const model = modelDefinitionFor(identity, modelSelectorFeature?.modelSelector);
|
|
1899
|
-
if (!model?.supportedReasoningLevels?.some((level) => level.effort === String(effort).toLowerCase())) return;
|
|
1900
|
-
const controller = nativeComposerModelController();
|
|
1901
|
-
if (typeof controller?.onSelectReasoningEffort === "function") {
|
|
1902
|
-
controller.onSelectReasoningEffort(effort);
|
|
1903
|
-
if (modelSelectorPendingOutsideDismiss) {
|
|
1904
|
-
closeReactControlledMenu(nativeComposerTrigger());
|
|
1905
|
-
modelSelectorPendingOutsideDismiss = false;
|
|
1906
|
-
}
|
|
1907
|
-
queueEnsure();
|
|
1908
|
-
return;
|
|
1909
|
-
}
|
|
1910
|
-
if (typeof controller?.onCommitPower === "function") {
|
|
1911
|
-
const normalizedEffort = String(effort).toLowerCase();
|
|
1912
|
-
const activeModel = String(controller.model ?? nativeCatalogModelFor(identity)?.slug ?? identity.raw);
|
|
1913
|
-
const next = controller.powerSelections?.find((selection) => (
|
|
1914
|
-
String(selection?.model ?? "") === activeModel
|
|
1915
|
-
&& String(selection?.reasoningEffort ?? "").toLowerCase() === normalizedEffort
|
|
1916
|
-
));
|
|
1917
|
-
const previous = controller.selectedPowerSelection
|
|
1918
|
-
?? controller.powerSelections?.find((selection) => (
|
|
1919
|
-
String(selection?.model ?? "") === activeModel
|
|
1920
|
-
&& String(selection?.reasoningEffort ?? "").toLowerCase()
|
|
1921
|
-
=== String(controller.reasoningEffort ?? "").toLowerCase()
|
|
1922
|
-
))
|
|
1923
|
-
?? controller.fallbackPowerSelection;
|
|
1924
|
-
if (!next || !previous) return;
|
|
1925
|
-
controller.onSelectPower?.(next);
|
|
1926
|
-
controller.onCommitPower(next, previous);
|
|
1927
|
-
if (modelSelectorPendingOutsideDismiss) {
|
|
1928
|
-
closeReactControlledMenu(nativeComposerTrigger());
|
|
1929
|
-
modelSelectorPendingOutsideDismiss = false;
|
|
1930
|
-
}
|
|
1931
|
-
queueEnsure();
|
|
1932
|
-
return;
|
|
1933
|
-
}
|
|
1934
|
-
const row = nativeEffortRow(parentMenu);
|
|
1935
|
-
if (!row) return;
|
|
1936
|
-
beginNativeEffortTransaction();
|
|
1937
|
-
if (!setReactControlledMenuOpen(row, true)) {
|
|
1938
|
-
endNativeEffortTransaction();
|
|
1939
|
-
return;
|
|
1940
|
-
}
|
|
1941
|
-
let observer = null;
|
|
1942
|
-
let selectionTimer = null;
|
|
1943
|
-
const select = () => {
|
|
1944
|
-
const submenu = nativeEffortSubmenu();
|
|
1945
|
-
const desired = effortLabel(effort).toLowerCase();
|
|
1946
|
-
const item = modelMenuItems(submenu).find((candidate) => (
|
|
1947
|
-
nativeMenuItemLabel(candidate).toLowerCase() === desired
|
|
1948
|
-
));
|
|
1949
|
-
if (!item) return;
|
|
1950
|
-
observer?.disconnect();
|
|
1951
|
-
clearTimeout(selectionTimer);
|
|
1952
|
-
const dismissAfterCommit = modelSelectorPendingOutsideDismiss;
|
|
1953
|
-
if (!invokeNativeMenuItemSelect(item)) {
|
|
1954
|
-
endNativeEffortTransaction();
|
|
1955
|
-
queueEnsure();
|
|
1956
|
-
return;
|
|
1957
|
-
}
|
|
1958
|
-
if (dismissAfterCommit) {
|
|
1959
|
-
closeReactControlledMenu(item);
|
|
1960
|
-
closeReactControlledMenu(nativeComposerTrigger());
|
|
1961
|
-
modelSelectorPendingReturn = false;
|
|
1962
|
-
endNativeEffortTransaction();
|
|
1963
|
-
return;
|
|
1964
|
-
}
|
|
1965
|
-
scheduleModelSliderReturn();
|
|
1966
|
-
};
|
|
1967
|
-
observer = new MutationObserver(select);
|
|
1968
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
1969
|
-
selectionTimer = setTimeout(() => {
|
|
1970
|
-
observer.disconnect();
|
|
1971
|
-
endNativeEffortTransaction();
|
|
1972
|
-
queueEnsure();
|
|
1973
|
-
}, 1_500);
|
|
1974
|
-
select();
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
function setGenericSliderVisual(rail, index, count) {
|
|
1978
|
-
const progress = count <= 1 ? 0 : (index / (count - 1)) * 100;
|
|
1979
|
-
const nativeRange = rail.querySelector("[data-codex-model-slider-native-range]");
|
|
1980
|
-
const nativeThumb = rail.querySelector("[data-codex-model-slider-native-thumb]");
|
|
1981
|
-
if (nativeRange && nativeThumb) {
|
|
1982
|
-
const thumbRadius = 13;
|
|
1983
|
-
const correction = thumbRadius - (progress / 50) * thumbRadius;
|
|
1984
|
-
const segment = count <= 1 ? 100 : 100 / (count - 1);
|
|
1985
|
-
const edgeScale = Math.min(progress / segment, (100 - progress) / segment, 1);
|
|
1986
|
-
const edgeCorrection = correction * Math.max(0, edgeScale);
|
|
1987
|
-
nativeRange.style.transform = `translateX(calc(${progress - 100}% + ${edgeCorrection}px))`;
|
|
1988
|
-
nativeThumb.style.left = `calc(${progress}% + ${correction}px)`;
|
|
1989
|
-
const particleClip = rail.querySelector('[class*="_FastParticleClip_"]');
|
|
1990
|
-
if (particleClip) particleClip.style.clipPath = `inset(0 calc(${100 - progress}% - ${edgeCorrection}px) 0 0)`;
|
|
1991
|
-
}
|
|
1992
|
-
rail.style.setProperty("--codex-model-slider-progress", `${progress}%`);
|
|
1993
|
-
rail.querySelectorAll("[data-codex-model-slider-tick]").forEach((tick, tickIndex) => {
|
|
1994
|
-
tick.setAttribute("data-selected", String(tickIndex <= index));
|
|
1995
|
-
});
|
|
1996
|
-
rail.setAttribute("aria-valuenow", String(index));
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
function syncGenericMaximumVisual(rail, effortText, fastButton, maximum) {
|
|
2000
|
-
const value = String(maximum);
|
|
2001
|
-
rail?.setAttribute("data-highlighted", value);
|
|
2002
|
-
rail?.setAttribute("data-max", value);
|
|
2003
|
-
effortText?.setAttribute("data-maximum", value);
|
|
2004
|
-
fastButton?.setAttribute("data-max-power-selection", value);
|
|
2005
|
-
const maxEffects = rail?.querySelector('[class*="_MaxEffects_"]');
|
|
2006
|
-
if (maxEffects) {
|
|
2007
|
-
maxEffects.style.opacity = maximum ? "1" : "0";
|
|
2008
|
-
if (maximum) maxEffects.style.transform = "translateX(0)";
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
function neutralizeStaleSliderVisual(rail) {
|
|
2013
|
-
for (const selector of [
|
|
2014
|
-
"[data-codex-model-slider-native-range]",
|
|
2015
|
-
"[data-codex-model-slider-native-thumb]",
|
|
2016
|
-
"[data-codex-model-slider-range]",
|
|
2017
|
-
"[data-codex-model-slider-thumb]",
|
|
2018
|
-
]) {
|
|
2019
|
-
rail.querySelectorAll(selector).forEach((element) => {
|
|
2020
|
-
element.style.setProperty("visibility", "hidden");
|
|
2021
|
-
});
|
|
2022
|
-
}
|
|
2023
|
-
rail.querySelectorAll("[data-codex-model-slider-tick]").forEach((tick) => {
|
|
2024
|
-
tick.removeAttribute("data-selected");
|
|
2025
|
-
});
|
|
2026
|
-
rail.removeAttribute("aria-valuenow");
|
|
2027
|
-
rail.style.removeProperty("--codex-model-slider-progress");
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
function nativeParticleSeed(index, salt) {
|
|
2031
|
-
const value = Math.sin((index + 1) * 12.9898 + salt * 78.233) * 43758.5453;
|
|
2032
|
-
return value - Math.floor(value);
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
function syncNativeFastParticles(rail, enabled, entering = false) {
|
|
2036
|
-
const track = Array.from(rail.children).find((element) => String(element.className).includes("_Track_"));
|
|
2037
|
-
if (!track) return;
|
|
2038
|
-
const staticParticles = rail.querySelector('[class*="_ParticleTransition_"]');
|
|
2039
|
-
if (staticParticles && !matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
2040
|
-
staticParticles.__codexStaticParticleFade?.cancel();
|
|
2041
|
-
staticParticles.style.opacity = enabled ? "0" : "1";
|
|
2042
|
-
if (entering) {
|
|
2043
|
-
// 动态粒子渐显时,让原有星点在同一时间淡出。
|
|
2044
|
-
staticParticles.__codexStaticParticleFade = staticParticles.animate(
|
|
2045
|
-
[{ opacity: 1 }, { opacity: 0 }],
|
|
2046
|
-
{ duration: 240, easing: "ease-out" },
|
|
2047
|
-
);
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
track.querySelectorAll('[class*="_FastParticleClip_"]').forEach((element) => element.remove());
|
|
2051
|
-
if (!enabled || matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
2052
|
-
const clipClass = nativeCssModuleClass("FastParticleClip");
|
|
2053
|
-
const particlesClass = nativeCssModuleClass("FastTrackParticles");
|
|
2054
|
-
const pathClass = nativeCssModuleClass("FastTrackParticlePath");
|
|
2055
|
-
const particleClass = nativeCssModuleClass("TrackParticle");
|
|
2056
|
-
if (!clipClass || !particlesClass || !pathClass || !particleClass) return;
|
|
2057
|
-
const clip = document.createElement("span");
|
|
2058
|
-
clip.className = clipClass;
|
|
2059
|
-
clip.setAttribute("aria-hidden", "true");
|
|
2060
|
-
clip.style.opacity = "1";
|
|
2061
|
-
const particles = document.createElement("span");
|
|
2062
|
-
particles.className = particlesClass;
|
|
2063
|
-
particles.setAttribute("aria-hidden", "true");
|
|
2064
|
-
particles.setAttribute("data-animation-active", "true");
|
|
2065
|
-
const travelDuration = 1.9;
|
|
2066
|
-
const stagger = travelDuration / 14;
|
|
2067
|
-
const valueMax = Number(rail.getAttribute("aria-valuemax") ?? 0);
|
|
2068
|
-
const valueNow = Number(rail.getAttribute("aria-valuenow") ?? 0);
|
|
2069
|
-
const initialStartPercent = valueMax > 0 ? (valueNow / valueMax) * 100 : 0;
|
|
2070
|
-
const remainingTravel = 1 - Math.min(Math.max(initialStartPercent, 0), 100) / 100;
|
|
2071
|
-
particles.append(...Array.from({ length: 14 }, (_, index) => {
|
|
2072
|
-
const speed = 1 + (nativeParticleSeed(index, 21) - 0.5) * 0.4;
|
|
2073
|
-
const duration = travelDuration / speed;
|
|
2074
|
-
const path = document.createElement("span");
|
|
2075
|
-
path.className = pathClass;
|
|
2076
|
-
path.style.animationDelay = `${index * stagger - duration * remainingTravel}s`;
|
|
2077
|
-
path.style.animationDuration = `${duration}s`;
|
|
2078
|
-
path.style.top = `${12 + nativeParticleSeed(index, 23) * 76}%`;
|
|
2079
|
-
const particle = document.createElement("span");
|
|
2080
|
-
particle.className = particleClass;
|
|
2081
|
-
particle.style.opacity = String(0.4 + nativeParticleSeed(index, 11) * 0.6);
|
|
2082
|
-
particle.style.transform = `translate(-50%, -50%) scale(${0.5 + nativeParticleSeed(index, 12) * 0.45})`;
|
|
2083
|
-
path.append(particle);
|
|
2084
|
-
return path;
|
|
2085
|
-
}));
|
|
2086
|
-
clip.append(particles);
|
|
2087
|
-
const tickRail = Array.from(track.children).find((element) => String(element.className).includes("_TickRail_"));
|
|
2088
|
-
track.insertBefore(clip, tickRail ?? null);
|
|
2089
|
-
if (entering) {
|
|
2090
|
-
// 首帧沿用关闭 Fast 时的星点,再渐显流动粒子。
|
|
2091
|
-
clip.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 240, easing: "ease-out" });
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
function syncGenericFastVisual(shell, enabled, maxPowerSelection, animate = false) {
|
|
2096
|
-
const button = shell.querySelector("[data-codex-model-slider-fast]");
|
|
2097
|
-
const rail = shell.querySelector("[data-codex-model-slider-rail]");
|
|
2098
|
-
if (!button || !rail) return;
|
|
2099
|
-
button.setAttribute("aria-pressed", String(enabled));
|
|
2100
|
-
button.setAttribute("data-fast-mode-enabled", String(enabled));
|
|
2101
|
-
syncGenericMaximumVisual(
|
|
2102
|
-
rail,
|
|
2103
|
-
shell.querySelector("[data-codex-model-slider-effort-row] [data-maximum]"),
|
|
2104
|
-
button,
|
|
2105
|
-
maxPowerSelection,
|
|
2106
|
-
);
|
|
2107
|
-
button.setAttribute("aria-label", enabled
|
|
2108
|
-
? (locale === "zh-CN" ? "关闭 Fast 模式" : "Disable Fast mode")
|
|
2109
|
-
: (locale === "zh-CN" ? "开启 Fast 模式" : "Enable Fast mode"));
|
|
2110
|
-
const icon = createNativeFastIcon(enabled);
|
|
2111
|
-
const content = button.querySelector("[data-codex-model-slider-fast-content]");
|
|
2112
|
-
if (icon && content) content.replaceChildren(icon);
|
|
2113
|
-
rail.setAttribute("data-fast-mode", String(enabled));
|
|
2114
|
-
rail.setAttribute("data-fast-mode-dot-transition", animate ? (enabled ? "entering" : "exiting") : (enabled ? "active" : "inactive"));
|
|
2115
|
-
syncNativeFastParticles(rail, enabled, animate && enabled);
|
|
2116
|
-
setGenericSliderVisual(rail, Number(rail.getAttribute("aria-valuenow") ?? 0), Number(rail.getAttribute("aria-valuemax") ?? 0) + 1);
|
|
2117
|
-
if (animate && enabled) {
|
|
2118
|
-
shell.setAttribute("data-codex-model-slider-fast-transitioning", "");
|
|
2119
|
-
setTimeout(() => {
|
|
2120
|
-
if (!shell.isConnected) return;
|
|
2121
|
-
shell.removeAttribute("data-codex-model-slider-fast-transitioning");
|
|
2122
|
-
queueEnsure();
|
|
2123
|
-
}, 260);
|
|
2124
|
-
} else {
|
|
2125
|
-
shell.removeAttribute("data-codex-model-slider-fast-transitioning");
|
|
2126
|
-
}
|
|
2127
|
-
if (animate) {
|
|
2128
|
-
setTimeout(() => {
|
|
2129
|
-
if (rail.isConnected && rail.getAttribute("data-fast-mode") === String(enabled)) {
|
|
2130
|
-
rail.setAttribute("data-fast-mode-dot-transition", enabled ? "active" : "inactive");
|
|
2131
|
-
}
|
|
2132
|
-
}, enabled ? 1_200 : 350);
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
function cloneNativePowerSlider(parentMenu, count, fastEnabled) {
|
|
2137
|
-
const template = parentMenu.firstElementChild?.querySelector("[data-model-picker-power-slider]");
|
|
2138
|
-
if (!template) return null;
|
|
2139
|
-
const clone = template.cloneNode(true);
|
|
2140
|
-
clone.setAttribute("data-codex-model-slider-native-slider", "");
|
|
2141
|
-
clone.removeAttribute("data-keyboard-focused");
|
|
2142
|
-
const root = clone.firstElementChild;
|
|
2143
|
-
if (!root) return null;
|
|
2144
|
-
const nativeInput = clone.querySelector('[role="slider"]');
|
|
2145
|
-
nativeInput?.removeAttribute("role");
|
|
2146
|
-
nativeInput?.removeAttribute("tabindex");
|
|
2147
|
-
nativeInput?.removeAttribute("aria-valuemin");
|
|
2148
|
-
nativeInput?.removeAttribute("aria-valuemax");
|
|
2149
|
-
nativeInput?.removeAttribute("aria-valuenow");
|
|
2150
|
-
root.setAttribute("role", "slider");
|
|
2151
|
-
root.removeAttribute("aria-hidden");
|
|
2152
|
-
root.removeAttribute("data-disabled");
|
|
2153
|
-
root.removeAttribute("disabled");
|
|
2154
|
-
root.setAttribute("tabindex", "0");
|
|
2155
|
-
root.setAttribute("aria-valuemax", String(Math.max(0, count - 1)));
|
|
2156
|
-
root.setAttribute("data-fast-mode", String(fastEnabled));
|
|
2157
|
-
root.setAttribute("data-fast-mode-dot-transition", fastEnabled ? "active" : "inactive");
|
|
2158
|
-
root.setAttribute("data-max", "false");
|
|
2159
|
-
root.setAttribute("data-endpoint-labels-visible", "false");
|
|
2160
|
-
const range = Array.from(root.querySelectorAll("span")).find((element) => (
|
|
2161
|
-
String(element.className).includes("_Range_")
|
|
2162
|
-
));
|
|
2163
|
-
const thumbScale = Array.from(root.querySelectorAll("span")).find((element) => (
|
|
2164
|
-
String(element.className).includes("_ThumbScale_")
|
|
2165
|
-
));
|
|
2166
|
-
const tickRail = Array.from(root.querySelectorAll("div")).find((element) => (
|
|
2167
|
-
String(element.className).includes("_TickRail_")
|
|
2168
|
-
));
|
|
2169
|
-
const tickTemplate = tickRail?.querySelector("span");
|
|
2170
|
-
if (!range || !thumbScale || !tickRail || !tickTemplate) return null;
|
|
2171
|
-
range.setAttribute("data-codex-model-slider-native-range", "");
|
|
2172
|
-
thumbScale.setAttribute("data-codex-model-slider-native-thumb", "");
|
|
2173
|
-
tickRail.replaceChildren(...Array.from({ length: count }, (_, index) => {
|
|
2174
|
-
const tick = tickTemplate.cloneNode(true);
|
|
2175
|
-
tick.setAttribute("data-codex-model-slider-tick", "");
|
|
2176
|
-
const progress = count <= 1 ? 0 : (index / (count - 1)) * 100;
|
|
2177
|
-
const correction = 13 - (progress / 50) * 13;
|
|
2178
|
-
tick.style.left = `calc(${progress}% + ${correction}px)`;
|
|
2179
|
-
return tick;
|
|
2180
|
-
}));
|
|
2181
|
-
syncNativeFastParticles(root, fastEnabled);
|
|
2182
|
-
return clone;
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
function syncClonedPowerEffects(nativeContainer, shell) {
|
|
2186
|
-
const source = nativeContainer.querySelector("[data-model-picker-power-slider]");
|
|
2187
|
-
const clone = shell.querySelector("[data-codex-model-slider-native-slider]");
|
|
2188
|
-
if (!source || !clone) return null;
|
|
2189
|
-
const sourceCanvas = source.querySelector("canvas");
|
|
2190
|
-
const cloneCanvas = clone.querySelector("canvas");
|
|
2191
|
-
let canvasTimer = null;
|
|
2192
|
-
let observer = null;
|
|
2193
|
-
const disconnect = () => {
|
|
2194
|
-
observer?.disconnect();
|
|
2195
|
-
if (canvasTimer != null) clearInterval(canvasTimer);
|
|
2196
|
-
};
|
|
2197
|
-
if (sourceCanvas && cloneCanvas) {
|
|
2198
|
-
// 原生 canvas 可能在首次克隆时尚未绘制;持续同步其高光纹理。
|
|
2199
|
-
const scratch = document.createElement("canvas");
|
|
2200
|
-
const scratchContext = scratch.getContext("2d");
|
|
2201
|
-
const cloneContext = cloneCanvas.getContext("2d");
|
|
2202
|
-
const copyCanvas = () => {
|
|
2203
|
-
if (!source.isConnected || !shell.isConnected) {
|
|
2204
|
-
disconnect();
|
|
2205
|
-
return;
|
|
2206
|
-
}
|
|
2207
|
-
if (scratch.width !== sourceCanvas.width || scratch.height !== sourceCanvas.height) {
|
|
2208
|
-
scratch.width = sourceCanvas.width;
|
|
2209
|
-
scratch.height = sourceCanvas.height;
|
|
2210
|
-
cloneCanvas.width = sourceCanvas.width;
|
|
2211
|
-
cloneCanvas.height = sourceCanvas.height;
|
|
2212
|
-
}
|
|
2213
|
-
scratchContext.clearRect(0, 0, scratch.width, scratch.height);
|
|
2214
|
-
scratchContext.drawImage(sourceCanvas, 0, 0);
|
|
2215
|
-
const y = Math.floor(scratch.height / 2);
|
|
2216
|
-
const hasDrawing = [0.25, 0.5, 0.75].some((ratio) => (
|
|
2217
|
-
scratchContext.getImageData(Math.floor(scratch.width * ratio), y, 1, 1).data[3] > 0
|
|
2218
|
-
));
|
|
2219
|
-
if (!hasDrawing) return;
|
|
2220
|
-
cloneContext.clearRect(0, 0, cloneCanvas.width, cloneCanvas.height);
|
|
2221
|
-
cloneContext.drawImage(scratch, 0, 0);
|
|
2222
|
-
};
|
|
2223
|
-
copyCanvas();
|
|
2224
|
-
canvasTimer = setInterval(copyCanvas, 80);
|
|
2225
|
-
}
|
|
2226
|
-
const sourceEffects = source.querySelector('[class*="_MaxEffects_"]');
|
|
2227
|
-
const cloneEffects = clone.querySelector('[class*="_MaxEffects_"]');
|
|
2228
|
-
if (!sourceEffects || !cloneEffects) return { disconnect };
|
|
2229
|
-
const sync = () => {
|
|
2230
|
-
if (!source.isConnected || !shell.isConnected) {
|
|
2231
|
-
disconnect();
|
|
2232
|
-
return;
|
|
2233
|
-
}
|
|
2234
|
-
const sourceTransition = sourceEffects.querySelector('[class*="_ParticleTransition_"]');
|
|
2235
|
-
let cloneTransition = cloneEffects.querySelector('[class*="_ParticleTransition_"]');
|
|
2236
|
-
if (!sourceTransition) {
|
|
2237
|
-
if (clone.querySelector("[data-fast-mode]")?.getAttribute("data-fast-mode") !== "true") {
|
|
2238
|
-
cloneTransition?.remove();
|
|
2239
|
-
}
|
|
2240
|
-
return;
|
|
2241
|
-
}
|
|
2242
|
-
const sourceParticles = sourceTransition.querySelectorAll('[class*="_TrackParticle_"]');
|
|
2243
|
-
let cloneParticles = cloneTransition?.querySelectorAll('[class*="_TrackParticle_"]');
|
|
2244
|
-
if (!cloneTransition || sourceParticles.length !== cloneParticles.length) {
|
|
2245
|
-
cloneTransition?.remove();
|
|
2246
|
-
cloneTransition = sourceTransition.cloneNode(true);
|
|
2247
|
-
cloneEffects.append(cloneTransition);
|
|
2248
|
-
cloneParticles = cloneTransition.querySelectorAll('[class*="_TrackParticle_"]');
|
|
2249
|
-
}
|
|
2250
|
-
cloneTransition.setAttribute("style", sourceTransition.getAttribute("style") ?? "");
|
|
2251
|
-
sourceParticles.forEach((particle, index) => {
|
|
2252
|
-
const style = particle.getAttribute("style") ?? "";
|
|
2253
|
-
if (cloneParticles[index].getAttribute("style") !== style) {
|
|
2254
|
-
cloneParticles[index].setAttribute("style", style);
|
|
2255
|
-
}
|
|
2256
|
-
});
|
|
2257
|
-
if (!matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
2258
|
-
cloneTransition.style.opacity = clone.querySelector("[data-fast-mode]")?.getAttribute("data-fast-mode") === "true" ? "0" : "1";
|
|
2259
|
-
}
|
|
2260
|
-
};
|
|
2261
|
-
observer = new MutationObserver(sync);
|
|
2262
|
-
observer.observe(sourceEffects, { attributes: true, attributeFilter: ["style"], childList: true, subtree: true });
|
|
2263
|
-
sync();
|
|
2264
|
-
return { disconnect };
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
|
-
function createGenericModelSlider(parentMenu, selector, identity, model, selectedEffort) {
|
|
2268
|
-
const efforts = model.supportedReasoningLevels.map((level) => level.effort);
|
|
2269
|
-
const selectedIndex = efforts.indexOf(selectedEffort);
|
|
2270
|
-
const selectedEffortVisible = selectedIndex >= 0;
|
|
2271
|
-
const classes = Object.fromEntries([
|
|
2272
|
-
"Menu",
|
|
2273
|
-
"ViewToggle",
|
|
2274
|
-
"ViewToggleContent",
|
|
2275
|
-
"ViewToggleIcon",
|
|
2276
|
-
"ViewToggleModelLabel",
|
|
2277
|
-
"ViewToggleEffortLabel",
|
|
2278
|
-
"ViewControls",
|
|
2279
|
-
"ViewPanel",
|
|
2280
|
-
"FastModeToggle",
|
|
2281
|
-
"FastModeToggleContent",
|
|
2282
|
-
"SimpleView",
|
|
2283
|
-
].map((tokenName) => [tokenName, nativeCssModuleClass(tokenName, parentMenu)]));
|
|
2284
|
-
if (Object.values(classes).some((name) => !name)) return null;
|
|
2285
|
-
const shell = document.createElement("div");
|
|
2286
|
-
shell.setAttribute("data-codex-model-slider-generic", `${identity.raw}:${selectedEffort}`);
|
|
2287
|
-
shell.className = classes.Menu;
|
|
2288
|
-
const panel = document.createElement("div");
|
|
2289
|
-
panel.className = classes.ViewPanel;
|
|
2290
|
-
shell.append(panel);
|
|
2291
|
-
shell.setAttribute("data-model-picker-view", "simple");
|
|
2292
|
-
shell.setAttribute("data-reduced-motion", String(matchMedia("(prefers-reduced-motion: reduce)").matches));
|
|
2293
|
-
shell.setAttribute("data-transitions-ready", "false");
|
|
2294
|
-
const nativeRow = nativeModelPickerRow(parentMenu);
|
|
2295
|
-
const controller = nativeComposerModelController();
|
|
2296
|
-
const fastOption = controller?.serviceTierOptions?.find((option) => option?.value === "priority") ?? null;
|
|
2297
|
-
const nativeModel = nativeCatalogModelFor(identity);
|
|
2298
|
-
const supportsFast = Boolean(fastOption && typeof controller.onSelectServiceTier === "function");
|
|
2299
|
-
shell.setAttribute("data-codex-model-slider-has-fast", String(supportsFast));
|
|
2300
|
-
const modelButton = document.createElement("div");
|
|
2301
|
-
modelButton.className = classes.ViewToggle;
|
|
2302
|
-
modelButton.setAttribute("role", "button");
|
|
2303
|
-
modelButton.setAttribute("tabindex", "0");
|
|
2304
|
-
modelButton.setAttribute("data-codex-model-slider-model-button", "");
|
|
2305
|
-
modelButton.setAttribute("aria-label", `Model ${modelDisplayLabel(identity, selector)}`);
|
|
2306
|
-
modelButton.setAttribute("aria-haspopup", "menu");
|
|
2307
|
-
modelButton.setAttribute("aria-expanded", "false");
|
|
2308
|
-
const buttonContent = document.createElement("span");
|
|
2309
|
-
buttonContent.className = classes.ViewToggleContent;
|
|
2310
|
-
// 上下两行独立计算宽度,长模型名不能撑开强度与箭头。
|
|
2311
|
-
buttonContent.style.display = "flex";
|
|
2312
|
-
buttonContent.style.flexDirection = "column";
|
|
2313
|
-
buttonContent.style.gap = "0";
|
|
2314
|
-
const effortRow = document.createElement("span");
|
|
2315
|
-
effortRow.setAttribute("data-codex-model-slider-effort-row", "");
|
|
2316
|
-
effortRow.style.display = "grid";
|
|
2317
|
-
effortRow.style.gridTemplateColumns = "calc(var(--spacing) * 4) auto calc(var(--spacing) * 4)";
|
|
2318
|
-
effortRow.style.alignItems = "center";
|
|
2319
|
-
const buttonLabel = document.createElement("span");
|
|
2320
|
-
buttonLabel.className = classes.ViewToggleModelLabel;
|
|
2321
|
-
buttonLabel.setAttribute("data-codex-model-slider-model-label", "");
|
|
2322
|
-
buttonLabel.textContent = modelDisplayLabel(identity, selector);
|
|
2323
|
-
const modelRow = document.createElement("span");
|
|
2324
|
-
modelRow.setAttribute("data-codex-model-slider-model-row", "");
|
|
2325
|
-
modelRow.style.display = "inline-flex";
|
|
2326
|
-
modelRow.style.alignItems = "center";
|
|
2327
|
-
modelRow.style.justifyContent = "center";
|
|
2328
|
-
modelRow.style.gap = "calc(var(--spacing) / 2)";
|
|
2329
|
-
modelRow.style.minWidth = "0";
|
|
2330
|
-
modelRow.style.maxWidth = "100%";
|
|
2331
|
-
modelRow.append(buttonLabel);
|
|
2332
|
-
const effortText = selectedEffortVisible ? document.createElement("span") : null;
|
|
2333
|
-
if (effortText) {
|
|
2334
|
-
effortText.className = classes.ViewToggleEffortLabel;
|
|
2335
|
-
// 原生标签会跨满整行;收回中列,使上下两行各自居中。
|
|
2336
|
-
effortText.style.gridColumn = "2";
|
|
2337
|
-
effortText.style.gridRow = "1";
|
|
2338
|
-
effortText.textContent = effortLabel(selectedEffort);
|
|
2339
|
-
effortText.setAttribute("data-accent", "true");
|
|
2340
|
-
effortText.setAttribute("data-effort-only", "false");
|
|
2341
|
-
effortText.setAttribute("data-maximum", String(selectedEffort === "ultra"));
|
|
2342
|
-
}
|
|
2343
|
-
// 0.152.1 起原生头行 aria-label 为 "Select model",nativeModelPickerRow
|
|
2344
|
-
// 不再命中;优先从 view-toggle 行取 chevron,旧结构走原路径兜底。
|
|
2345
|
-
const nativeChevron = (
|
|
2346
|
-
parentMenu?.querySelector("[data-model-picker-view-toggle] svg")
|
|
2347
|
-
?? nativeRow?.querySelector("svg")
|
|
2348
|
-
)?.cloneNode(true);
|
|
2349
|
-
if (nativeChevron) {
|
|
2350
|
-
nativeChevron.classList.add(classes.ViewToggleIcon);
|
|
2351
|
-
nativeChevron.style.gridArea = "auto";
|
|
2352
|
-
nativeChevron.style.flexShrink = "0";
|
|
2353
|
-
nativeChevron.setAttribute("aria-hidden", "true");
|
|
2354
|
-
modelRow.append(nativeChevron);
|
|
2355
|
-
}
|
|
2356
|
-
if (effortText) effortRow.append(effortText);
|
|
2357
|
-
buttonContent.append(effortRow, modelRow);
|
|
2358
|
-
modelButton.append(buttonContent);
|
|
2359
|
-
const openModels = () => openControllerModelMenu(parentMenu, modelButton, selector);
|
|
2360
|
-
modelButton.addEventListener("click", openModels);
|
|
2361
|
-
modelButton.addEventListener("keydown", (event) => {
|
|
2362
|
-
if (event.key !== "Enter" && event.key !== " ") return;
|
|
2363
|
-
event.preventDefault();
|
|
2364
|
-
openModels();
|
|
2365
|
-
});
|
|
2366
|
-
|
|
2367
|
-
const viewControls = document.createElement("div");
|
|
2368
|
-
viewControls.className = classes.ViewControls;
|
|
2369
|
-
// 沿用原生显式模型布局:上方档位,下方模型,两侧放置能力控件。
|
|
2370
|
-
viewControls.setAttribute("data-explicit-model", "true");
|
|
2371
|
-
viewControls.setAttribute("data-ultra-warning-visible", "false");
|
|
2372
|
-
const fastEnabled = Boolean(
|
|
2373
|
-
supportsFast && (
|
|
2374
|
-
controller.selectedServiceTier?.id === (fastOption.tier?.id ?? "priority")
|
|
2375
|
-
|| controller.selectedServiceTier === fastOption.value
|
|
2376
|
-
),
|
|
2377
|
-
);
|
|
2378
|
-
if (supportsFast) {
|
|
2379
|
-
const fastButton = document.createElement("button");
|
|
2380
|
-
fastButton.type = "button";
|
|
2381
|
-
fastButton.className = classes.FastModeToggle;
|
|
2382
|
-
fastButton.setAttribute("data-codex-model-slider-fast", "");
|
|
2383
|
-
fastButton.setAttribute("aria-pressed", String(fastEnabled));
|
|
2384
|
-
fastButton.setAttribute("data-fast-mode-enabled", String(fastEnabled));
|
|
2385
|
-
fastButton.setAttribute("data-max-power-selection", String(selectedEffort === "ultra"));
|
|
2386
|
-
fastButton.setAttribute("data-visible", "true");
|
|
2387
|
-
fastButton.setAttribute("aria-label", fastEnabled
|
|
2388
|
-
? (locale === "zh-CN" ? "关闭 Fast 模式" : "Disable Fast mode")
|
|
2389
|
-
: (locale === "zh-CN" ? "开启 Fast 模式" : "Enable Fast mode"));
|
|
2390
|
-
const fastIcon = createNativeFastIcon(fastEnabled);
|
|
2391
|
-
const fastContent = document.createElement("span");
|
|
2392
|
-
fastContent.className = classes.FastModeToggleContent;
|
|
2393
|
-
fastContent.setAttribute("data-codex-model-slider-fast-content", "");
|
|
2394
|
-
if (fastIcon) fastContent.append(fastIcon);
|
|
2395
|
-
fastButton.append(fastContent);
|
|
2396
|
-
fastButton.addEventListener("click", () => {
|
|
2397
|
-
const nextEnabled = fastButton.getAttribute("aria-pressed") !== "true";
|
|
2398
|
-
const currentIndex = Number(shell.querySelector("[data-codex-model-slider-rail]")?.getAttribute("aria-valuenow"));
|
|
2399
|
-
syncGenericFastVisual(shell, nextEnabled, efforts[currentIndex] === "ultra", true);
|
|
2400
|
-
controller.onSelectServiceTier?.(nextEnabled ? fastOption.value : null);
|
|
2401
|
-
queueEnsure();
|
|
2402
|
-
});
|
|
2403
|
-
viewControls.append(modelButton, fastButton);
|
|
2404
|
-
} else {
|
|
2405
|
-
viewControls.append(modelButton);
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
const nativeReset = parentMenu.firstElementChild?.querySelector('[class*="_ResetToDefault_"]');
|
|
2409
|
-
if (nativeReset) {
|
|
2410
|
-
const reset = nativeReset.cloneNode(true);
|
|
2411
|
-
reset.setAttribute("role", "button");
|
|
2412
|
-
reset.setAttribute("tabindex", "0");
|
|
2413
|
-
reset.setAttribute("data-codex-model-slider-reset", "");
|
|
2414
|
-
reset.removeAttribute("data-radix-collection-item");
|
|
2415
|
-
reset.addEventListener("click", () => {
|
|
2416
|
-
const currentReset = parentMenu.firstElementChild?.querySelector('[class*="_ResetToDefault_"]');
|
|
2417
|
-
invokeNativeMenuItemSelect(currentReset);
|
|
2418
|
-
queueEnsure();
|
|
2419
|
-
});
|
|
2420
|
-
reset.addEventListener("keydown", (event) => {
|
|
2421
|
-
if (event.key !== "Enter" && event.key !== " ") return;
|
|
2422
|
-
event.preventDefault();
|
|
2423
|
-
reset.click();
|
|
2424
|
-
});
|
|
2425
|
-
viewControls.append(reset);
|
|
2426
|
-
}
|
|
2427
|
-
|
|
2428
|
-
if (efforts.length < 2) {
|
|
2429
|
-
panel.append(viewControls);
|
|
2430
|
-
return shell;
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
const nativeSlider = cloneNativePowerSlider(parentMenu, efforts.length, fastEnabled);
|
|
2434
|
-
const rail = nativeSlider?.querySelector('[role="slider"]') ?? document.createElement("div");
|
|
2435
|
-
rail.setAttribute("data-codex-model-slider-rail", "");
|
|
2436
|
-
if (!nativeSlider) rail.setAttribute("data-codex-model-slider-fallback", "");
|
|
2437
|
-
rail.setAttribute("role", "slider");
|
|
2438
|
-
rail.setAttribute("tabindex", "0");
|
|
2439
|
-
rail.setAttribute("aria-label", locale === "zh-CN" ? "推理强度" : "Reasoning effort");
|
|
2440
|
-
rail.setAttribute("aria-valuemin", "0");
|
|
2441
|
-
rail.setAttribute("aria-valuemax", String(efforts.length - 1));
|
|
2442
|
-
rail.setAttribute("aria-valuetext", selectedEffortVisible
|
|
2443
|
-
? effortLabel(selectedEffort)
|
|
2444
|
-
: (locale === "zh-CN" ? "当前档位不可用" : "Current effort unavailable"));
|
|
2445
|
-
rail.toggleAttribute("data-stale-reasoning-effort", !selectedEffortVisible);
|
|
2446
|
-
if (!selectedEffortVisible) {
|
|
2447
|
-
rail.setAttribute("aria-disabled", "true");
|
|
2448
|
-
rail.removeAttribute("tabindex");
|
|
2449
|
-
neutralizeStaleSliderVisual(rail);
|
|
2450
|
-
const staleView = document.createElement("div");
|
|
2451
|
-
staleView.className = classes.SimpleView;
|
|
2452
|
-
staleView.append(nativeSlider ?? rail);
|
|
2453
|
-
panel.append(viewControls, staleView);
|
|
2454
|
-
return shell;
|
|
2455
|
-
}
|
|
2456
|
-
if (!nativeSlider) {
|
|
2457
|
-
const track = document.createElement("div");
|
|
2458
|
-
track.setAttribute("data-codex-model-slider-track", "");
|
|
2459
|
-
const range = document.createElement("div");
|
|
2460
|
-
range.setAttribute("data-codex-model-slider-range", "");
|
|
2461
|
-
track.append(range);
|
|
2462
|
-
efforts.forEach((_effort, index) => {
|
|
2463
|
-
const tick = document.createElement("span");
|
|
2464
|
-
tick.setAttribute("data-codex-model-slider-tick", "");
|
|
2465
|
-
tick.style.left = `${efforts.length <= 1 ? 0 : (index / (efforts.length - 1)) * 100}%`;
|
|
2466
|
-
track.append(tick);
|
|
2467
|
-
});
|
|
2468
|
-
const thumb = document.createElement("span");
|
|
2469
|
-
thumb.setAttribute("data-codex-model-slider-thumb", "");
|
|
2470
|
-
rail.append(track, thumb);
|
|
2471
|
-
}
|
|
2472
|
-
setGenericSliderVisual(rail, selectedIndex, efforts.length);
|
|
2473
|
-
syncNativeFastParticles(rail, fastEnabled);
|
|
2474
|
-
syncGenericMaximumVisual(
|
|
2475
|
-
rail,
|
|
2476
|
-
effortText,
|
|
2477
|
-
shell.querySelector("[data-codex-model-slider-fast]"),
|
|
2478
|
-
selectedEffort === "ultra",
|
|
2479
|
-
);
|
|
2480
|
-
|
|
2481
|
-
let pendingIndex = selectedIndex;
|
|
2482
|
-
let dragging = false;
|
|
2483
|
-
let dragRect = null;
|
|
2484
|
-
let previewFrame = null;
|
|
2485
|
-
let pendingClientX = null;
|
|
2486
|
-
const indexAt = (clientX) => {
|
|
2487
|
-
const rect = dragRect ?? rail.getBoundingClientRect();
|
|
2488
|
-
const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / Math.max(1, rect.width)));
|
|
2489
|
-
return Math.round(ratio * (efforts.length - 1));
|
|
2490
|
-
};
|
|
2491
|
-
const previewIndex = (index) => {
|
|
2492
|
-
pendingIndex = index;
|
|
2493
|
-
const effort = efforts[index];
|
|
2494
|
-
setGenericSliderVisual(rail, index, efforts.length);
|
|
2495
|
-
rail.setAttribute("aria-valuetext", effortLabel(effort));
|
|
2496
|
-
if (effortText) {
|
|
2497
|
-
effortText.textContent = effortLabel(effort);
|
|
2498
|
-
}
|
|
2499
|
-
const fastButton = shell.querySelector("[data-codex-model-slider-fast]");
|
|
2500
|
-
syncGenericMaximumVisual(rail, effortText, fastButton, effort === "ultra");
|
|
2501
|
-
};
|
|
2502
|
-
const preview = (clientX) => previewIndex(indexAt(clientX));
|
|
2503
|
-
rail.addEventListener("pointerdown", (event) => {
|
|
2504
|
-
dragging = true;
|
|
2505
|
-
dragRect = rail.getBoundingClientRect();
|
|
2506
|
-
rail.setPointerCapture?.(event.pointerId);
|
|
2507
|
-
preview(event.clientX);
|
|
2508
|
-
});
|
|
2509
|
-
rail.addEventListener("pointermove", (event) => {
|
|
2510
|
-
if (!dragging) return;
|
|
2511
|
-
pendingClientX = event.clientX;
|
|
2512
|
-
if (previewFrame != null) return;
|
|
2513
|
-
previewFrame = requestAnimationFrame(() => {
|
|
2514
|
-
previewFrame = null;
|
|
2515
|
-
if (dragging && pendingClientX != null) preview(pendingClientX);
|
|
2516
|
-
});
|
|
2517
|
-
});
|
|
2518
|
-
rail.addEventListener("pointerup", (event) => {
|
|
2519
|
-
if (!dragging) return;
|
|
2520
|
-
dragging = false;
|
|
2521
|
-
if (previewFrame != null) cancelAnimationFrame(previewFrame);
|
|
2522
|
-
previewFrame = null;
|
|
2523
|
-
pendingClientX = null;
|
|
2524
|
-
preview(event.clientX);
|
|
2525
|
-
dragRect = null;
|
|
2526
|
-
selectNativeEffort(parentMenu, efforts[pendingIndex]);
|
|
2527
|
-
});
|
|
2528
|
-
rail.addEventListener("pointercancel", () => {
|
|
2529
|
-
dragging = false;
|
|
2530
|
-
if (previewFrame != null) cancelAnimationFrame(previewFrame);
|
|
2531
|
-
previewFrame = null;
|
|
2532
|
-
pendingClientX = null;
|
|
2533
|
-
dragRect = null;
|
|
2534
|
-
previewIndex(selectedIndex);
|
|
2535
|
-
});
|
|
2536
|
-
rail.addEventListener("keydown", (event) => {
|
|
2537
|
-
if (!new Set(["ArrowLeft", "ArrowRight", "Home", "End"]).has(event.key)) return;
|
|
2538
|
-
event.preventDefault();
|
|
2539
|
-
if (event.key === "Home") pendingIndex = 0;
|
|
2540
|
-
else if (event.key === "End") pendingIndex = efforts.length - 1;
|
|
2541
|
-
else pendingIndex = Math.min(
|
|
2542
|
-
efforts.length - 1,
|
|
2543
|
-
Math.max(0, pendingIndex + (event.key === "ArrowRight" ? 1 : -1)),
|
|
2544
|
-
);
|
|
2545
|
-
previewIndex(pendingIndex);
|
|
2546
|
-
selectNativeEffort(parentMenu, efforts[pendingIndex]);
|
|
2547
|
-
});
|
|
2548
|
-
const simpleView = document.createElement("div");
|
|
2549
|
-
simpleView.className = classes.SimpleView;
|
|
2550
|
-
simpleView.append(nativeSlider ?? rail);
|
|
2551
|
-
panel.append(viewControls, simpleView);
|
|
2552
|
-
return shell;
|
|
2553
|
-
}
|
|
2554
|
-
|
|
2555
|
-
function centerGenericModelPicker(parentMenu) {
|
|
2556
|
-
const trigger = nativeComposerTrigger();
|
|
2557
|
-
if (!trigger) return;
|
|
2558
|
-
const menuRect = parentMenu.getBoundingClientRect();
|
|
2559
|
-
const triggerRect = trigger.getBoundingClientRect();
|
|
2560
|
-
if (!(menuRect.width > 0 && triggerRect.width > 0)) return;
|
|
2561
|
-
const previousShift = Number.parseFloat(
|
|
2562
|
-
parentMenu.style.getPropertyValue("--codex-model-slider-menu-center-shift"),
|
|
2563
|
-
) || 0;
|
|
2564
|
-
const unshiftedCenter = menuRect.left + (menuRect.width / 2) - previousShift;
|
|
2565
|
-
const nextShift = triggerRect.left + (triggerRect.width / 2) - unshiftedCenter;
|
|
2566
|
-
parentMenu.style.setProperty("--codex-model-slider-menu-center-shift", `${nextShift}px`);
|
|
2567
|
-
}
|
|
2568
|
-
|
|
2569
|
-
// ========================================================================
|
|
2570
|
-
// 通用 picker 增强与恢复 — 菜单/触发器增强、布局居中与恢复降级
|
|
2571
|
-
// ========================================================================
|
|
2572
|
-
|
|
2573
|
-
function enhanceGenericModelPicker(parentMenu, selector) {
|
|
2574
|
-
const identity = modelIdentity(nativeModelValue(parentMenu));
|
|
2575
|
-
const model = modelDefinitionFor(identity, selector);
|
|
2576
|
-
const displayModel = modelWithOpenCodexEfforts(identity, selector, model);
|
|
2577
|
-
const efforts = displayModel?.supportedReasoningLevels ?? [];
|
|
2578
|
-
if (!model) return false;
|
|
2579
|
-
// 控制器推导的任务内实际 effort 优先,避免子任务/主任务切换后触发器属性滞后;
|
|
2580
|
-
// 触发器属性仅在控制器无法解析时作为实时回退,静态目录默认值最后兜底。
|
|
2581
|
-
const nativeModel = nativeCatalogModelFor(identity);
|
|
2582
|
-
const selectedEffort = nativeModel?.currentReasoningLevel
|
|
2583
|
-
?? nativeComposerTrigger()
|
|
2584
|
-
?.getAttribute("data-selected-reasoning-effort")
|
|
2585
|
-
?? model.defaultReasoningLevel
|
|
2586
|
-
?? efforts[0]?.effort
|
|
2587
|
-
?? "none";
|
|
2588
|
-
const effortRank = (value) => {
|
|
2589
|
-
const order = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
|
|
2590
|
-
const index = order.indexOf(String(value ?? "").toLowerCase());
|
|
2591
|
-
return index < 0 ? -1 : index;
|
|
2592
|
-
};
|
|
2593
|
-
const visibleEffort = efforts.find((level) => level.effort === selectedEffort)?.effort
|
|
2594
|
-
?? efforts.filter((level) => effortRank(level.effort) <= effortRank(selectedEffort))
|
|
2595
|
-
.sort((left, right) => effortRank(right.effort) - effortRank(left.effort))[0]?.effort
|
|
2596
|
-
?? efforts[0]?.effort
|
|
2597
|
-
?? "none";
|
|
2598
|
-
const selectedEffortValue = visibleEffort;
|
|
2599
|
-
const selectedServiceTier = nativeComposerModelController()?.selectedServiceTier;
|
|
2600
|
-
const serviceTierKey = selectedServiceTier?.id ?? selectedServiceTier ?? "standard";
|
|
2601
|
-
const selectionMode = nativeComposerModelController()?.selectionMode ?? "";
|
|
2602
|
-
const structureKey = `${identity.raw}:${selectedEffortValue}:${selectionMode}:${efforts.map((level) => level.effort).join(",")}`;
|
|
2603
|
-
const key = `${structureKey}:${serviceTierKey}`;
|
|
2604
|
-
const existing = parentMenu.querySelector(":scope > [data-codex-model-slider-generic]");
|
|
2605
|
-
const nativeTemplateReady = Boolean(
|
|
2606
|
-
parentMenu.firstElementChild
|
|
2607
|
-
?.querySelector("[data-model-picker-power-slider] [class*='_TickRail_'] > span"),
|
|
2608
|
-
);
|
|
2609
|
-
const templateWaitCount = Number(parentMenu.getAttribute("data-codex-model-slider-template-wait") ?? 0);
|
|
2610
|
-
if (nativeModel?.slug.toLowerCase().startsWith("gpt-") && !nativeTemplateReady && templateWaitCount < 3) {
|
|
2611
|
-
parentMenu.setAttribute("data-codex-model-slider-template-wait", String(templateWaitCount + 1));
|
|
2612
|
-
parentMenu.style.visibility = "hidden";
|
|
2613
|
-
requestAnimationFrame(queueEnsure);
|
|
2614
|
-
return true;
|
|
2615
|
-
}
|
|
2616
|
-
parentMenu.removeAttribute("data-codex-model-slider-template-wait");
|
|
2617
|
-
parentMenu.style.removeProperty("visibility");
|
|
2618
|
-
const canUpgradeFallback = Boolean(
|
|
2619
|
-
existing?.querySelector("[data-codex-model-slider-fallback]") && nativeTemplateReady,
|
|
2620
|
-
);
|
|
2621
|
-
if (existing?.getAttribute("data-codex-model-slider-structure-key") === structureKey
|
|
2622
|
-
&& existing.hasAttribute("data-codex-model-slider-fast-transitioning") && !canUpgradeFallback) {
|
|
2623
|
-
centerGenericModelPicker(parentMenu);
|
|
2624
|
-
return true;
|
|
2625
|
-
}
|
|
2626
|
-
const currentFast = existing?.querySelector("[data-codex-model-slider-rail]")?.getAttribute("data-fast-mode");
|
|
2627
|
-
const nativeFast = parentMenu.firstElementChild
|
|
2628
|
-
?.querySelector("[data-model-picker-power-slider] [data-fast-mode]")?.getAttribute("data-fast-mode");
|
|
2629
|
-
if (existing?.getAttribute("data-codex-model-slider-structure-key") === structureKey
|
|
2630
|
-
&& currentFast != null && currentFast === nativeFast && !canUpgradeFallback) {
|
|
2631
|
-
existing.setAttribute("data-codex-model-slider-generic", key);
|
|
2632
|
-
centerGenericModelPicker(parentMenu);
|
|
2633
|
-
return true;
|
|
2634
|
-
}
|
|
2635
|
-
if (existing?.getAttribute("data-codex-model-slider-generic") === key && !canUpgradeFallback) {
|
|
2636
|
-
centerGenericModelPicker(parentMenu);
|
|
2637
|
-
return true;
|
|
2638
|
-
}
|
|
2639
|
-
const nativeContainer = parentMenu.firstElementChild;
|
|
2640
|
-
if (!nativeContainer) return false;
|
|
2641
|
-
const shell = createGenericModelSlider(
|
|
2642
|
-
parentMenu,
|
|
2643
|
-
selector,
|
|
2644
|
-
identity,
|
|
2645
|
-
displayModel,
|
|
2646
|
-
selectedEffortValue,
|
|
2647
|
-
);
|
|
2648
|
-
if (!shell) {
|
|
2649
|
-
restoreGenericModelPicker(nativeContainer);
|
|
2650
|
-
return false;
|
|
2651
|
-
}
|
|
2652
|
-
modelSelectorGenericContainers.get(nativeContainer)?.effectMirror?.disconnect();
|
|
2653
|
-
existing?.remove();
|
|
2654
|
-
if (!modelSelectorGenericContainers.has(nativeContainer)) {
|
|
2655
|
-
modelSelectorGenericContainers.set(nativeContainer, {
|
|
2656
|
-
position: nativeContainer.style.position,
|
|
2657
|
-
inset: nativeContainer.style.inset,
|
|
2658
|
-
opacity: nativeContainer.style.opacity,
|
|
2659
|
-
pointerEvents: nativeContainer.style.pointerEvents,
|
|
2660
|
-
ariaHidden: nativeContainer.getAttribute("aria-hidden"),
|
|
2661
|
-
inert: nativeContainer.inert,
|
|
2662
|
-
});
|
|
2663
|
-
}
|
|
2664
|
-
parentMenu.setAttribute("data-codex-model-slider-generic-menu", "");
|
|
2665
|
-
nativeContainer.style.position = "absolute";
|
|
2666
|
-
nativeContainer.style.inset = "0";
|
|
2667
|
-
nativeContainer.style.opacity = "0";
|
|
2668
|
-
nativeContainer.style.pointerEvents = "none";
|
|
2669
|
-
nativeContainer.setAttribute("aria-hidden", "true");
|
|
2670
|
-
nativeContainer.inert = true;
|
|
2671
|
-
shell.setAttribute("data-codex-model-slider-generic", key);
|
|
2672
|
-
shell.setAttribute("data-codex-model-slider-structure-key", structureKey);
|
|
2673
|
-
parentMenu.append(shell);
|
|
2674
|
-
modelSelectorGenericContainers.get(nativeContainer).effectMirror = syncClonedPowerEffects(nativeContainer, shell);
|
|
2675
|
-
centerGenericModelPicker(parentMenu);
|
|
2676
|
-
return true;
|
|
2677
|
-
}
|
|
2678
|
-
|
|
2679
|
-
function enhanceNativeModelPicker(parentMenu, selector) {
|
|
2680
|
-
if (enhanceGenericModelPicker(parentMenu, selector)) return;
|
|
2681
|
-
const toggle = parentMenu.querySelector("[data-model-picker-view-toggle]");
|
|
2682
|
-
if (toggle && typeof nativeComposerModelController()?.onSelectModel === "function") {
|
|
2683
|
-
// 私有 DOM 无法满足 MCX shell 契约时,保留原生菜单作为降级路径。
|
|
2684
|
-
const raw = nativeModelValue(parentMenu);
|
|
2685
|
-
const textNode = firstVisibleTextNode(toggle);
|
|
2686
|
-
if (raw && textNode) {
|
|
2687
|
-
if (!modelSelectorOriginalToggleLabels.has(textNode)) {
|
|
2688
|
-
modelSelectorOriginalToggleLabels.set(textNode, textNode.textContent);
|
|
2689
|
-
}
|
|
2690
|
-
const label = compactModelLabel(raw);
|
|
2691
|
-
if (textNode.textContent !== label) textNode.textContent = label;
|
|
2692
|
-
}
|
|
2693
|
-
if (!toggle.hasAttribute("data-codex-model-slider-toggle")) {
|
|
2694
|
-
const originalAriaExpanded = toggle.getAttribute("aria-expanded");
|
|
2695
|
-
toggle.setAttribute("data-codex-model-slider-toggle", "");
|
|
2696
|
-
const openModels = (event) => {
|
|
2697
|
-
event.preventDefault();
|
|
2698
|
-
event.stopPropagation();
|
|
2699
|
-
event.stopImmediatePropagation();
|
|
2700
|
-
openControllerModelMenu(parentMenu, toggle, selector);
|
|
2701
|
-
};
|
|
2702
|
-
const onKeyDown = (event) => {
|
|
2703
|
-
if (event.key !== "Enter" && event.key !== " ") return;
|
|
2704
|
-
openModels(event);
|
|
2705
|
-
};
|
|
2706
|
-
toggle.addEventListener("click", openModels);
|
|
2707
|
-
toggle.addEventListener("keydown", onKeyDown);
|
|
2708
|
-
modelSelectorNativeToggleHandlers.set(toggle, { openModels, onKeyDown, originalAriaExpanded });
|
|
2709
|
-
}
|
|
2710
|
-
return;
|
|
2711
|
-
}
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
// 原生 ModelText 重建前的空窗期:立即插回 MCX 标签,避免关盒后芯片
|
|
2715
|
-
// 长时间失去模型名。
|
|
2716
|
-
function restoreTriggerLabelWithoutNative(trigger, selector) {
|
|
2717
|
-
if (trigger.getAttribute("data-state") !== "closed") return;
|
|
2718
|
-
if (trigger.querySelector("[data-codex-model-slider-trigger-label]")) return;
|
|
2719
|
-
const raw = trigger.getAttribute("data-codex-model-slider-trigger-raw");
|
|
2720
|
-
if (!raw) return;
|
|
2721
|
-
const anchor = trigger.querySelector('[class*="_ModelPickerTriggerLabel_"]')
|
|
2722
|
-
?? trigger.querySelector('[class*="_ModelPickerTriggerContent_"]');
|
|
2723
|
-
if (!anchor) return;
|
|
2724
|
-
const displayLabel = document.createElement("span");
|
|
2725
|
-
displayLabel.setAttribute("data-codex-model-slider-trigger-label", "");
|
|
2726
|
-
displayLabel.textContent = modelDisplayLabel(modelIdentity(raw), selector);
|
|
2727
|
-
anchor.prepend(displayLabel);
|
|
2728
|
-
}
|
|
2729
|
-
|
|
2730
|
-
function isComposerModelPlaceholder(value) {
|
|
2731
|
-
return /^(select\s+(model|effort)|选择\s*(模型|思考强度))$/i.test(String(value ?? "").trim());
|
|
2732
|
-
}
|
|
2733
|
-
|
|
2734
|
-
function enhanceComposerModelTrigger(selector) {
|
|
2735
|
-
const trigger = nativeComposerTrigger();
|
|
2736
|
-
if (!trigger) return;
|
|
2737
|
-
if (modelSelectorObservedTrigger !== trigger) {
|
|
2738
|
-
modelSelectorTriggerObserver?.disconnect();
|
|
2739
|
-
modelSelectorObservedTrigger = trigger;
|
|
2740
|
-
modelSelectorTriggerObserver = new MutationObserver(queueEnsure);
|
|
2741
|
-
modelSelectorTriggerObserver.observe(trigger, { childList: true, subtree: true, characterData: true });
|
|
2742
|
-
}
|
|
2743
|
-
const nativeLabels = Array.from(trigger.querySelectorAll(
|
|
2744
|
-
'[class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label])',
|
|
2745
|
-
));
|
|
2746
|
-
if (!nativeLabels.length) {
|
|
2747
|
-
trigger.setAttribute("data-codex-model-slider-trigger", "");
|
|
2748
|
-
trigger.toggleAttribute(
|
|
2749
|
-
"data-codex-model-slider-trigger-placeholder",
|
|
2750
|
-
isComposerModelPlaceholder(trigger.innerText),
|
|
2751
|
-
);
|
|
2752
|
-
restoreTriggerLabelWithoutNative(trigger, selector);
|
|
2753
|
-
return;
|
|
2754
|
-
}
|
|
2755
|
-
const visibleValue = String(nativeLabels.at(-1)?.textContent ?? "").trim();
|
|
2756
|
-
const previousRaw = trigger.getAttribute("data-codex-model-slider-trigger-raw") ?? "";
|
|
2757
|
-
const previousLabel = previousRaw
|
|
2758
|
-
? modelDisplayLabel(modelIdentity(previousRaw), selector)
|
|
2759
|
-
: "";
|
|
2760
|
-
const raw = !previousRaw || visibleValue !== previousLabel ? visibleValue : previousRaw;
|
|
2761
|
-
if (!raw) return;
|
|
2762
|
-
const label = modelDisplayLabel(modelIdentity(raw), selector);
|
|
2763
|
-
trigger.setAttribute("data-codex-model-slider-trigger", "");
|
|
2764
|
-
trigger.setAttribute("data-codex-model-slider-trigger-raw", raw);
|
|
2765
|
-
// 区分占位态(Select model / Select effort)与真实模型态:占位态沿用
|
|
2766
|
-
// 原生宽度契约,真实模型态让芯片 HUG 完整名称,避免第三方长名被截断。
|
|
2767
|
-
const placeholder = isComposerModelPlaceholder(label);
|
|
2768
|
-
trigger.toggleAttribute("data-codex-model-slider-trigger-placeholder", placeholder);
|
|
2769
|
-
for (const nativeLabel of nativeLabels) {
|
|
2770
|
-
let displayLabel = nativeLabel.nextElementSibling;
|
|
2771
|
-
if (!displayLabel?.hasAttribute("data-codex-model-slider-trigger-label")) {
|
|
2772
|
-
// 空窗期回插等场景会在别处留下现成的 MCX 标签,优先移动复用,
|
|
2773
|
-
// 避免恢复后出现两个可见名称把芯片撑宽。
|
|
2774
|
-
displayLabel = trigger.querySelector("[data-codex-model-slider-trigger-label]");
|
|
2775
|
-
if (!displayLabel) {
|
|
2776
|
-
displayLabel = document.createElement("span");
|
|
2777
|
-
displayLabel.className = nativeLabel.className;
|
|
2778
|
-
displayLabel.setAttribute("data-codex-model-slider-trigger-label", "");
|
|
2779
|
-
}
|
|
2780
|
-
nativeLabel.after(displayLabel);
|
|
2781
|
-
}
|
|
2782
|
-
if (displayLabel.textContent !== label) displayLabel.textContent = label;
|
|
2783
|
-
}
|
|
2784
|
-
// 原生开合的临时 min-width 钉定与弹层水平居中都基于对 Measurement
|
|
2785
|
-
// 探针的测量;把探针文本同步为 MCX 显示标签,原生机制即可测得
|
|
2786
|
-
// 真实显示宽度。保留 React 持有的子节点,避免切模型时卸载失败。
|
|
2787
|
-
const measurement = trigger.querySelector('[class*="_ModelPickerTriggerMeasurement_"]');
|
|
2788
|
-
const measurementText = measurement && firstVisibleTextNode(measurement);
|
|
2789
|
-
if (measurementText && measurementText.nodeValue !== label) measurementText.nodeValue = label;
|
|
2790
|
-
}
|
|
2791
|
-
|
|
2792
|
-
function ensureModelSelector() {
|
|
2793
|
-
const selector = modelSelectorFeature?.modelSelector;
|
|
2794
|
-
if (!selector) return;
|
|
2795
|
-
if (!nativeComposerModelController()) {
|
|
2796
|
-
if (modelSelectorStyle.isConnected) restoreModelSelectorEnhancements();
|
|
2797
|
-
return;
|
|
2798
|
-
}
|
|
2799
|
-
pruneModelSubmenuPositionObservers();
|
|
2800
|
-
pruneNativeModelToggleHandlers();
|
|
2801
|
-
pruneGenericModelPickerContainers();
|
|
2802
|
-
document.documentElement.setAttribute("data-codex-model-slider-catalog-ready", "");
|
|
2803
|
-
if (!modelSelectorStyle.isConnected) document.head?.append(modelSelectorStyle);
|
|
2804
|
-
enhanceComposerModelTrigger(selector);
|
|
2805
|
-
const parentMenu = nativeModelPickerMenu();
|
|
2806
|
-
if (modelSelectorControllerMenu && modelSelectorControllerMenu.parentMenu !== parentMenu) {
|
|
2807
|
-
closeControllerModelMenu();
|
|
2808
|
-
}
|
|
2809
|
-
if (!parentMenu) {
|
|
2810
|
-
if (
|
|
2811
|
-
modelSelectorPendingReturn
|
|
2812
|
-
&& !visibleBlockingDialog()
|
|
2813
|
-
&& nativeModelPickerSubmenus().length === 0
|
|
2814
|
-
) {
|
|
2815
|
-
clearTimeout(modelSelectorReturnTimer);
|
|
2816
|
-
modelSelectorReturnTimer = null;
|
|
2817
|
-
returnToNativeModelSlider();
|
|
2818
|
-
}
|
|
2819
|
-
} else {
|
|
2820
|
-
enhanceNativeModelPicker(parentMenu, selector);
|
|
2821
|
-
const submenu = nativeModelSubmenu(parentMenu);
|
|
2822
|
-
if (submenu) enhanceNativeModelSubmenu(submenu, selector);
|
|
2823
|
-
}
|
|
2824
|
-
try {
|
|
2825
|
-
// 图标工作与菜单增强解耦:放在最后并隔离异常,任何失败都不影响标签与菜单。
|
|
2826
|
-
captureNativeFastIcon();
|
|
2827
|
-
} catch (error) {
|
|
2828
|
-
console.warn("[codex-personal] fast icon capture failed", error);
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
|
|
2832
|
-
function restoreModelSelectorEnhancements() {
|
|
2833
|
-
closeControllerModelMenu();
|
|
2834
|
-
clearTimeout(modelSelectorReturnTimer);
|
|
2835
|
-
clearTimeout(modelSelectorEffortTransactionTimer);
|
|
2836
|
-
modelSelectorTriggerObserver?.disconnect();
|
|
2837
|
-
for (const record of modelSelectorPositionObservers.values()) {
|
|
2838
|
-
record.observer.disconnect();
|
|
2839
|
-
record.resizeObserver.disconnect();
|
|
2840
|
-
}
|
|
2841
|
-
modelSelectorPositionObservers.clear();
|
|
2842
|
-
for (const frame of modelSelectorMenuSizeFrames.values()) cancelAnimationFrame(frame);
|
|
2843
|
-
modelSelectorMenuSizeFrames.clear();
|
|
2844
|
-
modelSelectorTriggerObserver = null;
|
|
2845
|
-
modelSelectorObservedTrigger = null;
|
|
2846
|
-
modelSelectorReturnTimer = null;
|
|
2847
|
-
modelSelectorEffortTransactionTimer = null;
|
|
2848
|
-
modelSelectorPendingOutsideDismiss = false;
|
|
2849
|
-
modelSelectorSuppressOutsidePointerSequence = false;
|
|
2850
|
-
modelSelectorPendingReturn = false;
|
|
2851
|
-
document.documentElement.removeAttribute("data-codex-model-slider-selecting-effort");
|
|
2852
|
-
for (const item of document.querySelectorAll("[data-codex-model-slider-item]")) {
|
|
2853
|
-
item.style.removeProperty("display");
|
|
2854
|
-
for (const name of [
|
|
2855
|
-
"data-codex-model-slider-raw",
|
|
2856
|
-
"data-codex-model-slider-item",
|
|
2857
|
-
"data-codex-model-slider-order",
|
|
2858
|
-
"data-codex-model-slider-identity-source",
|
|
2859
|
-
"data-codex-model-slider-return-listener",
|
|
2860
|
-
]) item.removeAttribute(name);
|
|
2861
|
-
}
|
|
2862
|
-
for (const [node, value] of modelSelectorOriginalToggleLabels) {
|
|
2863
|
-
if (node.isConnected) node.textContent = value;
|
|
2864
|
-
}
|
|
2865
|
-
for (const [toggle, handlers] of modelSelectorNativeToggleHandlers) {
|
|
2866
|
-
toggle.removeEventListener("click", handlers.openModels);
|
|
2867
|
-
toggle.removeEventListener("keydown", handlers.onKeyDown);
|
|
2868
|
-
toggle.removeAttribute("data-codex-model-slider-toggle");
|
|
2869
|
-
if (handlers.originalAriaExpanded == null) toggle.removeAttribute("aria-expanded");
|
|
2870
|
-
else toggle.setAttribute("aria-expanded", handlers.originalAriaExpanded);
|
|
2871
|
-
}
|
|
2872
|
-
for (const container of modelSelectorGenericContainers.keys()) {
|
|
2873
|
-
restoreGenericModelPicker(container);
|
|
2874
|
-
}
|
|
2875
|
-
document.querySelectorAll("[data-codex-model-slider-divider]")
|
|
2876
|
-
.forEach((element) => element.remove());
|
|
2877
|
-
document.querySelectorAll("[data-codex-model-slider-generic]").forEach((element) => element.remove());
|
|
2878
|
-
document.querySelectorAll("[data-codex-model-slider-generic-menu]")
|
|
2879
|
-
.forEach((element) => element.removeAttribute("data-codex-model-slider-generic-menu"));
|
|
2880
|
-
document.querySelectorAll("[data-codex-model-slider-menu]")
|
|
2881
|
-
.forEach((element) => element.removeAttribute("data-codex-model-slider-menu"));
|
|
2882
|
-
document.querySelectorAll("[data-codex-model-slider-trigger]").forEach((element) => {
|
|
2883
|
-
element.removeAttribute("data-codex-model-slider-trigger");
|
|
2884
|
-
element.removeAttribute("data-codex-model-slider-trigger-raw");
|
|
2885
|
-
});
|
|
2886
|
-
document.querySelectorAll("[data-codex-model-slider-trigger-label]").forEach((element) => element.remove());
|
|
2887
|
-
document.documentElement.removeAttribute("data-codex-model-slider-catalog-ready");
|
|
2888
|
-
modelSelectorStyle.remove();
|
|
2889
|
-
modelSelectorOriginalToggleLabels.clear();
|
|
2890
|
-
modelSelectorNativeToggleHandlers.clear();
|
|
2891
|
-
}
|
|
2892
563
|
|
|
2893
564
|
// ========================================================================
|
|
2894
565
|
// Surface 注册与主题桥 — surface 记录、侧栏状态、主题 token 与 Composer 文本
|
|
@@ -3018,29 +689,8 @@ export function createInjectionSource(features, {
|
|
|
3018
689
|
});
|
|
3019
690
|
}
|
|
3020
691
|
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
const style = getComputedStyle(root);
|
|
3024
|
-
const tokens = Object.fromEntries(
|
|
3025
|
-
themeTokenNames
|
|
3026
|
-
.map((name) => [name, style.getPropertyValue(name).trim()])
|
|
3027
|
-
.filter(([, value]) => value),
|
|
3028
|
-
);
|
|
3029
|
-
// 按语义和命名空间解析,不依赖构建哈希或猜测近似名称。
|
|
3030
|
-
for (const name of themeTokenNames) {
|
|
3031
|
-
if (tokens[name]) continue;
|
|
3032
|
-
const semanticNames = [name, ...(themeTokenAliases[name] ?? [])];
|
|
3033
|
-
const candidates = semanticNames.flatMap((candidate) => candidate.startsWith("--color-")
|
|
3034
|
-
? [candidate, candidate.replace("--color-", "--app-color-")]
|
|
3035
|
-
: [candidate]);
|
|
3036
|
-
const value = candidates.map((candidate) => style.getPropertyValue(candidate).trim()).find(Boolean);
|
|
3037
|
-
if (value) tokens[name] = value;
|
|
3038
|
-
}
|
|
3039
|
-
const mode = root.dataset.theme === "dark" || root.classList.contains("electron-dark") || style.colorScheme === "dark"
|
|
3040
|
-
? "dark"
|
|
3041
|
-
: "light";
|
|
3042
|
-
return { root, style, tokens, mode };
|
|
3043
|
-
}
|
|
692
|
+
const resolvedTheme=featureUI.theme.read;
|
|
693
|
+
resolvedTheme();
|
|
3044
694
|
|
|
3045
695
|
function sendTheme() {
|
|
3046
696
|
themeQueued = false;
|
|
@@ -3051,8 +701,8 @@ export function createInjectionSource(features, {
|
|
|
3051
701
|
const radiusScale = Number.parseFloat(tokens["--codex-corner-radius-scale"] ?? "1.25") || 1.25;
|
|
3052
702
|
const panelRadius = `${radiusBase * radiusScale}${radiusUnit}`;
|
|
3053
703
|
const panelCornerShape = tokens["--codex-corner-shape"] ?? "superellipse(1.5)";
|
|
3054
|
-
const dialogRadius = tokens["--radius-
|
|
3055
|
-
const dialogShadow =
|
|
704
|
+
const dialogRadius = tokens["--radius-3xl"];
|
|
705
|
+
const dialogShadow = `0 0 0 .5px ${tokens["--color-border"]},${tokens["--shadow-lg"]}`;
|
|
3056
706
|
const surfaceTokens = {
|
|
3057
707
|
...tokens,
|
|
3058
708
|
// 部分 Codex 版本仅提供等价的正文前景 token。
|
|
@@ -3087,12 +737,7 @@ export function createInjectionSource(features, {
|
|
|
3087
737
|
record.element.style.boxShadow = tokens["--elevation-prominent"]
|
|
3088
738
|
?? "0 0 0 .5px rgba(13,13,13,.11),0 3px 7.5px rgba(0,0,0,.04),0 0 20px rgba(0,0,0,.05)";
|
|
3089
739
|
}
|
|
3090
|
-
if (record.kind
|
|
3091
|
-
record.element.style.borderRadius = dialogRadius;
|
|
3092
|
-
record.element.style.setProperty("corner-shape", "round");
|
|
3093
|
-
record.element.style.boxShadow = dialogShadow;
|
|
3094
|
-
}
|
|
3095
|
-
record.element.style.background = new Set(["pinned", "modal"]).has(record.kind)
|
|
740
|
+
if (record.kind !== "modal") record.element.style.background = record.kind === "pinned"
|
|
3096
741
|
? tokens["--color-token-dropdown-background"]
|
|
3097
742
|
?? tokens["--color-token-main-surface-primary"]
|
|
3098
743
|
?? style.backgroundColor
|
|
@@ -3314,12 +959,11 @@ export function createInjectionSource(features, {
|
|
|
3314
959
|
function createPageSurface(feature) {
|
|
3315
960
|
const surface = document.createElement("section");
|
|
3316
961
|
surface.setAttribute(pageSurfaceMarker, feature.id);
|
|
962
|
+
featureUI.page(surface, { transparent:true });
|
|
3317
963
|
surface.style.cssText = [
|
|
3318
|
-
"position:fixed",
|
|
3319
964
|
"top:0",
|
|
3320
965
|
"right:0",
|
|
3321
966
|
"bottom:0",
|
|
3322
|
-
"z-index:40",
|
|
3323
967
|
"background:transparent",
|
|
3324
968
|
].join(";");
|
|
3325
969
|
const frame = createFrame(feature.id, feature.label);
|
|
@@ -3546,9 +1190,8 @@ export function createInjectionSource(features, {
|
|
|
3546
1190
|
surface.setAttribute("role", "dialog");
|
|
3547
1191
|
surface.setAttribute("aria-label", definition.label ?? feature.label);
|
|
3548
1192
|
surface.hidden = true;
|
|
1193
|
+
featureUI.page(surface, { transparent:true });
|
|
3549
1194
|
surface.style.cssText = [
|
|
3550
|
-
"position:fixed",
|
|
3551
|
-
"z-index:40",
|
|
3552
1195
|
"overflow:hidden",
|
|
3553
1196
|
"border-radius:25px",
|
|
3554
1197
|
"corner-shape:var(--codex-corner-shape,superellipse(1.5))",
|
|
@@ -3721,16 +1364,17 @@ export function createInjectionSource(features, {
|
|
|
3721
1364
|
|
|
3722
1365
|
const overlay = document.createElement("div");
|
|
3723
1366
|
overlay.setAttribute("data-codex-personal-modal-overlay", feature.id);
|
|
1367
|
+
featureUI.overlay(overlay);
|
|
3724
1368
|
overlay.setAttribute("aria-hidden", "true");
|
|
3725
1369
|
overlay.style.cssText = [
|
|
3726
1370
|
"position:fixed",
|
|
3727
1371
|
"inset:0",
|
|
3728
1372
|
"z-index:50",
|
|
3729
|
-
"background:rgba(0,0,0,.133)",
|
|
3730
1373
|
].join(";");
|
|
3731
1374
|
|
|
3732
1375
|
const dialog = document.createElement("section");
|
|
3733
1376
|
dialog.setAttribute(modalSurfaceMarker, feature.id);
|
|
1377
|
+
featureUI.surface(dialog, "dialog");
|
|
3734
1378
|
dialog.setAttribute("role", "dialog");
|
|
3735
1379
|
dialog.setAttribute("aria-modal", "true");
|
|
3736
1380
|
dialog.setAttribute("aria-label", `${payload.id ? "Edit" : "Add"} ${kind}`);
|
|
@@ -3745,11 +1389,7 @@ export function createInjectionSource(features, {
|
|
|
3745
1389
|
"max-height:88vh",
|
|
3746
1390
|
"transform:translate(-50%,-50%)",
|
|
3747
1391
|
"overflow:hidden",
|
|
3748
|
-
"
|
|
3749
|
-
"border-radius:20px",
|
|
3750
|
-
"corner-shape:round",
|
|
3751
|
-
"background:var(--color-token-dropdown-background,var(--color-token-main-surface-primary))",
|
|
3752
|
-
"box-shadow:0 16px 32px -8px rgba(0,0,0,.19)",
|
|
1392
|
+
"padding:0",
|
|
3753
1393
|
"outline:none",
|
|
3754
1394
|
].join(";");
|
|
3755
1395
|
|
|
@@ -4390,7 +2030,7 @@ export function createInjectionSource(features, {
|
|
|
4390
2030
|
ensureTimer = null;
|
|
4391
2031
|
ensureSidebarEntries();
|
|
4392
2032
|
ensureToolbarEntries();
|
|
4393
|
-
|
|
2033
|
+
modelSlider.ensure();
|
|
4394
2034
|
checkThreadChange();
|
|
4395
2035
|
updateSidebarSelectedState();
|
|
4396
2036
|
updatePageSurfacePositions();
|
|
@@ -4580,11 +2220,6 @@ export function createInjectionSource(features, {
|
|
|
4580
2220
|
if (event.target.closest?.('[data-app-shell-sidebar-trigger="true"]')) {
|
|
4581
2221
|
syncAfterNativeSidebarToggle();
|
|
4582
2222
|
}
|
|
4583
|
-
if (
|
|
4584
|
-
modelSelectorControllerMenu
|
|
4585
|
-
&& !modelSelectorControllerMenu.menu.contains(event.target)
|
|
4586
|
-
&& !modelSelectorControllerMenu.button.contains(event.target)
|
|
4587
|
-
) closeControllerModelMenu();
|
|
4588
2223
|
if (isNativeSummaryButton(event.target) && activePinnedFeatureId) hidePinnedSurfaces();
|
|
4589
2224
|
if (activePinnedFeatureId && summaryDisplayMode() === "overlay") {
|
|
4590
2225
|
const state = summaryState(activePinnedFeatureId);
|
|
@@ -4729,6 +2364,7 @@ export function createInjectionSource(features, {
|
|
|
4729
2364
|
version: config.version,
|
|
4730
2365
|
sessionId: config.sessionId,
|
|
4731
2366
|
updateHostBinding,
|
|
2367
|
+
nativeCapabilities: () => codex.diagnostics(),
|
|
4732
2368
|
ensure: queueEnsure,
|
|
4733
2369
|
show: showPageSurface,
|
|
4734
2370
|
hide: () => {
|
|
@@ -4769,8 +2405,9 @@ export function createInjectionSource(features, {
|
|
|
4769
2405
|
closePromptPreview({ immediate: true });
|
|
4770
2406
|
closeModalSurface();
|
|
4771
2407
|
closeNativeDetailTabs();
|
|
4772
|
-
|
|
2408
|
+
modelSlider.dispose();
|
|
4773
2409
|
surfaceRecords.clear();
|
|
2410
|
+
featureUI.theme.dispose();
|
|
4774
2411
|
if (window.__codexPersonalRuntime === runtimeApi) delete window.__codexPersonalRuntime;
|
|
4775
2412
|
},
|
|
4776
2413
|
};
|
|
@@ -4795,6 +2432,7 @@ export function createInjectionSource(features, {
|
|
|
4795
2432
|
pageScript: feature.pageScript ? {
|
|
4796
2433
|
...feature.pageScript,
|
|
4797
2434
|
config: {
|
|
2435
|
+
resources: feature.pageScript.resources ?? {},
|
|
4798
2436
|
serviceOrigin: pageScriptServiceOrigin(feature),
|
|
4799
2437
|
bindingName,
|
|
4800
2438
|
bindingToken,
|
|
@@ -4811,8 +2449,9 @@ export function createInjectionSource(features, {
|
|
|
4811
2449
|
rightPanelRouteKinds: RIGHT_PANEL_ROUTE_KINDS,
|
|
4812
2450
|
themeTokens: CODEX_THEME_TOKENS,
|
|
4813
2451
|
themeTokenAliases: CODEX_THEME_TOKEN_ALIASES,
|
|
2452
|
+
themeBindings: MINECODEX_THEME_BINDINGS,
|
|
4814
2453
|
features: runtimeFeatures,
|
|
4815
|
-
})}, ${
|
|
2454
|
+
})}, ${createUIInstallerSource()}, ${createModelSliderInstallerSource()}, ${createCodexEnvironment.toString()})`;
|
|
4816
2455
|
}
|
|
4817
2456
|
|
|
4818
2457
|
export function createRuntimeReadyContract({
|