minecodex 2.0.2 → 2.0.4
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 +10 -8
- 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 +46 -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 +42 -102
- 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
|
@@ -0,0 +1,2110 @@
|
|
|
1
|
+
// 专有原生控制器适配与呈现由插件维护;宿主只提供刷新和共用 UI。
|
|
2
|
+
export function installModelSlider(signal, { feature, ui, createIcon, requestRefresh, locale, codex }, rules, createModelControllerAdapter) {
|
|
3
|
+
const modelSelectorFeature = feature;
|
|
4
|
+
const featureUI = ui, createMarkupIcon = createIcon, queueEnsure = requestRefresh;
|
|
5
|
+
const { modelIdentity, formatIdentifier, providerLabel, compactModelLabel, brandForModel, comparableModelLabel, catalogModelFor, resolveModelAlias, modelWithOpenCodexEfforts, nativeGptSortRank, visibleControllerModels } = rules;
|
|
6
|
+
const modelSelectorOriginalToggleLabels = new Map();
|
|
7
|
+
const modelSelectorGenericContainers = new Map();
|
|
8
|
+
const modelSelectorNativeToggleHandlers = new Map();
|
|
9
|
+
const modelSelectorPositionObservers = new Map();
|
|
10
|
+
const modelSelectorMenuSizeFrames = new Map();
|
|
11
|
+
let modelSelectorReturnTimer = null;
|
|
12
|
+
let modelSelectorPendingReturn = false;
|
|
13
|
+
let modelSelectorEffortTransactionTimer = null;
|
|
14
|
+
let modelSelectorPendingOutsideDismiss = false;
|
|
15
|
+
let modelSelectorSuppressOutsidePointerSequence = false;
|
|
16
|
+
let modelSelectorTriggerObserver = null;
|
|
17
|
+
let modelSelectorObservedTrigger = null;
|
|
18
|
+
let modelSelectorControllerMenu = null;
|
|
19
|
+
let modelSelectorNativeFastIcons = null;
|
|
20
|
+
// 持久化脏标记:仅在图标实际变化时写一次 localStorage。
|
|
21
|
+
let modelSelectorNativeFastIconsDirty = false;
|
|
22
|
+
const modelSelectorFastIconStorageKey = "codex-model-slider:fast-icons:v1";
|
|
23
|
+
const modelSelectorStyle = document.createElement("style");
|
|
24
|
+
modelSelectorStyle.setAttribute("data-codex-model-slider-style", "");
|
|
25
|
+
modelSelectorStyle.textContent = `
|
|
26
|
+
[data-codex-model-slider-menu] { width: 264px !important; min-width: 264px; overflow-x: hidden; }
|
|
27
|
+
[data-codex-model-slider-controller-menu] {
|
|
28
|
+
position: absolute !important;
|
|
29
|
+
left: 50%;
|
|
30
|
+
bottom: 0;
|
|
31
|
+
transform: translateX(-50%) !important;
|
|
32
|
+
z-index: 10;
|
|
33
|
+
padding: 6px;
|
|
34
|
+
}
|
|
35
|
+
[data-codex-model-slider-menu][data-codex-model-slider-overflow] {
|
|
36
|
+
max-height: var(--codex-model-slider-menu-max-height) !important;
|
|
37
|
+
overflow-y: auto;
|
|
38
|
+
}
|
|
39
|
+
[data-codex-model-slider-trigger] [class*="_ModelPickerTriggerModelLabel_"] {
|
|
40
|
+
min-width: 0; max-width: 110px; flex-shrink: 1; overflow: hidden; text-overflow: ellipsis;
|
|
41
|
+
}
|
|
42
|
+
[data-codex-model-slider-trigger] [class*="_ModelPickerTriggerModelText_"] {
|
|
43
|
+
min-width: 0; max-width: 110px; overflow: hidden; text-overflow: ellipsis;
|
|
44
|
+
}
|
|
45
|
+
html[data-codex-model-slider-catalog-ready]
|
|
46
|
+
[data-codex-model-slider-trigger]
|
|
47
|
+
[class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label]) {
|
|
48
|
+
display: none !important;
|
|
49
|
+
}
|
|
50
|
+
[data-codex-model-slider-trigger-label] {
|
|
51
|
+
display: inline-block; min-width: 0; max-width: 110px;
|
|
52
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
53
|
+
}
|
|
54
|
+
/* 展示真实模型名/思考强度时:芯片 HUG 内容,长名称不被截断;
|
|
55
|
+
仅 "Select model / Select effort" 占位态沿用原生宽度。 */
|
|
56
|
+
[data-codex-model-slider-trigger]:not([data-codex-model-slider-trigger-placeholder]) [data-codex-model-slider-trigger-label] {
|
|
57
|
+
max-width: none; overflow: visible; text-overflow: clip; flex-shrink: 0;
|
|
58
|
+
}
|
|
59
|
+
[data-codex-model-slider-trigger]:not([data-codex-model-slider-trigger-placeholder]) [class*="_ModelPickerTriggerModelLabel_"] {
|
|
60
|
+
max-width: none; overflow: visible; flex-shrink: 0;
|
|
61
|
+
}
|
|
62
|
+
[data-codex-model-slider-trigger]:not([data-codex-model-slider-trigger-placeholder]) [class*="_ModelPickerTriggerContent_"] {
|
|
63
|
+
max-width: none !important;
|
|
64
|
+
}
|
|
65
|
+
[data-codex-model-slider-trigger][data-codex-model-slider-trigger-placeholder] [class*="_ModelPickerTriggerContent_"] {
|
|
66
|
+
width: 160px; min-width: 160px !important; max-width: 160px;
|
|
67
|
+
}
|
|
68
|
+
[data-codex-model-slider-trigger] [class*="_ModelPickerTriggerEffortLabel_"] {
|
|
69
|
+
display: inline-flex; align-items: center; align-self: center;
|
|
70
|
+
}
|
|
71
|
+
html[data-codex-model-slider-selecting-effort] [role="menu"][data-state="open"]:not(:has([data-reasoning-slider])) {
|
|
72
|
+
visibility: hidden !important; opacity: 0 !important; animation: none !important; pointer-events: none !important;
|
|
73
|
+
}
|
|
74
|
+
[data-codex-model-slider-row] { display: flex; width: 100%; min-width: 0; align-items: center; gap: 10px; }
|
|
75
|
+
[data-codex-model-slider-brand] { display: grid; width: 18px; height: 18px; flex: 0 0 18px; place-items: center; }
|
|
76
|
+
[data-codex-model-slider-brand] svg { width: 16px; height: 16px; display: block; }
|
|
77
|
+
[data-codex-model-slider-copy] { display: flex; min-width: 0; flex: 1; flex-direction: column; gap: 1px; }
|
|
78
|
+
[data-codex-model-slider-name] { overflow: hidden; color: var(--mcx-color-text-foreground); text-overflow: ellipsis; white-space: nowrap; }
|
|
79
|
+
[data-codex-model-slider-provider] { overflow: hidden; color: var(--mcx-color-text-foreground-tertiary); font-size: 11px; line-height: 14px; text-overflow: ellipsis; white-space: nowrap; }
|
|
80
|
+
[data-codex-model-slider-item] { position: relative; }
|
|
81
|
+
[data-codex-model-slider-item][data-codex-model-slider-selected] { background: var(--mcx-color-token-list-hover-background); }
|
|
82
|
+
[data-codex-model-slider-star] {
|
|
83
|
+
display: grid; width: 24px; height: 24px; flex: 0 0 24px; place-items: center;
|
|
84
|
+
border: 0; border-radius: var(--mcx-radius-md, 6px); padding: 0; background: transparent;
|
|
85
|
+
color: var(--mcx-color-text-foreground-tertiary); cursor: pointer; opacity: 0; pointer-events: none;
|
|
86
|
+
}
|
|
87
|
+
[data-codex-model-slider-item]:hover [data-codex-model-slider-star],
|
|
88
|
+
[data-codex-model-slider-item]:focus-within [data-codex-model-slider-star] { opacity: 1; pointer-events: auto; }
|
|
89
|
+
[data-codex-model-slider-star]:hover, [data-codex-model-slider-star]:focus-visible {
|
|
90
|
+
background: var(--mcx-color-token-list-hover-background); color: var(--mcx-color-text-foreground); outline: none;
|
|
91
|
+
}
|
|
92
|
+
[data-codex-model-slider-star]::after {
|
|
93
|
+
content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 40px; z-index: 1;
|
|
94
|
+
}
|
|
95
|
+
[data-codex-model-slider-star] svg { position: relative; z-index: 2; width: 16px; height: 16px; pointer-events: none; }
|
|
96
|
+
[data-codex-model-slider-divider] {
|
|
97
|
+
height: 0; border-top: 1px solid var(--mcx-color-token-border, var(--mcx-color-border));
|
|
98
|
+
margin: 6px 8px;
|
|
99
|
+
}
|
|
100
|
+
[data-codex-model-slider-generic-menu] {
|
|
101
|
+
overflow-x: hidden;
|
|
102
|
+
transform: translateX(var(--codex-model-slider-menu-center-shift, 0px)) !important;
|
|
103
|
+
}
|
|
104
|
+
[data-codex-model-slider-generic] { box-sizing: border-box; width: 100%; }
|
|
105
|
+
[data-codex-model-slider-fast] { border: 0; background: transparent; cursor: pointer; }
|
|
106
|
+
[data-codex-model-slider-fast]:not(:hover) [data-codex-model-slider-fast-content] { background: transparent; }
|
|
107
|
+
[data-codex-model-slider-fast]:focus-visible { outline: 2px solid var(--mcx-color-border-focus); outline-offset: 1px; }
|
|
108
|
+
[data-codex-model-slider-rail][data-codex-model-slider-fallback] { position: relative; height: 38px; margin: 1px 8px 0; outline: none; cursor: pointer; }
|
|
109
|
+
[data-codex-model-slider-track] { position: absolute; top: 12px; right: 0; left: 0; height: 16px; border-radius: 999px; background: var(--mcx-color-background-control); }
|
|
110
|
+
[data-codex-model-slider-range] { position: absolute; top: 0; bottom: 0; left: 0; width: var(--codex-model-slider-progress); border-radius: inherit; background: var(--mcx-color-text-accent); }
|
|
111
|
+
[data-codex-model-slider-rail][data-max="true"] [data-codex-model-slider-range] { background: var(--mcx-color-chart-purple); }
|
|
112
|
+
[data-codex-model-slider-fallback] [data-codex-model-slider-tick] {
|
|
113
|
+
position: absolute; top: 16px; width: 6px; height: 6px; transform: translate(-50%, -50%); border: 0; border-radius: 50%;
|
|
114
|
+
padding: 0; background: var(--mcx-color-text-foreground-tertiary); pointer-events: none;
|
|
115
|
+
}
|
|
116
|
+
[data-codex-model-slider-fallback] [data-codex-model-slider-tick][data-selected="true"] { background: var(--mcx-color-text-on-accent); }
|
|
117
|
+
[data-codex-model-slider-thumb] {
|
|
118
|
+
position: absolute; top: 7px; left: var(--codex-model-slider-progress); width: 26px; height: 26px;
|
|
119
|
+
transform: translateX(-50%); border: 0.5px solid var(--mcx-color-token-border, var(--mcx-color-border)); border-radius: 50%;
|
|
120
|
+
background: var(--mcx-color-background-elevated-primary-opaque, var(--mcx-color-background-panel)); box-shadow: var(--mcx-shadow-md);
|
|
121
|
+
pointer-events: none;
|
|
122
|
+
}
|
|
123
|
+
[data-codex-model-slider-fallback]:focus-visible [data-codex-model-slider-thumb] { box-shadow: 0 0 0 2px var(--mcx-color-border-focus); }
|
|
124
|
+
@media (prefers-reduced-motion: reduce) {
|
|
125
|
+
[data-codex-model-slider-star] { transition: none; }
|
|
126
|
+
}
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
const nativeModel=createModelControllerAdapter(codex,comparableModelLabel);
|
|
130
|
+
const nativeComposerTrigger=nativeModel.trigger, nativeComposerModelController=nativeModel.read, nativeModelDescriptorForItem=nativeModel.descriptor, setReactControlledMenuOpen=nativeModel.setOpen, invokeNativeMenuItemSelect=nativeModel.selectItem;
|
|
131
|
+
|
|
132
|
+
function createModelSelectorIcon(definition, { brand = false } = {}) {
|
|
133
|
+
const icon = createMarkupIcon(definition, "", 16);
|
|
134
|
+
if (!icon) return null;
|
|
135
|
+
if (brand) {
|
|
136
|
+
icon.setAttribute("fill", "currentColor");
|
|
137
|
+
icon.removeAttribute("stroke");
|
|
138
|
+
icon.removeAttribute("stroke-width");
|
|
139
|
+
icon.removeAttribute("stroke-linecap");
|
|
140
|
+
icon.removeAttribute("stroke-linejoin");
|
|
141
|
+
}
|
|
142
|
+
return icon;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function nativeCssModuleClass(tokenName, root = document) {
|
|
146
|
+
const pattern = new RegExp(`^_${tokenName}_[A-Za-z0-9_-]+$`);
|
|
147
|
+
const live = Array.from(root.querySelectorAll(`[class*="_${tokenName}_"]`))
|
|
148
|
+
.flatMap((element) => Array.from(element.classList))
|
|
149
|
+
.find((name) => pattern.test(name));
|
|
150
|
+
if (live) return live;
|
|
151
|
+
const selectorPattern = new RegExp(`\\.(_${tokenName}_[A-Za-z0-9_-]+)`);
|
|
152
|
+
const find = (rules) => {
|
|
153
|
+
for (const rule of rules ?? []) {
|
|
154
|
+
const match = String(rule.selectorText ?? "").match(selectorPattern);
|
|
155
|
+
if (match?.[1]) return match[1];
|
|
156
|
+
try {
|
|
157
|
+
const nested = find(rule.cssRules);
|
|
158
|
+
if (nested) return nested;
|
|
159
|
+
} catch {
|
|
160
|
+
// Ignore inaccessible nested rules and continue with Codex's readable sheets.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
};
|
|
165
|
+
for (const sheet of document.styleSheets) {
|
|
166
|
+
try {
|
|
167
|
+
const match = find(sheet.cssRules);
|
|
168
|
+
if (match) return match;
|
|
169
|
+
} catch {
|
|
170
|
+
// Ignore cross-origin stylesheets.
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function persistNativeFastIcons() {
|
|
177
|
+
if (!modelSelectorNativeFastIcons || !modelSelectorNativeFastIconsDirty) return;
|
|
178
|
+
try {
|
|
179
|
+
const payload = {};
|
|
180
|
+
for (const [state, template] of Object.entries(modelSelectorNativeFastIcons)) {
|
|
181
|
+
payload[state] = template.outerHTML;
|
|
182
|
+
}
|
|
183
|
+
localStorage.setItem(modelSelectorFastIconStorageKey, JSON.stringify(payload));
|
|
184
|
+
modelSelectorNativeFastIconsDirty = false;
|
|
185
|
+
} catch {
|
|
186
|
+
// localStorage 不可用时静默降级,仅本次会话内生效。
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function loadPersistedNativeFastIcons(storageKey) {
|
|
191
|
+
let stored;
|
|
192
|
+
try {
|
|
193
|
+
stored = localStorage.getItem(storageKey);
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
let payload;
|
|
198
|
+
try {
|
|
199
|
+
payload = JSON.parse(stored ?? "null");
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
if (!payload || typeof payload !== "object") return null;
|
|
204
|
+
const icons = {};
|
|
205
|
+
for (const [state, markup] of Object.entries(payload)) {
|
|
206
|
+
if (typeof markup !== "string") continue;
|
|
207
|
+
const container = document.createElement("template");
|
|
208
|
+
container.innerHTML = markup;
|
|
209
|
+
const svg = container.content.querySelector("svg");
|
|
210
|
+
if (svg) icons[state] = svg;
|
|
211
|
+
}
|
|
212
|
+
return Object.keys(icons).length ? icons : null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function loadNativeFastIconFromBundle() {
|
|
216
|
+
// 两个图标均已就绪(持久化或 DOM 克隆)时不再拉取整包 app-initial 资源。
|
|
217
|
+
if (modelSelectorNativeFastIcons?.active && modelSelectorNativeFastIcons?.inactive) return;
|
|
218
|
+
try {
|
|
219
|
+
const definitions=await nativeModel.fastIcons(signal);
|
|
220
|
+
if (!definitions) return;
|
|
221
|
+
if (!modelSelectorNativeFastIcons) {
|
|
222
|
+
modelSelectorNativeFastIcons = {};
|
|
223
|
+
}
|
|
224
|
+
let gained = false;
|
|
225
|
+
const define = (state) => {
|
|
226
|
+
const definition=definitions[state];
|
|
227
|
+
if (modelSelectorNativeFastIcons[state]) return;
|
|
228
|
+
const path = definition?.path;
|
|
229
|
+
if (!path) return;
|
|
230
|
+
const svg = createModelSelectorIcon({
|
|
231
|
+
viewBox: definition.viewBox,
|
|
232
|
+
markup: definition.transform
|
|
233
|
+
? `<g transform="${definition.transform}"><path d="${path}" fill="currentColor" /></g>`
|
|
234
|
+
: `<path d="${path}" fill="currentColor" />`,
|
|
235
|
+
}, { brand: true });
|
|
236
|
+
if (!svg) return;
|
|
237
|
+
svg.setAttribute("width", "20");
|
|
238
|
+
svg.setAttribute("height", "20");
|
|
239
|
+
modelSelectorNativeFastIcons[state] = svg;
|
|
240
|
+
gained = true;
|
|
241
|
+
};
|
|
242
|
+
define("active");
|
|
243
|
+
define("inactive");
|
|
244
|
+
if (gained) {
|
|
245
|
+
modelSelectorNativeFastIconsDirty = true;
|
|
246
|
+
persistNativeFastIcons();
|
|
247
|
+
applyNativeFastIconsToShells();
|
|
248
|
+
queueEnsure();
|
|
249
|
+
}
|
|
250
|
+
} catch {
|
|
251
|
+
// 私有资源移动时保持原生控件不变,等待 DOM 克隆兜底。
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function captureNativeFastIcon() {
|
|
256
|
+
// 原生控件每次只渲染当前状态;状态切换后按控件本身更新缓存。
|
|
257
|
+
const nativeContent = Array.from(document.querySelectorAll('[class*="_FastModeToggleContent_"]'))
|
|
258
|
+
.find((content) => !content.closest("[data-codex-model-slider-generic]"));
|
|
259
|
+
const toggle = nativeContent?.closest('[class*="_FastModeToggle_"]');
|
|
260
|
+
const state = toggle?.getAttribute("data-fast-mode-enabled") === "true" ? "active" : "inactive";
|
|
261
|
+
const svg = nativeContent?.querySelector("svg");
|
|
262
|
+
const current = modelSelectorNativeFastIcons?.[state];
|
|
263
|
+
if (svg && (current?.innerHTML !== svg.innerHTML
|
|
264
|
+
|| current.getAttribute("viewBox") !== svg.getAttribute("viewBox")
|
|
265
|
+
|| current.getAttribute("width") !== svg.getAttribute("width"))) {
|
|
266
|
+
const template = svg.cloneNode(true);
|
|
267
|
+
template.removeAttribute("id");
|
|
268
|
+
if (!modelSelectorNativeFastIcons) {
|
|
269
|
+
modelSelectorNativeFastIcons = {};
|
|
270
|
+
}
|
|
271
|
+
modelSelectorNativeFastIcons[state] = template;
|
|
272
|
+
modelSelectorNativeFastIconsDirty = true;
|
|
273
|
+
}
|
|
274
|
+
const ready = Boolean(
|
|
275
|
+
modelSelectorNativeFastIcons?.active && modelSelectorNativeFastIcons?.inactive,
|
|
276
|
+
);
|
|
277
|
+
if (ready) {
|
|
278
|
+
persistNativeFastIcons();
|
|
279
|
+
applyNativeFastIconsToShells();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function applyNativeFastIconsToShells() {
|
|
284
|
+
document.querySelectorAll("[data-codex-model-slider-fast]").forEach((button) => {
|
|
285
|
+
const content = button.querySelector("[data-codex-model-slider-fast-content]");
|
|
286
|
+
if (!content) return;
|
|
287
|
+
const icon = createNativeFastIcon(button.getAttribute("aria-pressed") === "true");
|
|
288
|
+
if (!icon) return;
|
|
289
|
+
const current = content.querySelector("svg");
|
|
290
|
+
if (!current || current.innerHTML !== icon.innerHTML
|
|
291
|
+
|| current.getAttribute("viewBox") !== icon.getAttribute("viewBox")
|
|
292
|
+
|| current.getAttribute("width") !== icon.getAttribute("width")) {
|
|
293
|
+
content.replaceChildren(icon);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function createNativeFastIcon(active) {
|
|
299
|
+
// 直接读取已就绪的图标存储(启动时由 bundle/持久化填充,运行期由
|
|
300
|
+
// captureNativeFastIcon 升级为 DOM 克隆),避免再次触发捕获造成递归。
|
|
301
|
+
// 优先克隆当前面板的图标;切换瞬间使用缓存时,也沿用原生图标尺寸。
|
|
302
|
+
const nativeContents = Array.from(document.querySelectorAll('[class*="_FastModeToggleContent_"]'))
|
|
303
|
+
.filter((content) => !content.closest("[data-codex-model-slider-generic]"));
|
|
304
|
+
const nativeContent = nativeContents.find((content) => (
|
|
305
|
+
(content.getAttribute("data-fast-mode-enabled")
|
|
306
|
+
?? content.closest('[class*="_FastModeToggle_"]')?.getAttribute("data-fast-mode-enabled")) === String(active)
|
|
307
|
+
));
|
|
308
|
+
const nativeSvg = nativeContent?.querySelector("svg") ?? nativeContents[0]?.querySelector("svg");
|
|
309
|
+
const template = nativeContent?.querySelector("svg")
|
|
310
|
+
?? modelSelectorNativeFastIcons?.[active ? "active" : "inactive"];
|
|
311
|
+
if (!template) return null;
|
|
312
|
+
const icon = template.cloneNode(true);
|
|
313
|
+
if (nativeSvg) {
|
|
314
|
+
icon.setAttribute("width", nativeSvg.getAttribute("width") ?? "16");
|
|
315
|
+
icon.setAttribute("height", nativeSvg.getAttribute("height") ?? "16");
|
|
316
|
+
}
|
|
317
|
+
icon.setAttribute("aria-hidden", "true");
|
|
318
|
+
return icon;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ========================================================================
|
|
322
|
+
// 模型 identity、目录与菜单增强 — 模型身份解析、品牌/别名、收藏与原生菜单增强
|
|
323
|
+
// ========================================================================
|
|
324
|
+
|
|
325
|
+
function modelDisplayLabel(identity, selector) {
|
|
326
|
+
const model = catalogModelFor(identity, selector) ?? nativeCatalogModelFor(identity);
|
|
327
|
+
const source = model?.displayName || model?.slug || identity.backend;
|
|
328
|
+
return formatIdentifier(source.slice(source.lastIndexOf("/") + 1));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function nativeCatalogModelFor(identity, selector = modelSelectorFeature?.modelSelector) {
|
|
332
|
+
const controller = nativeComposerModelController();
|
|
333
|
+
const models = controller?.models ?? [];
|
|
334
|
+
const exact = models.find((model) => String(model.model ?? "").toLowerCase() === identity.raw.toLowerCase());
|
|
335
|
+
const target = comparableModelLabel(identity.backend);
|
|
336
|
+
const aliases = exact ? [] : models.filter((model) => (
|
|
337
|
+
comparableModelLabel(model.model) === target || comparableModelLabel(model.displayName) === target
|
|
338
|
+
));
|
|
339
|
+
const model = exact ?? (aliases.length === 1 ? aliases[0] : null);
|
|
340
|
+
if (!model) return null;
|
|
341
|
+
const nativeReasoningLevels = (model.supportedReasoningEfforts ?? []).map((level) => ({
|
|
342
|
+
effort: String(level?.reasoningEffort ?? level?.effort ?? level ?? "").toLowerCase(),
|
|
343
|
+
})).filter((level) => level.effort);
|
|
344
|
+
const supportedReasoningLevels = nativeReasoningLevels;
|
|
345
|
+
const currentReasoningLevel = String(controller?.reasoningEffort ?? "").toLowerCase();
|
|
346
|
+
return {
|
|
347
|
+
slug: String(model.model ?? ""),
|
|
348
|
+
displayName: String(model.displayName ?? model.model ?? ""),
|
|
349
|
+
currentReasoningLevel: nativeReasoningLevels.some(
|
|
350
|
+
(level) => level.effort === currentReasoningLevel,
|
|
351
|
+
) ? currentReasoningLevel : null,
|
|
352
|
+
currentReasoningLevelVisible: supportedReasoningLevels.some(
|
|
353
|
+
(level) => level.effort === currentReasoningLevel,
|
|
354
|
+
),
|
|
355
|
+
defaultReasoningLevel: String(model.defaultReasoningEffort ?? "").toLowerCase(),
|
|
356
|
+
supportedReasoningLevels,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function modelDefinitionFor(identity, selector) {
|
|
361
|
+
return nativeCatalogModelFor(identity) ?? catalogModelFor(identity, selector);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 用 OpenCodex 配置声明的真实档位对滑条做精确交集,过滤 catalog 为支持
|
|
365
|
+
// mock 顶档而额外广告的 max/ultra。键匹配顺序:模型自带的
|
|
366
|
+
// opencodex_capability_provenance → catalog 模型 → 菜单原始 identity;
|
|
367
|
+
// 只做过滤不扩展,配置未声明的模型保持 catalog 原样。
|
|
368
|
+
|
|
369
|
+
function loadModelFavorites(selector) {
|
|
370
|
+
try {
|
|
371
|
+
const value = JSON.parse(localStorage.getItem(selector.favoriteStorageKey) ?? "[]");
|
|
372
|
+
return new Set(Array.isArray(value) ? value.map(String) : []);
|
|
373
|
+
} catch {
|
|
374
|
+
return new Set();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function saveModelFavorites(selector, favorites) {
|
|
379
|
+
try {
|
|
380
|
+
localStorage.setItem(selector.favoriteStorageKey, JSON.stringify(Array.from(favorites)));
|
|
381
|
+
} catch {
|
|
382
|
+
// A disabled localStorage must not break the native picker.
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function nativeModelPickerMenu() {
|
|
387
|
+
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]')).find(
|
|
388
|
+
(menu) => (
|
|
389
|
+
menu.querySelector("[data-reasoning-slider]")
|
|
390
|
+
&& (menu.querySelector("[data-model-picker-view-toggle]") || nativeModelPickerRow(menu))
|
|
391
|
+
),
|
|
392
|
+
) ?? null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function nativeModelPickerRow(menu) {
|
|
396
|
+
const rows = Array.from(menu?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
397
|
+
return rows.find((row) => (
|
|
398
|
+
row.getAttribute("aria-haspopup") === "menu"
|
|
399
|
+
&& !row.hasAttribute("data-model-picker-view-toggle")
|
|
400
|
+
)) ?? rows.find((row) => {
|
|
401
|
+
const aria = String(row.getAttribute("aria-label") ?? "").trim();
|
|
402
|
+
const firstLine = String(row.innerText ?? "").split("\n", 1)[0].trim();
|
|
403
|
+
return /^model(?:\s|$)/i.test(aria) || /^model$/i.test(firstLine);
|
|
404
|
+
}) ?? null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function nativeModelValue(menu) {
|
|
408
|
+
const toggle = menu?.querySelector("[data-model-picker-view-toggle]");
|
|
409
|
+
const toggleModelLabel = toggle?.querySelector('[class*="_ViewToggleModelLabel_"]');
|
|
410
|
+
const toggleValues = String(
|
|
411
|
+
toggle?.hasAttribute("data-model-picker-view-toggle")
|
|
412
|
+
? (toggleModelLabel?.textContent ?? toggle.innerText)
|
|
413
|
+
: "",
|
|
414
|
+
).split("\n").map((value) => value.trim()).filter(Boolean);
|
|
415
|
+
if (toggleValues.length) return toggleValues[0];
|
|
416
|
+
const row = nativeModelPickerRow(menu);
|
|
417
|
+
const values = String(row?.innerText ?? "").split("\n").map((value) => value.trim()).filter(Boolean);
|
|
418
|
+
if (values.length > 1) return values.at(-1);
|
|
419
|
+
const aria = String(row?.getAttribute("aria-label") ?? "").trim();
|
|
420
|
+
return aria.replace(/^\S+\s+/, "").trim();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function firstVisibleTextNode(element) {
|
|
424
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
425
|
+
let node = walker.nextNode();
|
|
426
|
+
while (node) {
|
|
427
|
+
if (node.textContent.trim() && !node.parentElement?.closest("svg")) return node;
|
|
428
|
+
node = walker.nextNode();
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function modelMenuItems(menu) {
|
|
434
|
+
return Array.from(menu?.querySelectorAll(
|
|
435
|
+
':scope [role="menuitemradio"], :scope [role="menuitemcheckbox"], :scope [role="menuitem"]',
|
|
436
|
+
) ?? []).filter((item) => item.closest('[role="menu"]') === menu && !item.hasAttribute("aria-haspopup"));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function nativeMenuItemLabel(item) {
|
|
440
|
+
return Array.from(item?.querySelectorAll("span") ?? [])
|
|
441
|
+
.map((span) => String(span.textContent ?? "").trim())
|
|
442
|
+
.find(Boolean) ?? String(item?.textContent ?? "").trim();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function nativeModelSubmenu(parentMenu) {
|
|
446
|
+
if (nativeModelPickerRow(parentMenu)?.getAttribute("aria-expanded") !== "true") return null;
|
|
447
|
+
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]')).find((menu) => (
|
|
448
|
+
!menu.querySelector("[data-reasoning-slider]") && modelMenuItems(menu).length > 1
|
|
449
|
+
)) ?? null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function isNativeEffortSubmenu(menu) {
|
|
453
|
+
const effortLabels = new Set([
|
|
454
|
+
"none", "minimal", "light", "low", "medium", "high", "extra high", "max", "ultra",
|
|
455
|
+
]);
|
|
456
|
+
if (!menu || menu.querySelector("[data-reasoning-slider]")) return false;
|
|
457
|
+
const labels = modelMenuItems(menu).map((item) => (
|
|
458
|
+
nativeMenuItemLabel(item).toLowerCase()
|
|
459
|
+
)).filter(Boolean);
|
|
460
|
+
return labels.length > 1 && labels.every((label) => effortLabels.has(label));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function nativeEffortSubmenu() {
|
|
464
|
+
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]'))
|
|
465
|
+
.find((menu) => isNativeEffortSubmenu(menu)) ?? null;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function nativeModelPickerSubmenus() {
|
|
469
|
+
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]')).filter((menu) => {
|
|
470
|
+
if (menu.querySelector("[data-reasoning-slider]")) return false;
|
|
471
|
+
if (menu.hasAttribute("data-codex-model-slider-menu")) return true;
|
|
472
|
+
return isNativeEffortSubmenu(menu);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function closeReactControlledMenu(element) {
|
|
477
|
+
return setReactControlledMenuOpen(element, false);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function closeNativeModelPickerSubmenus() {
|
|
481
|
+
const submenus = nativeModelPickerSubmenus();
|
|
482
|
+
const parentMenu = nativeModelPickerMenu();
|
|
483
|
+
const expandedRows = Array.from(
|
|
484
|
+
parentMenu?.querySelectorAll('[role="menuitem"][aria-haspopup="menu"][aria-expanded="true"]') ?? [],
|
|
485
|
+
);
|
|
486
|
+
let controlledMenuClosed = false;
|
|
487
|
+
for (const row of expandedRows) {
|
|
488
|
+
controlledMenuClosed = closeReactControlledMenu(row) || controlledMenuClosed;
|
|
489
|
+
}
|
|
490
|
+
for (const submenu of submenus.reverse()) {
|
|
491
|
+
if (controlledMenuClosed || closeReactControlledMenu(submenu)) continue;
|
|
492
|
+
submenu.dispatchEvent(new KeyboardEvent("keydown", {
|
|
493
|
+
key: "Escape",
|
|
494
|
+
code: "Escape",
|
|
495
|
+
bubbles: true,
|
|
496
|
+
cancelable: true,
|
|
497
|
+
}));
|
|
498
|
+
}
|
|
499
|
+
return submenus.length > 0;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function endNativeEffortTransaction() {
|
|
503
|
+
clearTimeout(modelSelectorEffortTransactionTimer);
|
|
504
|
+
modelSelectorEffortTransactionTimer = null;
|
|
505
|
+
modelSelectorPendingOutsideDismiss = false;
|
|
506
|
+
document.documentElement.removeAttribute("data-codex-model-slider-selecting-effort");
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function beginNativeEffortTransaction() {
|
|
510
|
+
clearTimeout(modelSelectorEffortTransactionTimer);
|
|
511
|
+
document.documentElement.setAttribute("data-codex-model-slider-selecting-effort", "");
|
|
512
|
+
modelSelectorEffortTransactionTimer = setTimeout(() => {
|
|
513
|
+
closeNativeModelPickerSubmenus();
|
|
514
|
+
endNativeEffortTransaction();
|
|
515
|
+
}, 2_000);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function deferNativeEffortOutsideDismiss(event) {
|
|
519
|
+
const insideMenu = event.target.closest?.('[role="menu"]');
|
|
520
|
+
if (insideMenu) return;
|
|
521
|
+
if (event.type === "pointerdown" && modelSelectorEffortTransactionTimer != null) {
|
|
522
|
+
modelSelectorPendingOutsideDismiss = true;
|
|
523
|
+
modelSelectorSuppressOutsidePointerSequence = true;
|
|
524
|
+
}
|
|
525
|
+
if (!modelSelectorSuppressOutsidePointerSequence) return;
|
|
526
|
+
event.preventDefault();
|
|
527
|
+
event.stopPropagation();
|
|
528
|
+
event.stopImmediatePropagation();
|
|
529
|
+
if (event.type === "click") modelSelectorSuppressOutsidePointerSequence = false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
for (const type of ["pointerdown", "mousedown", "pointerup", "mouseup", "click"]) {
|
|
533
|
+
document.addEventListener(type, deferNativeEffortOutsideDismiss, {
|
|
534
|
+
capture: true,
|
|
535
|
+
signal: signal,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function modelItemRawValue(item) {
|
|
540
|
+
if (item.dataset.codexModelSliderRaw) return item.dataset.codexModelSliderRaw;
|
|
541
|
+
return String(item.innerText ?? "").split("\n").map((value) => value.trim()).filter(Boolean).join(" ");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function visibleBlockingDialog() {
|
|
545
|
+
return Array.from(document.querySelectorAll('[role="dialog"]')).some((dialog) => {
|
|
546
|
+
const rect = dialog.getBoundingClientRect();
|
|
547
|
+
const style = getComputedStyle(dialog);
|
|
548
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function dispatchPointerClick(element) {
|
|
553
|
+
if (!element) return;
|
|
554
|
+
const rect = element.getBoundingClientRect();
|
|
555
|
+
const init = {
|
|
556
|
+
bubbles: true,
|
|
557
|
+
cancelable: true,
|
|
558
|
+
view: window,
|
|
559
|
+
pointerType: "mouse",
|
|
560
|
+
button: 0,
|
|
561
|
+
clientX: rect.left + rect.width / 2,
|
|
562
|
+
clientY: rect.top + rect.height / 2,
|
|
563
|
+
};
|
|
564
|
+
for (const type of [
|
|
565
|
+
"pointermove", "pointerover", "mouseover", "mousemove",
|
|
566
|
+
"pointerdown", "mousedown", "pointerup", "mouseup", "click",
|
|
567
|
+
]) {
|
|
568
|
+
const EventType = type.startsWith("pointer") ? PointerEvent : MouseEvent;
|
|
569
|
+
const pressed = type === "pointerdown" || type === "mousedown";
|
|
570
|
+
element.dispatchEvent(new EventType(type, {
|
|
571
|
+
...init,
|
|
572
|
+
buttons: pressed ? 1 : 0,
|
|
573
|
+
detail: type === "click" ? 1 : 0,
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function returnToNativeModelSlider(attempt = 0) {
|
|
579
|
+
if (visibleBlockingDialog()) {
|
|
580
|
+
closeNativeModelPickerSubmenus();
|
|
581
|
+
setTimeout(endNativeEffortTransaction, 120);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (closeNativeModelPickerSubmenus()) {
|
|
585
|
+
setTimeout(() => returnToNativeModelSlider(attempt), 100);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
let parentMenu = nativeModelPickerMenu();
|
|
589
|
+
if (parentMenu) {
|
|
590
|
+
const toggle = parentMenu.querySelector("[data-model-picker-view-toggle]");
|
|
591
|
+
if (toggle?.getAttribute("aria-expanded") === "true") toggle.click();
|
|
592
|
+
setTimeout(() => {
|
|
593
|
+
if (!nativeModelPickerMenu() && attempt < 3) {
|
|
594
|
+
returnToNativeModelSlider(attempt + 1);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
modelSelectorPendingReturn = false;
|
|
598
|
+
if (nativeModelPickerSubmenus().length === 0) endNativeEffortTransaction();
|
|
599
|
+
queueEnsure();
|
|
600
|
+
}, 140);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const trigger = nativeComposerTrigger();
|
|
604
|
+
if (!trigger) return;
|
|
605
|
+
dispatchPointerClick(trigger);
|
|
606
|
+
setTimeout(() => {
|
|
607
|
+
if (!nativeModelPickerMenu() && attempt < 3) {
|
|
608
|
+
returnToNativeModelSlider(attempt + 1);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
modelSelectorPendingReturn = false;
|
|
612
|
+
if (nativeModelPickerSubmenus().length === 0) endNativeEffortTransaction();
|
|
613
|
+
queueEnsure();
|
|
614
|
+
}, 140);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function scheduleModelSliderReturn() {
|
|
618
|
+
modelSelectorPendingReturn = true;
|
|
619
|
+
clearTimeout(modelSelectorReturnTimer);
|
|
620
|
+
modelSelectorReturnTimer = setTimeout(() => {
|
|
621
|
+
modelSelectorReturnTimer = null;
|
|
622
|
+
returnToNativeModelSlider();
|
|
623
|
+
}, 260);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function compareEnhancedModelItems(a, b, favorites) {
|
|
627
|
+
const favA = Number(favorites.has(a.dataset.codexModelSliderRaw));
|
|
628
|
+
const favB = Number(favorites.has(b.dataset.codexModelSliderRaw));
|
|
629
|
+
if (favA !== favB) return favB - favA;
|
|
630
|
+
const rankA = nativeGptSortRank(a.dataset.codexModelSliderRaw);
|
|
631
|
+
const rankB = nativeGptSortRank(b.dataset.codexModelSliderRaw);
|
|
632
|
+
if (Boolean(rankA) !== Boolean(rankB)) return rankA ? -1 : 1;
|
|
633
|
+
if (rankA && rankB) return rankA.rank - rankB.rank;
|
|
634
|
+
return 0;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function renderEnhancedModelMenu(menu, selector) {
|
|
638
|
+
const items = modelMenuItems(menu).filter((item) => item.dataset.codexModelSliderRaw);
|
|
639
|
+
if (!items.length) return;
|
|
640
|
+
const favorites = loadModelFavorites(selector);
|
|
641
|
+
items.forEach((item) => { item.style.display = ""; });
|
|
642
|
+
if (!menu.hasAttribute("data-codex-model-slider-controller-menu")) {
|
|
643
|
+
if (!modelSelectorMenuSizeFrames.has(menu)) {
|
|
644
|
+
const frame = requestAnimationFrame(() => {
|
|
645
|
+
modelSelectorMenuSizeFrames.delete(menu);
|
|
646
|
+
sizeModelMenuViewport(menu, selector.maxVisibleItems);
|
|
647
|
+
});
|
|
648
|
+
modelSelectorMenuSizeFrames.set(menu, frame);
|
|
649
|
+
}
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const starred = items.filter((item) => favorites.has(item.dataset.codexModelSliderRaw));
|
|
653
|
+
const ordinary = items.filter((item) => !favorites.has(item.dataset.codexModelSliderRaw));
|
|
654
|
+
const desired = items.slice().sort((a, b) => compareEnhancedModelItems(a, b, favorites));
|
|
655
|
+
const parent = items[0].parentElement;
|
|
656
|
+
if (!parent || !desired.every((item) => item.parentElement === parent)) return;
|
|
657
|
+
const current = Array.from(parent.children).filter((child) => child.hasAttribute?.("data-codex-model-slider-item"));
|
|
658
|
+
if (desired.some((item, index) => current[index] !== item)) desired.forEach((item) => parent.append(item));
|
|
659
|
+
|
|
660
|
+
let divider = parent.querySelector(":scope > [data-codex-model-slider-divider]");
|
|
661
|
+
if (starred.length && ordinary.length) {
|
|
662
|
+
if (!divider) {
|
|
663
|
+
divider = document.createElement("div");
|
|
664
|
+
divider.setAttribute("data-codex-model-slider-divider", "");
|
|
665
|
+
divider.setAttribute("role", "separator");
|
|
666
|
+
}
|
|
667
|
+
const firstOrdinary = desired.find((item) => !favorites.has(item.dataset.codexModelSliderRaw));
|
|
668
|
+
if (firstOrdinary && divider.nextElementSibling !== firstOrdinary) {
|
|
669
|
+
parent.insertBefore(divider, firstOrdinary);
|
|
670
|
+
}
|
|
671
|
+
} else {
|
|
672
|
+
divider?.remove();
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (!modelSelectorMenuSizeFrames.has(menu)) {
|
|
676
|
+
const frame = requestAnimationFrame(() => {
|
|
677
|
+
modelSelectorMenuSizeFrames.delete(menu);
|
|
678
|
+
sizeModelMenuViewport(menu, selector.maxVisibleItems);
|
|
679
|
+
});
|
|
680
|
+
modelSelectorMenuSizeFrames.set(menu, frame);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function sizeModelMenuViewport(menu, maxVisibleItems) {
|
|
685
|
+
if (!menu.isConnected) return;
|
|
686
|
+
const items = modelMenuItems(menu).filter((item) => item.dataset.codexModelSliderRaw);
|
|
687
|
+
if (items.length <= maxVisibleItems) {
|
|
688
|
+
menu.removeAttribute("data-codex-model-slider-overflow");
|
|
689
|
+
menu.style.removeProperty("--codex-model-slider-menu-max-height");
|
|
690
|
+
menu.style.removeProperty("max-height");
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const lastVisibleItem = items[maxVisibleItems - 1];
|
|
694
|
+
const menuRect = menu.getBoundingClientRect();
|
|
695
|
+
const itemRect = lastVisibleItem.getBoundingClientRect();
|
|
696
|
+
const paddingBottom = Number.parseFloat(getComputedStyle(menu).paddingBottom) || 0;
|
|
697
|
+
const maxHeight = Math.ceil(itemRect.bottom - menuRect.top + menu.scrollTop + paddingBottom);
|
|
698
|
+
menu.setAttribute("data-codex-model-slider-overflow", "");
|
|
699
|
+
menu.style.setProperty("--codex-model-slider-menu-max-height", `${maxHeight}px`);
|
|
700
|
+
menu.style.overflowY = "auto";
|
|
701
|
+
positionModelSubmenu(menu);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function toggleModelFavorite(event, menu, selector, raw, label) {
|
|
705
|
+
event.preventDefault();
|
|
706
|
+
event.stopPropagation();
|
|
707
|
+
event.stopImmediatePropagation();
|
|
708
|
+
const favorites = loadModelFavorites(selector);
|
|
709
|
+
if (favorites.has(raw)) favorites.delete(raw);
|
|
710
|
+
else favorites.add(raw);
|
|
711
|
+
saveModelFavorites(selector, favorites);
|
|
712
|
+
for (const item of modelMenuItems(menu)) {
|
|
713
|
+
if (!item.dataset.codexModelSliderRaw) continue;
|
|
714
|
+
const starred = favorites.has(item.dataset.codexModelSliderRaw);
|
|
715
|
+
const button = item.querySelector("[data-codex-model-slider-star]");
|
|
716
|
+
if (!button) continue;
|
|
717
|
+
button.replaceChildren(createModelSelectorIcon(starred ? selector.icons.unstar : selector.icons.star));
|
|
718
|
+
const itemLabel = item.querySelector("[data-codex-model-slider-name]")?.textContent ?? label;
|
|
719
|
+
button.setAttribute("aria-label", selector.strings[starred ? "unstar" : "star"].replace("{model}", itemLabel));
|
|
720
|
+
button.setAttribute("data-minecodex-tooltip", selector.strings[starred ? "unstar" : "star"].replace("{model}", itemLabel));
|
|
721
|
+
}
|
|
722
|
+
renderEnhancedModelMenu(menu, selector);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function enhanceModelItem(item, menu, selector, index, {
|
|
726
|
+
rawValue = null,
|
|
727
|
+
selected = null,
|
|
728
|
+
scheduleReturn = true,
|
|
729
|
+
owned = false,
|
|
730
|
+
} = {}) {
|
|
731
|
+
const observedRaw = rawValue ?? modelItemRawValue(item);
|
|
732
|
+
if (!observedRaw) return;
|
|
733
|
+
const observedIdentity = modelIdentity(observedRaw);
|
|
734
|
+
const nativeDescriptor = nativeModelDescriptorForItem(item, observedRaw);
|
|
735
|
+
const catalogModel = catalogModelFor(observedIdentity, selector);
|
|
736
|
+
const raw = catalogModel?.slug ?? nativeDescriptor?.slug ?? observedRaw;
|
|
737
|
+
// 先刷新 dataset:React 原地复用菜单节点时,raw/排序/收藏键不能基于过期值。
|
|
738
|
+
item.dataset.codexModelSliderRaw = raw;
|
|
739
|
+
item.dataset.codexModelSliderItem = "";
|
|
740
|
+
item.dataset.codexModelSliderOrder = String(index);
|
|
741
|
+
item.dataset.codexModelSliderIdentitySource = catalogModel ? "catalog" : nativeDescriptor ? "native" : "visible";
|
|
742
|
+
if (item.querySelector("[data-codex-model-slider-row]")) return;
|
|
743
|
+
item.toggleAttribute(
|
|
744
|
+
"data-codex-model-slider-selected",
|
|
745
|
+
selected ?? Boolean(item.querySelector("svg")),
|
|
746
|
+
);
|
|
747
|
+
if (!owned) {
|
|
748
|
+
if (scheduleReturn && !item.hasAttribute("data-codex-model-slider-return-listener")) {
|
|
749
|
+
item.setAttribute("data-codex-model-slider-return-listener", "");
|
|
750
|
+
item.addEventListener("click", scheduleModelSliderReturn);
|
|
751
|
+
}
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const identity = modelIdentity(raw);
|
|
755
|
+
const display = modelDisplayLabel(identity, selector);
|
|
756
|
+
const provider = providerLabel(identity.provider, selector);
|
|
757
|
+
const brand = brandForModel(identity, selector);
|
|
758
|
+
const favorites = loadModelFavorites(selector);
|
|
759
|
+
const starred = favorites.has(raw);
|
|
760
|
+
const row = document.createElement("span");
|
|
761
|
+
row.setAttribute("data-codex-model-slider-row", "");
|
|
762
|
+
const brandRoot = document.createElement("span");
|
|
763
|
+
brandRoot.setAttribute("data-codex-model-slider-brand", brand?.id ?? "fallback");
|
|
764
|
+
brandRoot.append(createModelSelectorIcon(brand?.icon ?? selector.icons.fallback, { brand: Boolean(brand) }));
|
|
765
|
+
const copy = document.createElement("span");
|
|
766
|
+
copy.setAttribute("data-codex-model-slider-copy", "");
|
|
767
|
+
const name = document.createElement("span");
|
|
768
|
+
name.setAttribute("data-codex-model-slider-name", "");
|
|
769
|
+
name.textContent = display;
|
|
770
|
+
copy.append(name);
|
|
771
|
+
if (provider) {
|
|
772
|
+
const secondary = document.createElement("span");
|
|
773
|
+
secondary.setAttribute("data-codex-model-slider-provider", "");
|
|
774
|
+
secondary.textContent = provider;
|
|
775
|
+
copy.append(secondary);
|
|
776
|
+
}
|
|
777
|
+
row.append(brandRoot, copy);
|
|
778
|
+
const star = document.createElement("button");
|
|
779
|
+
star.type = "button";
|
|
780
|
+
star.setAttribute("data-codex-model-slider-star", starred ? "unstar" : "star");
|
|
781
|
+
star.setAttribute("aria-label", selector.strings[starred ? "unstar" : "star"].replace("{model}", display));
|
|
782
|
+
star.setAttribute("data-minecodex-tooltip", selector.strings[starred ? "unstar" : "star"].replace("{model}", display));
|
|
783
|
+
star.append(createModelSelectorIcon(starred ? selector.icons.unstar : selector.icons.star));
|
|
784
|
+
row.append(star);
|
|
785
|
+
item.replaceChildren(row);
|
|
786
|
+
if (scheduleReturn && !item.hasAttribute("data-codex-model-slider-return-listener")) {
|
|
787
|
+
item.setAttribute("data-codex-model-slider-return-listener", "");
|
|
788
|
+
item.addEventListener("click", (event) => {
|
|
789
|
+
if (!event.target.closest?.("[data-codex-model-slider-star]")) scheduleModelSliderReturn();
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function enhanceNativeModelSubmenu(menu, selector) {
|
|
795
|
+
menu.setAttribute("data-codex-model-slider-menu", "");
|
|
796
|
+
observeModelSubmenuPosition(menu);
|
|
797
|
+
positionModelSubmenu(menu);
|
|
798
|
+
const items = modelMenuItems(menu);
|
|
799
|
+
items.forEach((item, index) => enhanceModelItem(item, menu, selector, index));
|
|
800
|
+
if (!menu.hasAttribute("data-codex-model-slider-events")) {
|
|
801
|
+
menu.setAttribute("data-codex-model-slider-events", "");
|
|
802
|
+
menu.addEventListener("click", (event) => {
|
|
803
|
+
const star = event.target.closest?.("[data-codex-model-slider-star]");
|
|
804
|
+
if (star) {
|
|
805
|
+
const item = star.closest("[data-codex-model-slider-item]");
|
|
806
|
+
const raw = item?.dataset.codexModelSliderRaw;
|
|
807
|
+
if (!raw) return;
|
|
808
|
+
const label = item.querySelector("[data-codex-model-slider-name]")?.textContent ?? raw;
|
|
809
|
+
toggleModelFavorite(event, menu, selector, raw, label);
|
|
810
|
+
event.stopImmediatePropagation();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (event.target.closest?.("[data-codex-model-slider-item]")) scheduleModelSliderReturn();
|
|
814
|
+
}, { capture: true });
|
|
815
|
+
menu.addEventListener("pointerdown", (event) => {
|
|
816
|
+
const star = event.target.closest?.("[data-codex-model-slider-star]");
|
|
817
|
+
if (star) {
|
|
818
|
+
event.stopPropagation();
|
|
819
|
+
event.stopImmediatePropagation();
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
if (event.target.closest?.("[data-codex-model-slider-item]")) scheduleModelSliderReturn();
|
|
823
|
+
}, { capture: true });
|
|
824
|
+
for (const type of ["mousedown", "pointerup", "mouseup"]) {
|
|
825
|
+
menu.addEventListener(type, (event) => {
|
|
826
|
+
if (!event.target.closest?.("[data-codex-model-slider-star]")) return;
|
|
827
|
+
event.stopPropagation();
|
|
828
|
+
event.stopImmediatePropagation();
|
|
829
|
+
}, { capture: true });
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
renderEnhancedModelMenu(menu, selector);
|
|
833
|
+
if (!menu.hasAttribute("data-codex-model-slider-positioned")) menu.style.visibility = "hidden";
|
|
834
|
+
positionModelSubmenu(menu);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function positionModelSubmenu(menu) {
|
|
838
|
+
const trigger = document.getElementById(menu?.getAttribute("aria-labelledby") ?? "");
|
|
839
|
+
const parentMenu = trigger?.closest('[role="menu"]') ?? nativeModelPickerMenu();
|
|
840
|
+
const wrapper = menu?.parentElement;
|
|
841
|
+
if (
|
|
842
|
+
!parentMenu?.hasAttribute("data-codex-model-slider-generic-menu")
|
|
843
|
+
|| !wrapper?.hasAttribute("data-radix-popper-content-wrapper")
|
|
844
|
+
) return;
|
|
845
|
+
const parentRect = parentMenu.getBoundingClientRect();
|
|
846
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
847
|
+
if (!(parentRect.width > 0 && wrapperRect.width > 0 && wrapperRect.height > 0)) return;
|
|
848
|
+
const inset = 8;
|
|
849
|
+
const left = Math.min(
|
|
850
|
+
innerWidth - wrapperRect.width - inset,
|
|
851
|
+
Math.max(inset, parentRect.right - wrapperRect.width),
|
|
852
|
+
);
|
|
853
|
+
const top = Math.min(
|
|
854
|
+
innerHeight - wrapperRect.height - inset,
|
|
855
|
+
Math.max(inset, parentRect.bottom - wrapperRect.height),
|
|
856
|
+
);
|
|
857
|
+
const transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;
|
|
858
|
+
if (wrapper.style.transform !== transform) wrapper.style.transform = transform;
|
|
859
|
+
if (wrapper.style.getPropertyValue("--radix-popper-transform-origin") !== "100% 100%") {
|
|
860
|
+
wrapper.style.setProperty("--radix-popper-transform-origin", "100% 100%");
|
|
861
|
+
}
|
|
862
|
+
const record = modelSelectorPositionObservers.get(wrapper);
|
|
863
|
+
if (record) record.lastAppliedStyle = wrapper.getAttribute("style") ?? "";
|
|
864
|
+
requestAnimationFrame(() => {
|
|
865
|
+
if (!menu.isConnected) return;
|
|
866
|
+
const positionedRect = wrapper.getBoundingClientRect();
|
|
867
|
+
if (
|
|
868
|
+
Math.abs(positionedRect.right - parentRect.right) <= 2
|
|
869
|
+
&& Math.abs(positionedRect.bottom - parentRect.bottom) <= 2
|
|
870
|
+
) {
|
|
871
|
+
menu.setAttribute("data-codex-model-slider-positioned", "");
|
|
872
|
+
menu.style.removeProperty("visibility");
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function observeModelSubmenuPosition(menu) {
|
|
878
|
+
const wrapper = menu?.parentElement;
|
|
879
|
+
if (!wrapper?.hasAttribute("data-radix-popper-content-wrapper")) return;
|
|
880
|
+
const existing = modelSelectorPositionObservers.get(wrapper);
|
|
881
|
+
if (existing) {
|
|
882
|
+
if (existing.menu !== menu) {
|
|
883
|
+
existing.resizeObserver.unobserve(existing.menu);
|
|
884
|
+
existing.resizeObserver.observe(menu);
|
|
885
|
+
existing.menu = menu;
|
|
886
|
+
}
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
const record = {
|
|
890
|
+
menu,
|
|
891
|
+
queued: false,
|
|
892
|
+
observer: null,
|
|
893
|
+
resizeObserver: null,
|
|
894
|
+
lastAppliedStyle: null,
|
|
895
|
+
};
|
|
896
|
+
const schedule = (mutations = []) => {
|
|
897
|
+
if (
|
|
898
|
+
mutations.length
|
|
899
|
+
&& wrapper.getAttribute("style") === record.lastAppliedStyle
|
|
900
|
+
) return;
|
|
901
|
+
if (record.queued) return;
|
|
902
|
+
record.queued = true;
|
|
903
|
+
requestAnimationFrame(() => {
|
|
904
|
+
record.queued = false;
|
|
905
|
+
if (record.menu.isConnected) positionModelSubmenu(record.menu);
|
|
906
|
+
});
|
|
907
|
+
};
|
|
908
|
+
record.observer = new MutationObserver(schedule);
|
|
909
|
+
record.observer.observe(wrapper, { attributes: true, attributeFilter: ["style"] });
|
|
910
|
+
record.resizeObserver = new ResizeObserver(schedule);
|
|
911
|
+
record.resizeObserver.observe(menu);
|
|
912
|
+
const trigger = document.getElementById(menu.getAttribute("aria-labelledby") ?? "");
|
|
913
|
+
const parentMenu = trigger?.closest('[role="menu"]');
|
|
914
|
+
if (parentMenu) record.resizeObserver.observe(parentMenu);
|
|
915
|
+
modelSelectorPositionObservers.set(wrapper, record);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function pruneModelSubmenuPositionObservers() {
|
|
919
|
+
for (const [wrapper, record] of modelSelectorPositionObservers) {
|
|
920
|
+
if (wrapper.isConnected) continue;
|
|
921
|
+
record.observer.disconnect();
|
|
922
|
+
record.resizeObserver.disconnect();
|
|
923
|
+
modelSelectorPositionObservers.delete(wrapper);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function pruneNativeModelToggleHandlers() {
|
|
928
|
+
for (const [toggle, handlers] of modelSelectorNativeToggleHandlers) {
|
|
929
|
+
if (toggle.isConnected) continue;
|
|
930
|
+
toggle.removeEventListener("click", handlers.openModels);
|
|
931
|
+
toggle.removeEventListener("keydown", handlers.onKeyDown);
|
|
932
|
+
modelSelectorNativeToggleHandlers.delete(toggle);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function restoreGenericModelPicker(container) {
|
|
937
|
+
const original = modelSelectorGenericContainers.get(container);
|
|
938
|
+
if (!original) return;
|
|
939
|
+
original.effectMirror?.disconnect();
|
|
940
|
+
if (container.isConnected) {
|
|
941
|
+
const menu = container.parentElement;
|
|
942
|
+
menu?.querySelector(":scope > [data-codex-model-slider-generic]")?.remove();
|
|
943
|
+
menu?.removeAttribute("data-codex-model-slider-generic-menu");
|
|
944
|
+
menu?.style.removeProperty("--codex-model-slider-menu-center-shift");
|
|
945
|
+
container.style.position = original.position;
|
|
946
|
+
container.style.inset = original.inset;
|
|
947
|
+
container.style.opacity = original.opacity;
|
|
948
|
+
container.style.pointerEvents = original.pointerEvents;
|
|
949
|
+
if (original.ariaHidden == null) container.removeAttribute("aria-hidden");
|
|
950
|
+
else container.setAttribute("aria-hidden", original.ariaHidden);
|
|
951
|
+
container.inert = original.inert;
|
|
952
|
+
}
|
|
953
|
+
modelSelectorGenericContainers.delete(container);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function pruneGenericModelPickerContainers() {
|
|
957
|
+
for (const container of modelSelectorGenericContainers.keys()) {
|
|
958
|
+
if (!container.isConnected) restoreGenericModelPicker(container);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function closeControllerModelMenu() {
|
|
963
|
+
const record = modelSelectorControllerMenu;
|
|
964
|
+
if (!record) return false;
|
|
965
|
+
const placeholder = document.querySelector(
|
|
966
|
+
'[data-codex-intelligence-trigger] [class*="_ModelPickerTriggerPlaceholder_"]',
|
|
967
|
+
);
|
|
968
|
+
if (placeholder?.firstChild && record.placeholderText != null) {
|
|
969
|
+
placeholder.firstChild.nodeValue = record.placeholderText;
|
|
970
|
+
}
|
|
971
|
+
record.button.setAttribute("aria-expanded", "false");
|
|
972
|
+
record.parentMenu.style.position = record.position;
|
|
973
|
+
record.parentMenu.style.overflow = record.overflow;
|
|
974
|
+
record.menu.remove();
|
|
975
|
+
modelSelectorControllerMenu = null;
|
|
976
|
+
return true;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function controllerModelReasoningEffort(controller, model) {
|
|
980
|
+
const current = String(controller?.reasoningEffort ?? "").toLowerCase();
|
|
981
|
+
const supported = (model?.supportedReasoningEfforts ?? []).map((entry) => (
|
|
982
|
+
String(entry?.reasoningEffort ?? entry ?? "").toLowerCase()
|
|
983
|
+
)).filter(Boolean);
|
|
984
|
+
return supported.includes(current)
|
|
985
|
+
? current
|
|
986
|
+
: String(model?.defaultReasoningEffort ?? supported[0] ?? "none").toLowerCase();
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function openControllerModelMenu(parentMenu, modelButton, selector) {
|
|
990
|
+
if (modelSelectorControllerMenu) {
|
|
991
|
+
closeControllerModelMenu();
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
const controller = nativeComposerModelController();
|
|
995
|
+
if (!Array.isArray(controller?.models) || typeof controller.onSelectModel !== "function") return;
|
|
996
|
+
const models = visibleControllerModels(controller);
|
|
997
|
+
if (!models.length) return;
|
|
998
|
+
|
|
999
|
+
const menu = document.createElement("div");
|
|
1000
|
+
menu.className = parentMenu.className;
|
|
1001
|
+
menu.setAttribute("role", "menu");
|
|
1002
|
+
menu.setAttribute("data-state", "open");
|
|
1003
|
+
menu.setAttribute("data-codex-model-slider-menu", "");
|
|
1004
|
+
menu.setAttribute("data-codex-model-slider-controller-menu", "");
|
|
1005
|
+
featureUI.surface(menu, "dropdown");
|
|
1006
|
+
models.forEach((model, index) => {
|
|
1007
|
+
const item = document.createElement("div");
|
|
1008
|
+
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";
|
|
1009
|
+
item.setAttribute("role", "menuitem");
|
|
1010
|
+
item.setAttribute("tabindex", "-1");
|
|
1011
|
+
enhanceModelItem(item, menu, selector, index, {
|
|
1012
|
+
rawValue: String(model.model),
|
|
1013
|
+
selected: String(model.model) === String(controller.model),
|
|
1014
|
+
scheduleReturn: false,
|
|
1015
|
+
owned: true,
|
|
1016
|
+
});
|
|
1017
|
+
menu.append(item);
|
|
1018
|
+
});
|
|
1019
|
+
menu.addEventListener("click", (event) => {
|
|
1020
|
+
const star = event.target.closest?.("[data-codex-model-slider-star]");
|
|
1021
|
+
const item = event.target.closest?.("[data-codex-model-slider-item]");
|
|
1022
|
+
if (!item) return;
|
|
1023
|
+
if (star) {
|
|
1024
|
+
const raw = item.dataset.codexModelSliderRaw;
|
|
1025
|
+
const label = item.querySelector("[data-codex-model-slider-name]")?.textContent ?? raw;
|
|
1026
|
+
toggleModelFavorite(event, menu, selector, raw, label);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
const currentController = nativeComposerModelController();
|
|
1030
|
+
const raw = item.dataset.codexModelSliderRaw;
|
|
1031
|
+
const model = currentController?.models?.find((candidate) => String(candidate.model) === raw);
|
|
1032
|
+
if (!model || typeof currentController.onSelectModel !== "function") return;
|
|
1033
|
+
const reasoningEffort = controllerModelReasoningEffort(currentController, model);
|
|
1034
|
+
currentController.onSelectModel(raw, reasoningEffort);
|
|
1035
|
+
closeControllerModelMenu();
|
|
1036
|
+
queueEnsure();
|
|
1037
|
+
});
|
|
1038
|
+
menu.addEventListener("keydown", (event) => {
|
|
1039
|
+
if (event.key === "Escape") {
|
|
1040
|
+
event.preventDefault();
|
|
1041
|
+
closeControllerModelMenu();
|
|
1042
|
+
modelButton.focus();
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1046
|
+
const item = event.target.closest?.("[data-codex-model-slider-item]");
|
|
1047
|
+
if (!item) return;
|
|
1048
|
+
event.preventDefault();
|
|
1049
|
+
item.click();
|
|
1050
|
+
});
|
|
1051
|
+
renderEnhancedModelMenu(menu, selector);
|
|
1052
|
+
|
|
1053
|
+
const placeholder = document.querySelector(
|
|
1054
|
+
'[data-codex-intelligence-trigger] [class*="_ModelPickerTriggerPlaceholder_"]',
|
|
1055
|
+
);
|
|
1056
|
+
const placeholderText = placeholder?.firstChild?.nodeValue ?? null;
|
|
1057
|
+
// 保留产品行为:模型列表打开时 chip 显示 Select model。只能原地更新
|
|
1058
|
+
// 同一个文本节点(nodeValue),不能 textContent 替换整个节点,否则
|
|
1059
|
+
// React 卸载菜单时 removeChild 找不到旧文本节点会触发错误边界。
|
|
1060
|
+
if (placeholder?.firstChild) {
|
|
1061
|
+
placeholder.firstChild.nodeValue = locale === "zh-CN" ? "选择模型" : "Select model";
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
modelSelectorControllerMenu = {
|
|
1065
|
+
menu,
|
|
1066
|
+
parentMenu,
|
|
1067
|
+
button: modelButton,
|
|
1068
|
+
placeholderText,
|
|
1069
|
+
position: parentMenu.style.position,
|
|
1070
|
+
overflow: parentMenu.style.overflow,
|
|
1071
|
+
};
|
|
1072
|
+
parentMenu.style.position = "relative";
|
|
1073
|
+
parentMenu.style.overflow = "visible";
|
|
1074
|
+
parentMenu.append(menu);
|
|
1075
|
+
sizeModelMenuViewport(menu, selector.maxVisibleItems);
|
|
1076
|
+
modelButton.setAttribute("aria-expanded", "true");
|
|
1077
|
+
(menu.querySelector('[data-codex-model-slider-selected]')
|
|
1078
|
+
?? menu.querySelector('[role="menuitem"]'))?.focus();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// ========================================================================
|
|
1082
|
+
// 原生 effort 子菜单与滑条视觉 — effort 事务、Fast/通用滑条视觉与状态同步
|
|
1083
|
+
// ========================================================================
|
|
1084
|
+
|
|
1085
|
+
function nativeEffortRow(parentMenu) {
|
|
1086
|
+
return Array.from(parentMenu?.querySelectorAll('[role="menuitem"][aria-haspopup="menu"]') ?? [])
|
|
1087
|
+
.filter((row) => !row.hasAttribute("data-model-picker-view-toggle"))[1] ?? null;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function effortLabel(effort) {
|
|
1091
|
+
return new Map([
|
|
1092
|
+
["none", "None"], ["minimal", "Minimal"], ["low", "Light"], ["medium", "Medium"],
|
|
1093
|
+
["high", "High"], ["xhigh", "Extra High"], ["max", "Max"], ["ultra", "Ultra"],
|
|
1094
|
+
]).get(String(effort).toLowerCase()) ?? formatIdentifier(effort);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function selectNativeEffort(parentMenu, effort) {
|
|
1098
|
+
const identity = modelIdentity(nativeModelValue(parentMenu));
|
|
1099
|
+
const model = modelDefinitionFor(identity, modelSelectorFeature?.modelSelector);
|
|
1100
|
+
if (!model?.supportedReasoningLevels?.some((level) => level.effort === String(effort).toLowerCase())) return;
|
|
1101
|
+
const controller = nativeComposerModelController();
|
|
1102
|
+
if (typeof controller?.onSelectReasoningEffort === "function") {
|
|
1103
|
+
controller.onSelectReasoningEffort(effort);
|
|
1104
|
+
if (modelSelectorPendingOutsideDismiss) {
|
|
1105
|
+
closeReactControlledMenu(nativeComposerTrigger());
|
|
1106
|
+
modelSelectorPendingOutsideDismiss = false;
|
|
1107
|
+
}
|
|
1108
|
+
queueEnsure();
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
if (typeof controller?.onCommitPower === "function") {
|
|
1112
|
+
const normalizedEffort = String(effort).toLowerCase();
|
|
1113
|
+
const activeModel = String(controller.model ?? nativeCatalogModelFor(identity)?.slug ?? identity.raw);
|
|
1114
|
+
const next = controller.powerSelections?.find((selection) => (
|
|
1115
|
+
String(selection?.model ?? "") === activeModel
|
|
1116
|
+
&& String(selection?.reasoningEffort ?? "").toLowerCase() === normalizedEffort
|
|
1117
|
+
));
|
|
1118
|
+
const previous = controller.selectedPowerSelection
|
|
1119
|
+
?? controller.powerSelections?.find((selection) => (
|
|
1120
|
+
String(selection?.model ?? "") === activeModel
|
|
1121
|
+
&& String(selection?.reasoningEffort ?? "").toLowerCase()
|
|
1122
|
+
=== String(controller.reasoningEffort ?? "").toLowerCase()
|
|
1123
|
+
))
|
|
1124
|
+
?? controller.fallbackPowerSelection;
|
|
1125
|
+
if (!next || !previous) return;
|
|
1126
|
+
controller.onSelectPower?.(next);
|
|
1127
|
+
controller.onCommitPower(next, previous);
|
|
1128
|
+
if (modelSelectorPendingOutsideDismiss) {
|
|
1129
|
+
closeReactControlledMenu(nativeComposerTrigger());
|
|
1130
|
+
modelSelectorPendingOutsideDismiss = false;
|
|
1131
|
+
}
|
|
1132
|
+
queueEnsure();
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
const row = nativeEffortRow(parentMenu);
|
|
1136
|
+
if (!row) return;
|
|
1137
|
+
beginNativeEffortTransaction();
|
|
1138
|
+
if (!setReactControlledMenuOpen(row, true)) {
|
|
1139
|
+
endNativeEffortTransaction();
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
let observer = null;
|
|
1143
|
+
let selectionTimer = null;
|
|
1144
|
+
const select = () => {
|
|
1145
|
+
const submenu = nativeEffortSubmenu();
|
|
1146
|
+
const desired = effortLabel(effort).toLowerCase();
|
|
1147
|
+
const item = modelMenuItems(submenu).find((candidate) => (
|
|
1148
|
+
nativeMenuItemLabel(candidate).toLowerCase() === desired
|
|
1149
|
+
));
|
|
1150
|
+
if (!item) return;
|
|
1151
|
+
observer?.disconnect();
|
|
1152
|
+
clearTimeout(selectionTimer);
|
|
1153
|
+
const dismissAfterCommit = modelSelectorPendingOutsideDismiss;
|
|
1154
|
+
if (!invokeNativeMenuItemSelect(item)) {
|
|
1155
|
+
endNativeEffortTransaction();
|
|
1156
|
+
queueEnsure();
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (dismissAfterCommit) {
|
|
1160
|
+
closeReactControlledMenu(item);
|
|
1161
|
+
closeReactControlledMenu(nativeComposerTrigger());
|
|
1162
|
+
modelSelectorPendingReturn = false;
|
|
1163
|
+
endNativeEffortTransaction();
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
scheduleModelSliderReturn();
|
|
1167
|
+
};
|
|
1168
|
+
observer = new MutationObserver(select);
|
|
1169
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1170
|
+
selectionTimer = setTimeout(() => {
|
|
1171
|
+
observer.disconnect();
|
|
1172
|
+
endNativeEffortTransaction();
|
|
1173
|
+
queueEnsure();
|
|
1174
|
+
}, 1_500);
|
|
1175
|
+
select();
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
function setGenericSliderVisual(rail, index, count) {
|
|
1179
|
+
const progress = count <= 1 ? 0 : (index / (count - 1)) * 100;
|
|
1180
|
+
const nativeRange = rail.querySelector("[data-codex-model-slider-native-range]");
|
|
1181
|
+
const nativeThumb = rail.querySelector("[data-codex-model-slider-native-thumb]");
|
|
1182
|
+
if (nativeRange && nativeThumb) {
|
|
1183
|
+
const thumbRadius = 13;
|
|
1184
|
+
const correction = thumbRadius - (progress / 50) * thumbRadius;
|
|
1185
|
+
const segment = count <= 1 ? 100 : 100 / (count - 1);
|
|
1186
|
+
const edgeScale = Math.min(progress / segment, (100 - progress) / segment, 1);
|
|
1187
|
+
const edgeCorrection = correction * Math.max(0, edgeScale);
|
|
1188
|
+
nativeRange.style.transform = `translateX(calc(${progress - 100}% + ${edgeCorrection}px))`;
|
|
1189
|
+
nativeThumb.style.left = `calc(${progress}% + ${correction}px)`;
|
|
1190
|
+
const particleClip = rail.querySelector('[class*="_FastParticleClip_"]');
|
|
1191
|
+
if (particleClip) particleClip.style.clipPath = `inset(0 calc(${100 - progress}% - ${edgeCorrection}px) 0 0)`;
|
|
1192
|
+
}
|
|
1193
|
+
rail.style.setProperty("--codex-model-slider-progress", `${progress}%`);
|
|
1194
|
+
rail.querySelectorAll("[data-codex-model-slider-tick]").forEach((tick, tickIndex) => {
|
|
1195
|
+
tick.setAttribute("data-selected", String(tickIndex <= index));
|
|
1196
|
+
});
|
|
1197
|
+
rail.setAttribute("aria-valuenow", String(index));
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function syncGenericMaximumVisual(rail, effortText, fastButton, maximum) {
|
|
1201
|
+
const value = String(maximum);
|
|
1202
|
+
rail?.setAttribute("data-highlighted", value);
|
|
1203
|
+
rail?.setAttribute("data-max", value);
|
|
1204
|
+
effortText?.setAttribute("data-maximum", value);
|
|
1205
|
+
fastButton?.setAttribute("data-max-power-selection", value);
|
|
1206
|
+
const maxEffects = rail?.querySelector('[class*="_MaxEffects_"]');
|
|
1207
|
+
if (maxEffects) {
|
|
1208
|
+
maxEffects.style.opacity = maximum ? "1" : "0";
|
|
1209
|
+
if (maximum) maxEffects.style.transform = "translateX(0)";
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
function neutralizeStaleSliderVisual(rail) {
|
|
1214
|
+
for (const selector of [
|
|
1215
|
+
"[data-codex-model-slider-native-range]",
|
|
1216
|
+
"[data-codex-model-slider-native-thumb]",
|
|
1217
|
+
"[data-codex-model-slider-range]",
|
|
1218
|
+
"[data-codex-model-slider-thumb]",
|
|
1219
|
+
]) {
|
|
1220
|
+
rail.querySelectorAll(selector).forEach((element) => {
|
|
1221
|
+
element.style.setProperty("visibility", "hidden");
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
rail.querySelectorAll("[data-codex-model-slider-tick]").forEach((tick) => {
|
|
1225
|
+
tick.removeAttribute("data-selected");
|
|
1226
|
+
});
|
|
1227
|
+
rail.removeAttribute("aria-valuenow");
|
|
1228
|
+
rail.style.removeProperty("--codex-model-slider-progress");
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function nativeParticleSeed(index, salt) {
|
|
1232
|
+
const value = Math.sin((index + 1) * 12.9898 + salt * 78.233) * 43758.5453;
|
|
1233
|
+
return value - Math.floor(value);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
function syncNativeFastParticles(rail, enabled, entering = false) {
|
|
1237
|
+
const track = Array.from(rail.children).find((element) => String(element.className).includes("_Track_"));
|
|
1238
|
+
if (!track) return;
|
|
1239
|
+
const staticParticles = rail.querySelector('[class*="_ParticleTransition_"]');
|
|
1240
|
+
if (staticParticles && !matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
1241
|
+
staticParticles.__codexStaticParticleFade?.cancel();
|
|
1242
|
+
staticParticles.style.opacity = enabled ? "0" : "1";
|
|
1243
|
+
if (entering) {
|
|
1244
|
+
// 动态粒子渐显时,让原有星点在同一时间淡出。
|
|
1245
|
+
staticParticles.__codexStaticParticleFade = staticParticles.animate(
|
|
1246
|
+
[{ opacity: 1 }, { opacity: 0 }],
|
|
1247
|
+
{ duration: 240, easing: "ease-out" },
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
track.querySelectorAll('[class*="_FastParticleClip_"]').forEach((element) => element.remove());
|
|
1252
|
+
if (!enabled || matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
1253
|
+
const clipClass = nativeCssModuleClass("FastParticleClip");
|
|
1254
|
+
const particlesClass = nativeCssModuleClass("FastTrackParticles");
|
|
1255
|
+
const pathClass = nativeCssModuleClass("FastTrackParticlePath");
|
|
1256
|
+
const particleClass = nativeCssModuleClass("TrackParticle");
|
|
1257
|
+
if (!clipClass || !particlesClass || !pathClass || !particleClass) return;
|
|
1258
|
+
const clip = document.createElement("span");
|
|
1259
|
+
clip.className = clipClass;
|
|
1260
|
+
clip.setAttribute("aria-hidden", "true");
|
|
1261
|
+
clip.style.opacity = "1";
|
|
1262
|
+
const particles = document.createElement("span");
|
|
1263
|
+
particles.className = particlesClass;
|
|
1264
|
+
particles.setAttribute("aria-hidden", "true");
|
|
1265
|
+
particles.setAttribute("data-animation-active", "true");
|
|
1266
|
+
const travelDuration = 1.9;
|
|
1267
|
+
const stagger = travelDuration / 14;
|
|
1268
|
+
const valueMax = Number(rail.getAttribute("aria-valuemax") ?? 0);
|
|
1269
|
+
const valueNow = Number(rail.getAttribute("aria-valuenow") ?? 0);
|
|
1270
|
+
const initialStartPercent = valueMax > 0 ? (valueNow / valueMax) * 100 : 0;
|
|
1271
|
+
const remainingTravel = 1 - Math.min(Math.max(initialStartPercent, 0), 100) / 100;
|
|
1272
|
+
particles.append(...Array.from({ length: 14 }, (_, index) => {
|
|
1273
|
+
const speed = 1 + (nativeParticleSeed(index, 21) - 0.5) * 0.4;
|
|
1274
|
+
const duration = travelDuration / speed;
|
|
1275
|
+
const path = document.createElement("span");
|
|
1276
|
+
path.className = pathClass;
|
|
1277
|
+
path.style.animationDelay = `${index * stagger - duration * remainingTravel}s`;
|
|
1278
|
+
path.style.animationDuration = `${duration}s`;
|
|
1279
|
+
path.style.top = `${12 + nativeParticleSeed(index, 23) * 76}%`;
|
|
1280
|
+
const particle = document.createElement("span");
|
|
1281
|
+
particle.className = particleClass;
|
|
1282
|
+
particle.style.opacity = String(0.4 + nativeParticleSeed(index, 11) * 0.6);
|
|
1283
|
+
particle.style.transform = `translate(-50%, -50%) scale(${0.5 + nativeParticleSeed(index, 12) * 0.45})`;
|
|
1284
|
+
path.append(particle);
|
|
1285
|
+
return path;
|
|
1286
|
+
}));
|
|
1287
|
+
clip.append(particles);
|
|
1288
|
+
const tickRail = Array.from(track.children).find((element) => String(element.className).includes("_TickRail_"));
|
|
1289
|
+
track.insertBefore(clip, tickRail ?? null);
|
|
1290
|
+
if (entering) {
|
|
1291
|
+
// 首帧沿用关闭 Fast 时的星点,再渐显流动粒子。
|
|
1292
|
+
clip.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 240, easing: "ease-out" });
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function syncGenericFastVisual(shell, enabled, maxPowerSelection, animate = false) {
|
|
1297
|
+
const button = shell.querySelector("[data-codex-model-slider-fast]");
|
|
1298
|
+
const rail = shell.querySelector("[data-codex-model-slider-rail]");
|
|
1299
|
+
if (!button || !rail) return;
|
|
1300
|
+
button.setAttribute("aria-pressed", String(enabled));
|
|
1301
|
+
button.setAttribute("data-fast-mode-enabled", String(enabled));
|
|
1302
|
+
syncGenericMaximumVisual(
|
|
1303
|
+
rail,
|
|
1304
|
+
shell.querySelector("[data-codex-model-slider-effort-row] [data-maximum]"),
|
|
1305
|
+
button,
|
|
1306
|
+
maxPowerSelection,
|
|
1307
|
+
);
|
|
1308
|
+
button.setAttribute("aria-label", enabled
|
|
1309
|
+
? (locale === "zh-CN" ? "关闭 Fast 模式" : "Disable Fast mode")
|
|
1310
|
+
: (locale === "zh-CN" ? "开启 Fast 模式" : "Enable Fast mode"));
|
|
1311
|
+
const icon = createNativeFastIcon(enabled);
|
|
1312
|
+
const content = button.querySelector("[data-codex-model-slider-fast-content]");
|
|
1313
|
+
if (icon && content) content.replaceChildren(icon);
|
|
1314
|
+
rail.setAttribute("data-fast-mode", String(enabled));
|
|
1315
|
+
rail.setAttribute("data-fast-mode-dot-transition", animate ? (enabled ? "entering" : "exiting") : (enabled ? "active" : "inactive"));
|
|
1316
|
+
syncNativeFastParticles(rail, enabled, animate && enabled);
|
|
1317
|
+
setGenericSliderVisual(rail, Number(rail.getAttribute("aria-valuenow") ?? 0), Number(rail.getAttribute("aria-valuemax") ?? 0) + 1);
|
|
1318
|
+
if (animate && enabled) {
|
|
1319
|
+
shell.setAttribute("data-codex-model-slider-fast-transitioning", "");
|
|
1320
|
+
setTimeout(() => {
|
|
1321
|
+
if (!shell.isConnected) return;
|
|
1322
|
+
shell.removeAttribute("data-codex-model-slider-fast-transitioning");
|
|
1323
|
+
queueEnsure();
|
|
1324
|
+
}, 260);
|
|
1325
|
+
} else {
|
|
1326
|
+
shell.removeAttribute("data-codex-model-slider-fast-transitioning");
|
|
1327
|
+
}
|
|
1328
|
+
if (animate) {
|
|
1329
|
+
setTimeout(() => {
|
|
1330
|
+
if (rail.isConnected && rail.getAttribute("data-fast-mode") === String(enabled)) {
|
|
1331
|
+
rail.setAttribute("data-fast-mode-dot-transition", enabled ? "active" : "inactive");
|
|
1332
|
+
}
|
|
1333
|
+
}, enabled ? 1_200 : 350);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function cloneNativePowerSlider(parentMenu, count, fastEnabled) {
|
|
1338
|
+
const template = parentMenu.firstElementChild?.querySelector("[data-model-picker-power-slider]");
|
|
1339
|
+
if (!template) return null;
|
|
1340
|
+
const clone = template.cloneNode(true);
|
|
1341
|
+
clone.setAttribute("data-codex-model-slider-native-slider", "");
|
|
1342
|
+
clone.removeAttribute("data-keyboard-focused");
|
|
1343
|
+
const root = clone.firstElementChild;
|
|
1344
|
+
if (!root) return null;
|
|
1345
|
+
const nativeInput = clone.querySelector('[role="slider"]');
|
|
1346
|
+
nativeInput?.removeAttribute("role");
|
|
1347
|
+
nativeInput?.removeAttribute("tabindex");
|
|
1348
|
+
nativeInput?.removeAttribute("aria-valuemin");
|
|
1349
|
+
nativeInput?.removeAttribute("aria-valuemax");
|
|
1350
|
+
nativeInput?.removeAttribute("aria-valuenow");
|
|
1351
|
+
root.setAttribute("role", "slider");
|
|
1352
|
+
root.removeAttribute("aria-hidden");
|
|
1353
|
+
root.removeAttribute("data-disabled");
|
|
1354
|
+
root.removeAttribute("disabled");
|
|
1355
|
+
root.setAttribute("tabindex", "0");
|
|
1356
|
+
root.setAttribute("aria-valuemax", String(Math.max(0, count - 1)));
|
|
1357
|
+
root.setAttribute("data-fast-mode", String(fastEnabled));
|
|
1358
|
+
root.setAttribute("data-fast-mode-dot-transition", fastEnabled ? "active" : "inactive");
|
|
1359
|
+
root.setAttribute("data-max", "false");
|
|
1360
|
+
root.setAttribute("data-endpoint-labels-visible", "false");
|
|
1361
|
+
const range = Array.from(root.querySelectorAll("span")).find((element) => (
|
|
1362
|
+
String(element.className).includes("_Range_")
|
|
1363
|
+
));
|
|
1364
|
+
const thumbScale = Array.from(root.querySelectorAll("span")).find((element) => (
|
|
1365
|
+
String(element.className).includes("_ThumbScale_")
|
|
1366
|
+
));
|
|
1367
|
+
const tickRail = Array.from(root.querySelectorAll("div")).find((element) => (
|
|
1368
|
+
String(element.className).includes("_TickRail_")
|
|
1369
|
+
));
|
|
1370
|
+
const tickTemplate = tickRail?.querySelector("span");
|
|
1371
|
+
if (!range || !thumbScale || !tickRail || !tickTemplate) return null;
|
|
1372
|
+
range.setAttribute("data-codex-model-slider-native-range", "");
|
|
1373
|
+
thumbScale.setAttribute("data-codex-model-slider-native-thumb", "");
|
|
1374
|
+
tickRail.replaceChildren(...Array.from({ length: count }, (_, index) => {
|
|
1375
|
+
const tick = tickTemplate.cloneNode(true);
|
|
1376
|
+
tick.setAttribute("data-codex-model-slider-tick", "");
|
|
1377
|
+
const progress = count <= 1 ? 0 : (index / (count - 1)) * 100;
|
|
1378
|
+
const correction = 13 - (progress / 50) * 13;
|
|
1379
|
+
tick.style.left = `calc(${progress}% + ${correction}px)`;
|
|
1380
|
+
return tick;
|
|
1381
|
+
}));
|
|
1382
|
+
syncNativeFastParticles(root, fastEnabled);
|
|
1383
|
+
return clone;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
function syncClonedPowerEffects(nativeContainer, shell) {
|
|
1387
|
+
const source = nativeContainer.querySelector("[data-model-picker-power-slider]");
|
|
1388
|
+
const clone = shell.querySelector("[data-codex-model-slider-native-slider]");
|
|
1389
|
+
if (!source || !clone) return null;
|
|
1390
|
+
const sourceCanvas = source.querySelector("canvas");
|
|
1391
|
+
const cloneCanvas = clone.querySelector("canvas");
|
|
1392
|
+
let canvasTimer = null;
|
|
1393
|
+
let observer = null;
|
|
1394
|
+
const disconnect = () => {
|
|
1395
|
+
observer?.disconnect();
|
|
1396
|
+
if (canvasTimer != null) clearInterval(canvasTimer);
|
|
1397
|
+
};
|
|
1398
|
+
if (sourceCanvas && cloneCanvas) {
|
|
1399
|
+
// 原生 canvas 可能在首次克隆时尚未绘制;持续同步其高光纹理。
|
|
1400
|
+
const scratch = document.createElement("canvas");
|
|
1401
|
+
const scratchContext = scratch.getContext("2d");
|
|
1402
|
+
const cloneContext = cloneCanvas.getContext("2d");
|
|
1403
|
+
const copyCanvas = () => {
|
|
1404
|
+
if (!source.isConnected || !shell.isConnected) {
|
|
1405
|
+
disconnect();
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if (scratch.width !== sourceCanvas.width || scratch.height !== sourceCanvas.height) {
|
|
1409
|
+
scratch.width = sourceCanvas.width;
|
|
1410
|
+
scratch.height = sourceCanvas.height;
|
|
1411
|
+
cloneCanvas.width = sourceCanvas.width;
|
|
1412
|
+
cloneCanvas.height = sourceCanvas.height;
|
|
1413
|
+
}
|
|
1414
|
+
scratchContext.clearRect(0, 0, scratch.width, scratch.height);
|
|
1415
|
+
scratchContext.drawImage(sourceCanvas, 0, 0);
|
|
1416
|
+
const y = Math.floor(scratch.height / 2);
|
|
1417
|
+
const hasDrawing = [0.25, 0.5, 0.75].some((ratio) => (
|
|
1418
|
+
scratchContext.getImageData(Math.floor(scratch.width * ratio), y, 1, 1).data[3] > 0
|
|
1419
|
+
));
|
|
1420
|
+
if (!hasDrawing) return;
|
|
1421
|
+
cloneContext.clearRect(0, 0, cloneCanvas.width, cloneCanvas.height);
|
|
1422
|
+
cloneContext.drawImage(scratch, 0, 0);
|
|
1423
|
+
};
|
|
1424
|
+
copyCanvas();
|
|
1425
|
+
canvasTimer = setInterval(copyCanvas, 80);
|
|
1426
|
+
}
|
|
1427
|
+
const sourceEffects = source.querySelector('[class*="_MaxEffects_"]');
|
|
1428
|
+
const cloneEffects = clone.querySelector('[class*="_MaxEffects_"]');
|
|
1429
|
+
if (!sourceEffects || !cloneEffects) return { disconnect };
|
|
1430
|
+
const sync = () => {
|
|
1431
|
+
if (!source.isConnected || !shell.isConnected) {
|
|
1432
|
+
disconnect();
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
const sourceTransition = sourceEffects.querySelector('[class*="_ParticleTransition_"]');
|
|
1436
|
+
let cloneTransition = cloneEffects.querySelector('[class*="_ParticleTransition_"]');
|
|
1437
|
+
if (!sourceTransition) {
|
|
1438
|
+
if (clone.querySelector("[data-fast-mode]")?.getAttribute("data-fast-mode") !== "true") {
|
|
1439
|
+
cloneTransition?.remove();
|
|
1440
|
+
}
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
const sourceParticles = sourceTransition.querySelectorAll('[class*="_TrackParticle_"]');
|
|
1444
|
+
let cloneParticles = cloneTransition?.querySelectorAll('[class*="_TrackParticle_"]');
|
|
1445
|
+
if (!cloneTransition || sourceParticles.length !== cloneParticles.length) {
|
|
1446
|
+
cloneTransition?.remove();
|
|
1447
|
+
cloneTransition = sourceTransition.cloneNode(true);
|
|
1448
|
+
cloneEffects.append(cloneTransition);
|
|
1449
|
+
cloneParticles = cloneTransition.querySelectorAll('[class*="_TrackParticle_"]');
|
|
1450
|
+
}
|
|
1451
|
+
cloneTransition.setAttribute("style", sourceTransition.getAttribute("style") ?? "");
|
|
1452
|
+
sourceParticles.forEach((particle, index) => {
|
|
1453
|
+
const style = particle.getAttribute("style") ?? "";
|
|
1454
|
+
if (cloneParticles[index].getAttribute("style") !== style) {
|
|
1455
|
+
cloneParticles[index].setAttribute("style", style);
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
if (!matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
1459
|
+
cloneTransition.style.opacity = clone.querySelector("[data-fast-mode]")?.getAttribute("data-fast-mode") === "true" ? "0" : "1";
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
observer = new MutationObserver(sync);
|
|
1463
|
+
observer.observe(sourceEffects, { attributes: true, attributeFilter: ["style"], childList: true, subtree: true });
|
|
1464
|
+
sync();
|
|
1465
|
+
return { disconnect };
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function createGenericModelSlider(parentMenu, selector, identity, model, selectedEffort) {
|
|
1469
|
+
const efforts = model.supportedReasoningLevels.map((level) => level.effort);
|
|
1470
|
+
const selectedIndex = efforts.indexOf(selectedEffort);
|
|
1471
|
+
const selectedEffortVisible = selectedIndex >= 0;
|
|
1472
|
+
const classes = Object.fromEntries([
|
|
1473
|
+
"Menu",
|
|
1474
|
+
"ViewToggle",
|
|
1475
|
+
"ViewToggleContent",
|
|
1476
|
+
"ViewToggleIcon",
|
|
1477
|
+
"ViewToggleModelLabel",
|
|
1478
|
+
"ViewToggleEffortLabel",
|
|
1479
|
+
"ViewControls",
|
|
1480
|
+
"ViewPanel",
|
|
1481
|
+
"FastModeToggle",
|
|
1482
|
+
"FastModeToggleContent",
|
|
1483
|
+
"SimpleView",
|
|
1484
|
+
].map((tokenName) => [tokenName, nativeCssModuleClass(tokenName, parentMenu)]));
|
|
1485
|
+
if (Object.values(classes).some((name) => !name)) return null;
|
|
1486
|
+
const shell = document.createElement("div");
|
|
1487
|
+
shell.setAttribute("data-codex-model-slider-generic", `${identity.raw}:${selectedEffort}`);
|
|
1488
|
+
shell.className = classes.Menu;
|
|
1489
|
+
const panel = document.createElement("div");
|
|
1490
|
+
panel.className = classes.ViewPanel;
|
|
1491
|
+
shell.append(panel);
|
|
1492
|
+
shell.setAttribute("data-model-picker-view", "simple");
|
|
1493
|
+
shell.setAttribute("data-reduced-motion", String(matchMedia("(prefers-reduced-motion: reduce)").matches));
|
|
1494
|
+
shell.setAttribute("data-transitions-ready", "false");
|
|
1495
|
+
const nativeRow = nativeModelPickerRow(parentMenu);
|
|
1496
|
+
const controller = nativeComposerModelController();
|
|
1497
|
+
const fastOption = controller?.serviceTierOptions?.find((option) => option?.value === "priority") ?? null;
|
|
1498
|
+
const nativeModel = nativeCatalogModelFor(identity);
|
|
1499
|
+
const supportsFast = Boolean(fastOption && typeof controller.onSelectServiceTier === "function");
|
|
1500
|
+
shell.setAttribute("data-codex-model-slider-has-fast", String(supportsFast));
|
|
1501
|
+
const modelButton = document.createElement("div");
|
|
1502
|
+
modelButton.className = classes.ViewToggle;
|
|
1503
|
+
modelButton.setAttribute("role", "button");
|
|
1504
|
+
modelButton.setAttribute("tabindex", "0");
|
|
1505
|
+
modelButton.setAttribute("data-codex-model-slider-model-button", "");
|
|
1506
|
+
modelButton.setAttribute("aria-label", `Model ${modelDisplayLabel(identity, selector)}`);
|
|
1507
|
+
modelButton.setAttribute("aria-haspopup", "menu");
|
|
1508
|
+
modelButton.setAttribute("aria-expanded", "false");
|
|
1509
|
+
const buttonContent = document.createElement("span");
|
|
1510
|
+
buttonContent.className = classes.ViewToggleContent;
|
|
1511
|
+
// 上下两行独立计算宽度,长模型名不能撑开强度与箭头。
|
|
1512
|
+
buttonContent.style.display = "flex";
|
|
1513
|
+
buttonContent.style.flexDirection = "column";
|
|
1514
|
+
buttonContent.style.gap = "0";
|
|
1515
|
+
const effortRow = document.createElement("span");
|
|
1516
|
+
effortRow.setAttribute("data-codex-model-slider-effort-row", "");
|
|
1517
|
+
effortRow.style.display = "grid";
|
|
1518
|
+
effortRow.style.gridTemplateColumns = "calc(var(--mcx-spacing) * 4) auto calc(var(--mcx-spacing) * 4)";
|
|
1519
|
+
effortRow.style.alignItems = "center";
|
|
1520
|
+
const buttonLabel = document.createElement("span");
|
|
1521
|
+
buttonLabel.className = classes.ViewToggleModelLabel;
|
|
1522
|
+
buttonLabel.setAttribute("data-codex-model-slider-model-label", "");
|
|
1523
|
+
buttonLabel.textContent = modelDisplayLabel(identity, selector);
|
|
1524
|
+
const modelRow = document.createElement("span");
|
|
1525
|
+
modelRow.setAttribute("data-codex-model-slider-model-row", "");
|
|
1526
|
+
modelRow.style.display = "inline-flex";
|
|
1527
|
+
modelRow.style.alignItems = "center";
|
|
1528
|
+
modelRow.style.justifyContent = "center";
|
|
1529
|
+
modelRow.style.gap = "calc(var(--mcx-spacing) / 2)";
|
|
1530
|
+
modelRow.style.minWidth = "0";
|
|
1531
|
+
modelRow.style.maxWidth = "100%";
|
|
1532
|
+
modelRow.append(buttonLabel);
|
|
1533
|
+
const effortText = selectedEffortVisible ? document.createElement("span") : null;
|
|
1534
|
+
if (effortText) {
|
|
1535
|
+
effortText.className = classes.ViewToggleEffortLabel;
|
|
1536
|
+
// 原生标签会跨满整行;收回中列,使上下两行各自居中。
|
|
1537
|
+
effortText.style.gridColumn = "2";
|
|
1538
|
+
effortText.style.gridRow = "1";
|
|
1539
|
+
effortText.textContent = effortLabel(selectedEffort);
|
|
1540
|
+
effortText.setAttribute("data-accent", "true");
|
|
1541
|
+
effortText.setAttribute("data-effort-only", "false");
|
|
1542
|
+
effortText.setAttribute("data-maximum", String(selectedEffort === "ultra"));
|
|
1543
|
+
}
|
|
1544
|
+
// 0.152.1 起原生头行 aria-label 为 "Select model",nativeModelPickerRow
|
|
1545
|
+
// 不再命中;优先从 view-toggle 行取 chevron,旧结构走原路径兜底。
|
|
1546
|
+
const nativeChevron = (
|
|
1547
|
+
parentMenu?.querySelector("[data-model-picker-view-toggle] svg")
|
|
1548
|
+
?? nativeRow?.querySelector("svg")
|
|
1549
|
+
)?.cloneNode(true);
|
|
1550
|
+
if (nativeChevron) {
|
|
1551
|
+
nativeChevron.classList.add(classes.ViewToggleIcon);
|
|
1552
|
+
nativeChevron.style.gridArea = "auto";
|
|
1553
|
+
nativeChevron.style.flexShrink = "0";
|
|
1554
|
+
nativeChevron.setAttribute("aria-hidden", "true");
|
|
1555
|
+
modelRow.append(nativeChevron);
|
|
1556
|
+
}
|
|
1557
|
+
if (effortText) effortRow.append(effortText);
|
|
1558
|
+
buttonContent.append(effortRow, modelRow);
|
|
1559
|
+
modelButton.append(buttonContent);
|
|
1560
|
+
const openModels = () => openControllerModelMenu(parentMenu, modelButton, selector);
|
|
1561
|
+
modelButton.addEventListener("click", openModels);
|
|
1562
|
+
modelButton.addEventListener("keydown", (event) => {
|
|
1563
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1564
|
+
event.preventDefault();
|
|
1565
|
+
openModels();
|
|
1566
|
+
});
|
|
1567
|
+
|
|
1568
|
+
const viewControls = document.createElement("div");
|
|
1569
|
+
viewControls.className = classes.ViewControls;
|
|
1570
|
+
// 沿用原生显式模型布局:上方档位,下方模型,两侧放置能力控件。
|
|
1571
|
+
viewControls.setAttribute("data-explicit-model", "true");
|
|
1572
|
+
viewControls.setAttribute("data-ultra-warning-visible", "false");
|
|
1573
|
+
const fastEnabled = Boolean(
|
|
1574
|
+
supportsFast && (
|
|
1575
|
+
controller.selectedServiceTier?.id === (fastOption.tier?.id ?? "priority")
|
|
1576
|
+
|| controller.selectedServiceTier === fastOption.value
|
|
1577
|
+
),
|
|
1578
|
+
);
|
|
1579
|
+
if (supportsFast) {
|
|
1580
|
+
const fastButton = document.createElement("button");
|
|
1581
|
+
fastButton.type = "button";
|
|
1582
|
+
fastButton.className = classes.FastModeToggle;
|
|
1583
|
+
fastButton.setAttribute("data-codex-model-slider-fast", "");
|
|
1584
|
+
fastButton.setAttribute("aria-pressed", String(fastEnabled));
|
|
1585
|
+
fastButton.setAttribute("data-fast-mode-enabled", String(fastEnabled));
|
|
1586
|
+
fastButton.setAttribute("data-max-power-selection", String(selectedEffort === "ultra"));
|
|
1587
|
+
fastButton.setAttribute("data-visible", "true");
|
|
1588
|
+
fastButton.setAttribute("aria-label", fastEnabled
|
|
1589
|
+
? (locale === "zh-CN" ? "关闭 Fast 模式" : "Disable Fast mode")
|
|
1590
|
+
: (locale === "zh-CN" ? "开启 Fast 模式" : "Enable Fast mode"));
|
|
1591
|
+
const fastIcon = createNativeFastIcon(fastEnabled);
|
|
1592
|
+
const fastContent = document.createElement("span");
|
|
1593
|
+
fastContent.className = classes.FastModeToggleContent;
|
|
1594
|
+
fastContent.setAttribute("data-codex-model-slider-fast-content", "");
|
|
1595
|
+
if (fastIcon) fastContent.append(fastIcon);
|
|
1596
|
+
fastButton.append(fastContent);
|
|
1597
|
+
fastButton.addEventListener("click", () => {
|
|
1598
|
+
const nextEnabled = fastButton.getAttribute("aria-pressed") !== "true";
|
|
1599
|
+
const currentIndex = Number(shell.querySelector("[data-codex-model-slider-rail]")?.getAttribute("aria-valuenow"));
|
|
1600
|
+
syncGenericFastVisual(shell, nextEnabled, efforts[currentIndex] === "ultra", true);
|
|
1601
|
+
controller.onSelectServiceTier?.(nextEnabled ? fastOption.value : null);
|
|
1602
|
+
queueEnsure();
|
|
1603
|
+
});
|
|
1604
|
+
viewControls.append(modelButton, fastButton);
|
|
1605
|
+
} else {
|
|
1606
|
+
viewControls.append(modelButton);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
const nativeReset = parentMenu.firstElementChild?.querySelector('[class*="_ResetToDefault_"]');
|
|
1610
|
+
if (nativeReset) {
|
|
1611
|
+
const reset = nativeReset.cloneNode(true);
|
|
1612
|
+
reset.setAttribute("role", "button");
|
|
1613
|
+
reset.setAttribute("tabindex", "0");
|
|
1614
|
+
reset.setAttribute("data-codex-model-slider-reset", "");
|
|
1615
|
+
reset.removeAttribute("data-radix-collection-item");
|
|
1616
|
+
reset.addEventListener("click", () => {
|
|
1617
|
+
const currentReset = parentMenu.firstElementChild?.querySelector('[class*="_ResetToDefault_"]');
|
|
1618
|
+
invokeNativeMenuItemSelect(currentReset);
|
|
1619
|
+
queueEnsure();
|
|
1620
|
+
});
|
|
1621
|
+
reset.addEventListener("keydown", (event) => {
|
|
1622
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1623
|
+
event.preventDefault();
|
|
1624
|
+
reset.click();
|
|
1625
|
+
});
|
|
1626
|
+
viewControls.append(reset);
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
if (efforts.length < 2) {
|
|
1630
|
+
panel.append(viewControls);
|
|
1631
|
+
return shell;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
const nativeSlider = cloneNativePowerSlider(parentMenu, efforts.length, fastEnabled);
|
|
1635
|
+
const rail = nativeSlider?.querySelector('[role="slider"]') ?? document.createElement("div");
|
|
1636
|
+
rail.setAttribute("data-codex-model-slider-rail", "");
|
|
1637
|
+
if (!nativeSlider) rail.setAttribute("data-codex-model-slider-fallback", "");
|
|
1638
|
+
rail.setAttribute("role", "slider");
|
|
1639
|
+
rail.setAttribute("tabindex", "0");
|
|
1640
|
+
rail.setAttribute("aria-label", locale === "zh-CN" ? "推理强度" : "Reasoning effort");
|
|
1641
|
+
rail.setAttribute("aria-valuemin", "0");
|
|
1642
|
+
rail.setAttribute("aria-valuemax", String(efforts.length - 1));
|
|
1643
|
+
rail.setAttribute("aria-valuetext", selectedEffortVisible
|
|
1644
|
+
? effortLabel(selectedEffort)
|
|
1645
|
+
: (locale === "zh-CN" ? "当前档位不可用" : "Current effort unavailable"));
|
|
1646
|
+
rail.toggleAttribute("data-stale-reasoning-effort", !selectedEffortVisible);
|
|
1647
|
+
if (!selectedEffortVisible) {
|
|
1648
|
+
rail.setAttribute("aria-disabled", "true");
|
|
1649
|
+
rail.removeAttribute("tabindex");
|
|
1650
|
+
neutralizeStaleSliderVisual(rail);
|
|
1651
|
+
const staleView = document.createElement("div");
|
|
1652
|
+
staleView.className = classes.SimpleView;
|
|
1653
|
+
staleView.append(nativeSlider ?? rail);
|
|
1654
|
+
panel.append(viewControls, staleView);
|
|
1655
|
+
return shell;
|
|
1656
|
+
}
|
|
1657
|
+
if (!nativeSlider) {
|
|
1658
|
+
const track = document.createElement("div");
|
|
1659
|
+
track.setAttribute("data-codex-model-slider-track", "");
|
|
1660
|
+
const range = document.createElement("div");
|
|
1661
|
+
range.setAttribute("data-codex-model-slider-range", "");
|
|
1662
|
+
track.append(range);
|
|
1663
|
+
efforts.forEach((_effort, index) => {
|
|
1664
|
+
const tick = document.createElement("span");
|
|
1665
|
+
tick.setAttribute("data-codex-model-slider-tick", "");
|
|
1666
|
+
tick.style.left = `${efforts.length <= 1 ? 0 : (index / (efforts.length - 1)) * 100}%`;
|
|
1667
|
+
track.append(tick);
|
|
1668
|
+
});
|
|
1669
|
+
const thumb = document.createElement("span");
|
|
1670
|
+
thumb.setAttribute("data-codex-model-slider-thumb", "");
|
|
1671
|
+
rail.append(track, thumb);
|
|
1672
|
+
}
|
|
1673
|
+
setGenericSliderVisual(rail, selectedIndex, efforts.length);
|
|
1674
|
+
syncNativeFastParticles(rail, fastEnabled);
|
|
1675
|
+
syncGenericMaximumVisual(
|
|
1676
|
+
rail,
|
|
1677
|
+
effortText,
|
|
1678
|
+
shell.querySelector("[data-codex-model-slider-fast]"),
|
|
1679
|
+
selectedEffort === "ultra",
|
|
1680
|
+
);
|
|
1681
|
+
|
|
1682
|
+
let pendingIndex = selectedIndex;
|
|
1683
|
+
let dragging = false;
|
|
1684
|
+
let dragRect = null;
|
|
1685
|
+
let previewFrame = null;
|
|
1686
|
+
let pendingClientX = null;
|
|
1687
|
+
const indexAt = (clientX) => {
|
|
1688
|
+
const rect = dragRect ?? rail.getBoundingClientRect();
|
|
1689
|
+
const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / Math.max(1, rect.width)));
|
|
1690
|
+
return Math.round(ratio * (efforts.length - 1));
|
|
1691
|
+
};
|
|
1692
|
+
const previewIndex = (index) => {
|
|
1693
|
+
pendingIndex = index;
|
|
1694
|
+
const effort = efforts[index];
|
|
1695
|
+
setGenericSliderVisual(rail, index, efforts.length);
|
|
1696
|
+
rail.setAttribute("aria-valuetext", effortLabel(effort));
|
|
1697
|
+
if (effortText) {
|
|
1698
|
+
effortText.textContent = effortLabel(effort);
|
|
1699
|
+
}
|
|
1700
|
+
const fastButton = shell.querySelector("[data-codex-model-slider-fast]");
|
|
1701
|
+
syncGenericMaximumVisual(rail, effortText, fastButton, effort === "ultra");
|
|
1702
|
+
};
|
|
1703
|
+
const preview = (clientX) => previewIndex(indexAt(clientX));
|
|
1704
|
+
rail.addEventListener("pointerdown", (event) => {
|
|
1705
|
+
dragging = true;
|
|
1706
|
+
dragRect = rail.getBoundingClientRect();
|
|
1707
|
+
rail.setPointerCapture?.(event.pointerId);
|
|
1708
|
+
preview(event.clientX);
|
|
1709
|
+
});
|
|
1710
|
+
rail.addEventListener("pointermove", (event) => {
|
|
1711
|
+
if (!dragging) return;
|
|
1712
|
+
pendingClientX = event.clientX;
|
|
1713
|
+
if (previewFrame != null) return;
|
|
1714
|
+
previewFrame = requestAnimationFrame(() => {
|
|
1715
|
+
previewFrame = null;
|
|
1716
|
+
if (dragging && pendingClientX != null) preview(pendingClientX);
|
|
1717
|
+
});
|
|
1718
|
+
});
|
|
1719
|
+
rail.addEventListener("pointerup", (event) => {
|
|
1720
|
+
if (!dragging) return;
|
|
1721
|
+
dragging = false;
|
|
1722
|
+
if (previewFrame != null) cancelAnimationFrame(previewFrame);
|
|
1723
|
+
previewFrame = null;
|
|
1724
|
+
pendingClientX = null;
|
|
1725
|
+
preview(event.clientX);
|
|
1726
|
+
dragRect = null;
|
|
1727
|
+
selectNativeEffort(parentMenu, efforts[pendingIndex]);
|
|
1728
|
+
});
|
|
1729
|
+
rail.addEventListener("pointercancel", () => {
|
|
1730
|
+
dragging = false;
|
|
1731
|
+
if (previewFrame != null) cancelAnimationFrame(previewFrame);
|
|
1732
|
+
previewFrame = null;
|
|
1733
|
+
pendingClientX = null;
|
|
1734
|
+
dragRect = null;
|
|
1735
|
+
previewIndex(selectedIndex);
|
|
1736
|
+
});
|
|
1737
|
+
rail.addEventListener("keydown", (event) => {
|
|
1738
|
+
if (!new Set(["ArrowLeft", "ArrowRight", "Home", "End"]).has(event.key)) return;
|
|
1739
|
+
event.preventDefault();
|
|
1740
|
+
if (event.key === "Home") pendingIndex = 0;
|
|
1741
|
+
else if (event.key === "End") pendingIndex = efforts.length - 1;
|
|
1742
|
+
else pendingIndex = Math.min(
|
|
1743
|
+
efforts.length - 1,
|
|
1744
|
+
Math.max(0, pendingIndex + (event.key === "ArrowRight" ? 1 : -1)),
|
|
1745
|
+
);
|
|
1746
|
+
previewIndex(pendingIndex);
|
|
1747
|
+
selectNativeEffort(parentMenu, efforts[pendingIndex]);
|
|
1748
|
+
});
|
|
1749
|
+
const simpleView = document.createElement("div");
|
|
1750
|
+
simpleView.className = classes.SimpleView;
|
|
1751
|
+
simpleView.append(nativeSlider ?? rail);
|
|
1752
|
+
panel.append(viewControls, simpleView);
|
|
1753
|
+
return shell;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
function centerGenericModelPicker(parentMenu) {
|
|
1757
|
+
const trigger = nativeComposerTrigger();
|
|
1758
|
+
if (!trigger) return;
|
|
1759
|
+
const menuRect = parentMenu.getBoundingClientRect();
|
|
1760
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
1761
|
+
if (!(menuRect.width > 0 && triggerRect.width > 0)) return;
|
|
1762
|
+
const previousShift = Number.parseFloat(
|
|
1763
|
+
parentMenu.style.getPropertyValue("--codex-model-slider-menu-center-shift"),
|
|
1764
|
+
) || 0;
|
|
1765
|
+
const unshiftedCenter = menuRect.left + (menuRect.width / 2) - previousShift;
|
|
1766
|
+
const nextShift = triggerRect.left + (triggerRect.width / 2) - unshiftedCenter;
|
|
1767
|
+
parentMenu.style.setProperty("--codex-model-slider-menu-center-shift", `${nextShift}px`);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// ========================================================================
|
|
1771
|
+
// 通用 picker 增强与恢复 — 菜单/触发器增强、布局居中与恢复降级
|
|
1772
|
+
// ========================================================================
|
|
1773
|
+
|
|
1774
|
+
function enhanceGenericModelPicker(parentMenu, selector) {
|
|
1775
|
+
const identity = modelIdentity(nativeModelValue(parentMenu));
|
|
1776
|
+
const model = modelDefinitionFor(identity, selector);
|
|
1777
|
+
const displayModel = modelWithOpenCodexEfforts(identity, selector, model);
|
|
1778
|
+
const efforts = displayModel?.supportedReasoningLevels ?? [];
|
|
1779
|
+
if (!model) return false;
|
|
1780
|
+
// 控制器推导的任务内实际 effort 优先,避免子任务/主任务切换后触发器属性滞后;
|
|
1781
|
+
// 触发器属性仅在控制器无法解析时作为实时回退,静态目录默认值最后兜底。
|
|
1782
|
+
const nativeModel = nativeCatalogModelFor(identity);
|
|
1783
|
+
const selectedEffort = nativeModel?.currentReasoningLevel
|
|
1784
|
+
?? nativeComposerTrigger()
|
|
1785
|
+
?.getAttribute("data-selected-reasoning-effort")
|
|
1786
|
+
?? model.defaultReasoningLevel
|
|
1787
|
+
?? efforts[0]?.effort
|
|
1788
|
+
?? "none";
|
|
1789
|
+
const effortRank = (value) => {
|
|
1790
|
+
const order = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
|
|
1791
|
+
const index = order.indexOf(String(value ?? "").toLowerCase());
|
|
1792
|
+
return index < 0 ? -1 : index;
|
|
1793
|
+
};
|
|
1794
|
+
const visibleEffort = efforts.find((level) => level.effort === selectedEffort)?.effort
|
|
1795
|
+
?? efforts.filter((level) => effortRank(level.effort) <= effortRank(selectedEffort))
|
|
1796
|
+
.sort((left, right) => effortRank(right.effort) - effortRank(left.effort))[0]?.effort
|
|
1797
|
+
?? efforts[0]?.effort
|
|
1798
|
+
?? "none";
|
|
1799
|
+
const selectedEffortValue = visibleEffort;
|
|
1800
|
+
const selectedServiceTier = nativeComposerModelController()?.selectedServiceTier;
|
|
1801
|
+
const serviceTierKey = selectedServiceTier?.id ?? selectedServiceTier ?? "standard";
|
|
1802
|
+
const selectionMode = nativeComposerModelController()?.selectionMode ?? "";
|
|
1803
|
+
const structureKey = `${identity.raw}:${selectedEffortValue}:${selectionMode}:${efforts.map((level) => level.effort).join(",")}`;
|
|
1804
|
+
const key = `${structureKey}:${serviceTierKey}`;
|
|
1805
|
+
const existing = parentMenu.querySelector(":scope > [data-codex-model-slider-generic]");
|
|
1806
|
+
const nativeTemplateReady = Boolean(
|
|
1807
|
+
parentMenu.firstElementChild
|
|
1808
|
+
?.querySelector("[data-model-picker-power-slider] [class*='_TickRail_'] > span"),
|
|
1809
|
+
);
|
|
1810
|
+
const templateWaitCount = Number(parentMenu.getAttribute("data-codex-model-slider-template-wait") ?? 0);
|
|
1811
|
+
if (nativeModel?.slug.toLowerCase().startsWith("gpt-") && !nativeTemplateReady && templateWaitCount < 3) {
|
|
1812
|
+
parentMenu.setAttribute("data-codex-model-slider-template-wait", String(templateWaitCount + 1));
|
|
1813
|
+
parentMenu.style.visibility = "hidden";
|
|
1814
|
+
requestAnimationFrame(queueEnsure);
|
|
1815
|
+
return true;
|
|
1816
|
+
}
|
|
1817
|
+
parentMenu.removeAttribute("data-codex-model-slider-template-wait");
|
|
1818
|
+
parentMenu.style.removeProperty("visibility");
|
|
1819
|
+
const canUpgradeFallback = Boolean(
|
|
1820
|
+
existing?.querySelector("[data-codex-model-slider-fallback]") && nativeTemplateReady,
|
|
1821
|
+
);
|
|
1822
|
+
if (existing?.getAttribute("data-codex-model-slider-structure-key") === structureKey
|
|
1823
|
+
&& existing.hasAttribute("data-codex-model-slider-fast-transitioning") && !canUpgradeFallback) {
|
|
1824
|
+
centerGenericModelPicker(parentMenu);
|
|
1825
|
+
return true;
|
|
1826
|
+
}
|
|
1827
|
+
const currentFast = existing?.querySelector("[data-codex-model-slider-rail]")?.getAttribute("data-fast-mode");
|
|
1828
|
+
const nativeFast = parentMenu.firstElementChild
|
|
1829
|
+
?.querySelector("[data-model-picker-power-slider] [data-fast-mode]")?.getAttribute("data-fast-mode");
|
|
1830
|
+
if (existing?.getAttribute("data-codex-model-slider-structure-key") === structureKey
|
|
1831
|
+
&& currentFast != null && currentFast === nativeFast && !canUpgradeFallback) {
|
|
1832
|
+
existing.setAttribute("data-codex-model-slider-generic", key);
|
|
1833
|
+
centerGenericModelPicker(parentMenu);
|
|
1834
|
+
return true;
|
|
1835
|
+
}
|
|
1836
|
+
if (existing?.getAttribute("data-codex-model-slider-generic") === key && !canUpgradeFallback) {
|
|
1837
|
+
centerGenericModelPicker(parentMenu);
|
|
1838
|
+
return true;
|
|
1839
|
+
}
|
|
1840
|
+
const nativeContainer = parentMenu.firstElementChild;
|
|
1841
|
+
if (!nativeContainer) return false;
|
|
1842
|
+
const shell = createGenericModelSlider(
|
|
1843
|
+
parentMenu,
|
|
1844
|
+
selector,
|
|
1845
|
+
identity,
|
|
1846
|
+
displayModel,
|
|
1847
|
+
selectedEffortValue,
|
|
1848
|
+
);
|
|
1849
|
+
if (!shell) {
|
|
1850
|
+
restoreGenericModelPicker(nativeContainer);
|
|
1851
|
+
return false;
|
|
1852
|
+
}
|
|
1853
|
+
modelSelectorGenericContainers.get(nativeContainer)?.effectMirror?.disconnect();
|
|
1854
|
+
existing?.remove();
|
|
1855
|
+
if (!modelSelectorGenericContainers.has(nativeContainer)) {
|
|
1856
|
+
modelSelectorGenericContainers.set(nativeContainer, {
|
|
1857
|
+
position: nativeContainer.style.position,
|
|
1858
|
+
inset: nativeContainer.style.inset,
|
|
1859
|
+
opacity: nativeContainer.style.opacity,
|
|
1860
|
+
pointerEvents: nativeContainer.style.pointerEvents,
|
|
1861
|
+
ariaHidden: nativeContainer.getAttribute("aria-hidden"),
|
|
1862
|
+
inert: nativeContainer.inert,
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
parentMenu.setAttribute("data-codex-model-slider-generic-menu", "");
|
|
1866
|
+
nativeContainer.style.position = "absolute";
|
|
1867
|
+
nativeContainer.style.inset = "0";
|
|
1868
|
+
nativeContainer.style.opacity = "0";
|
|
1869
|
+
nativeContainer.style.pointerEvents = "none";
|
|
1870
|
+
nativeContainer.setAttribute("aria-hidden", "true");
|
|
1871
|
+
nativeContainer.inert = true;
|
|
1872
|
+
shell.setAttribute("data-codex-model-slider-generic", key);
|
|
1873
|
+
shell.setAttribute("data-codex-model-slider-structure-key", structureKey);
|
|
1874
|
+
parentMenu.append(shell);
|
|
1875
|
+
modelSelectorGenericContainers.get(nativeContainer).effectMirror = syncClonedPowerEffects(nativeContainer, shell);
|
|
1876
|
+
centerGenericModelPicker(parentMenu);
|
|
1877
|
+
return true;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
function enhanceNativeModelPicker(parentMenu, selector) {
|
|
1881
|
+
if (enhanceGenericModelPicker(parentMenu, selector)) return;
|
|
1882
|
+
const toggle = parentMenu.querySelector("[data-model-picker-view-toggle]");
|
|
1883
|
+
if (toggle && typeof nativeComposerModelController()?.onSelectModel === "function") {
|
|
1884
|
+
// 私有 DOM 无法满足 MCX shell 契约时,保留原生菜单作为降级路径。
|
|
1885
|
+
const raw = nativeModelValue(parentMenu);
|
|
1886
|
+
const textNode = firstVisibleTextNode(toggle);
|
|
1887
|
+
if (raw && textNode) {
|
|
1888
|
+
if (!modelSelectorOriginalToggleLabels.has(textNode)) {
|
|
1889
|
+
modelSelectorOriginalToggleLabels.set(textNode, textNode.textContent);
|
|
1890
|
+
}
|
|
1891
|
+
const label = compactModelLabel(raw);
|
|
1892
|
+
if (textNode.textContent !== label) textNode.textContent = label;
|
|
1893
|
+
}
|
|
1894
|
+
if (!toggle.hasAttribute("data-codex-model-slider-toggle")) {
|
|
1895
|
+
const originalAriaExpanded = toggle.getAttribute("aria-expanded");
|
|
1896
|
+
toggle.setAttribute("data-codex-model-slider-toggle", "");
|
|
1897
|
+
const openModels = (event) => {
|
|
1898
|
+
event.preventDefault();
|
|
1899
|
+
event.stopPropagation();
|
|
1900
|
+
event.stopImmediatePropagation();
|
|
1901
|
+
openControllerModelMenu(parentMenu, toggle, selector);
|
|
1902
|
+
};
|
|
1903
|
+
const onKeyDown = (event) => {
|
|
1904
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1905
|
+
openModels(event);
|
|
1906
|
+
};
|
|
1907
|
+
toggle.addEventListener("click", openModels);
|
|
1908
|
+
toggle.addEventListener("keydown", onKeyDown);
|
|
1909
|
+
modelSelectorNativeToggleHandlers.set(toggle, { openModels, onKeyDown, originalAriaExpanded });
|
|
1910
|
+
}
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// 原生 ModelText 重建前的空窗期:立即插回 MCX 标签,避免关盒后芯片
|
|
1916
|
+
// 长时间失去模型名。
|
|
1917
|
+
function restoreTriggerLabelWithoutNative(trigger, selector) {
|
|
1918
|
+
if (trigger.getAttribute("data-state") !== "closed") return;
|
|
1919
|
+
if (trigger.querySelector("[data-codex-model-slider-trigger-label]")) return;
|
|
1920
|
+
const raw = trigger.getAttribute("data-codex-model-slider-trigger-raw");
|
|
1921
|
+
if (!raw) return;
|
|
1922
|
+
const anchor = trigger.querySelector('[class*="_ModelPickerTriggerLabel_"]')
|
|
1923
|
+
?? trigger.querySelector('[class*="_ModelPickerTriggerContent_"]');
|
|
1924
|
+
if (!anchor) return;
|
|
1925
|
+
const displayLabel = document.createElement("span");
|
|
1926
|
+
displayLabel.setAttribute("data-codex-model-slider-trigger-label", "");
|
|
1927
|
+
displayLabel.textContent = modelDisplayLabel(modelIdentity(raw), selector);
|
|
1928
|
+
anchor.prepend(displayLabel);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
function isComposerModelPlaceholder(value) {
|
|
1932
|
+
return /^(select\s+(model|effort)|选择\s*(模型|思考强度))$/i.test(String(value ?? "").trim());
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
function enhanceComposerModelTrigger(selector) {
|
|
1936
|
+
const trigger = nativeComposerTrigger();
|
|
1937
|
+
if (!trigger) return;
|
|
1938
|
+
if (modelSelectorObservedTrigger !== trigger) {
|
|
1939
|
+
modelSelectorTriggerObserver?.disconnect();
|
|
1940
|
+
modelSelectorObservedTrigger = trigger;
|
|
1941
|
+
modelSelectorTriggerObserver = new MutationObserver(queueEnsure);
|
|
1942
|
+
modelSelectorTriggerObserver.observe(trigger, { childList: true, subtree: true, characterData: true });
|
|
1943
|
+
}
|
|
1944
|
+
const nativeLabels = Array.from(trigger.querySelectorAll(
|
|
1945
|
+
'[class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label])',
|
|
1946
|
+
));
|
|
1947
|
+
if (!nativeLabels.length) {
|
|
1948
|
+
trigger.setAttribute("data-codex-model-slider-trigger", "");
|
|
1949
|
+
trigger.toggleAttribute(
|
|
1950
|
+
"data-codex-model-slider-trigger-placeholder",
|
|
1951
|
+
isComposerModelPlaceholder(trigger.innerText),
|
|
1952
|
+
);
|
|
1953
|
+
restoreTriggerLabelWithoutNative(trigger, selector);
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
const visibleValue = String(nativeLabels.at(-1)?.textContent ?? "").trim();
|
|
1957
|
+
const previousRaw = trigger.getAttribute("data-codex-model-slider-trigger-raw") ?? "";
|
|
1958
|
+
const previousLabel = previousRaw
|
|
1959
|
+
? modelDisplayLabel(modelIdentity(previousRaw), selector)
|
|
1960
|
+
: "";
|
|
1961
|
+
const raw = !previousRaw || visibleValue !== previousLabel ? visibleValue : previousRaw;
|
|
1962
|
+
if (!raw) return;
|
|
1963
|
+
const label = modelDisplayLabel(modelIdentity(raw), selector);
|
|
1964
|
+
trigger.setAttribute("data-codex-model-slider-trigger", "");
|
|
1965
|
+
trigger.setAttribute("data-codex-model-slider-trigger-raw", raw);
|
|
1966
|
+
// 区分占位态(Select model / Select effort)与真实模型态:占位态沿用
|
|
1967
|
+
// 原生宽度契约,真实模型态让芯片 HUG 完整名称,避免第三方长名被截断。
|
|
1968
|
+
const placeholder = isComposerModelPlaceholder(label);
|
|
1969
|
+
trigger.toggleAttribute("data-codex-model-slider-trigger-placeholder", placeholder);
|
|
1970
|
+
for (const nativeLabel of nativeLabels) {
|
|
1971
|
+
let displayLabel = nativeLabel.nextElementSibling;
|
|
1972
|
+
if (!displayLabel?.hasAttribute("data-codex-model-slider-trigger-label")) {
|
|
1973
|
+
// 空窗期回插等场景会在别处留下现成的 MCX 标签,优先移动复用,
|
|
1974
|
+
// 避免恢复后出现两个可见名称把芯片撑宽。
|
|
1975
|
+
displayLabel = trigger.querySelector("[data-codex-model-slider-trigger-label]");
|
|
1976
|
+
if (!displayLabel) {
|
|
1977
|
+
displayLabel = document.createElement("span");
|
|
1978
|
+
displayLabel.className = nativeLabel.className;
|
|
1979
|
+
displayLabel.setAttribute("data-codex-model-slider-trigger-label", "");
|
|
1980
|
+
}
|
|
1981
|
+
nativeLabel.after(displayLabel);
|
|
1982
|
+
}
|
|
1983
|
+
if (displayLabel.textContent !== label) displayLabel.textContent = label;
|
|
1984
|
+
}
|
|
1985
|
+
// 原生开合的临时 min-width 钉定与弹层水平居中都基于对 Measurement
|
|
1986
|
+
// 探针的测量;把探针文本同步为 MCX 显示标签,原生机制即可测得
|
|
1987
|
+
// 真实显示宽度。保留 React 持有的子节点,避免切模型时卸载失败。
|
|
1988
|
+
const measurement = trigger.querySelector('[class*="_ModelPickerTriggerMeasurement_"]');
|
|
1989
|
+
const measurementText = measurement && firstVisibleTextNode(measurement);
|
|
1990
|
+
if (measurementText && measurementText.nodeValue !== label) measurementText.nodeValue = label;
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
function ensureModelSelector() {
|
|
1994
|
+
const selector = modelSelectorFeature?.modelSelector;
|
|
1995
|
+
if (!selector) return;
|
|
1996
|
+
if (!nativeComposerModelController()) {
|
|
1997
|
+
if (modelSelectorStyle.isConnected) restoreModelSelectorEnhancements();
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
pruneModelSubmenuPositionObservers();
|
|
2001
|
+
pruneNativeModelToggleHandlers();
|
|
2002
|
+
pruneGenericModelPickerContainers();
|
|
2003
|
+
document.documentElement.setAttribute("data-codex-model-slider-catalog-ready", "");
|
|
2004
|
+
if (!modelSelectorStyle.isConnected) document.head?.append(modelSelectorStyle);
|
|
2005
|
+
enhanceComposerModelTrigger(selector);
|
|
2006
|
+
const parentMenu = nativeModelPickerMenu();
|
|
2007
|
+
if (modelSelectorControllerMenu && modelSelectorControllerMenu.parentMenu !== parentMenu) {
|
|
2008
|
+
closeControllerModelMenu();
|
|
2009
|
+
}
|
|
2010
|
+
if (!parentMenu) {
|
|
2011
|
+
if (
|
|
2012
|
+
modelSelectorPendingReturn
|
|
2013
|
+
&& !visibleBlockingDialog()
|
|
2014
|
+
&& nativeModelPickerSubmenus().length === 0
|
|
2015
|
+
) {
|
|
2016
|
+
clearTimeout(modelSelectorReturnTimer);
|
|
2017
|
+
modelSelectorReturnTimer = null;
|
|
2018
|
+
returnToNativeModelSlider();
|
|
2019
|
+
}
|
|
2020
|
+
} else {
|
|
2021
|
+
enhanceNativeModelPicker(parentMenu, selector);
|
|
2022
|
+
const submenu = nativeModelSubmenu(parentMenu);
|
|
2023
|
+
if (submenu) enhanceNativeModelSubmenu(submenu, selector);
|
|
2024
|
+
}
|
|
2025
|
+
try {
|
|
2026
|
+
// 图标工作与菜单增强解耦:放在最后并隔离异常,任何失败都不影响标签与菜单。
|
|
2027
|
+
captureNativeFastIcon();
|
|
2028
|
+
} catch (error) {
|
|
2029
|
+
console.warn("[codex-personal] fast icon capture failed", error);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
function restoreModelSelectorEnhancements() {
|
|
2034
|
+
closeControllerModelMenu();
|
|
2035
|
+
clearTimeout(modelSelectorReturnTimer);
|
|
2036
|
+
clearTimeout(modelSelectorEffortTransactionTimer);
|
|
2037
|
+
modelSelectorTriggerObserver?.disconnect();
|
|
2038
|
+
for (const record of modelSelectorPositionObservers.values()) {
|
|
2039
|
+
record.observer.disconnect();
|
|
2040
|
+
record.resizeObserver.disconnect();
|
|
2041
|
+
}
|
|
2042
|
+
modelSelectorPositionObservers.clear();
|
|
2043
|
+
for (const frame of modelSelectorMenuSizeFrames.values()) cancelAnimationFrame(frame);
|
|
2044
|
+
modelSelectorMenuSizeFrames.clear();
|
|
2045
|
+
modelSelectorTriggerObserver = null;
|
|
2046
|
+
modelSelectorObservedTrigger = null;
|
|
2047
|
+
modelSelectorReturnTimer = null;
|
|
2048
|
+
modelSelectorEffortTransactionTimer = null;
|
|
2049
|
+
modelSelectorPendingOutsideDismiss = false;
|
|
2050
|
+
modelSelectorSuppressOutsidePointerSequence = false;
|
|
2051
|
+
modelSelectorPendingReturn = false;
|
|
2052
|
+
document.documentElement.removeAttribute("data-codex-model-slider-selecting-effort");
|
|
2053
|
+
for (const item of document.querySelectorAll("[data-codex-model-slider-item]")) {
|
|
2054
|
+
item.style.removeProperty("display");
|
|
2055
|
+
for (const name of [
|
|
2056
|
+
"data-codex-model-slider-raw",
|
|
2057
|
+
"data-codex-model-slider-item",
|
|
2058
|
+
"data-codex-model-slider-order",
|
|
2059
|
+
"data-codex-model-slider-identity-source",
|
|
2060
|
+
"data-codex-model-slider-return-listener",
|
|
2061
|
+
]) item.removeAttribute(name);
|
|
2062
|
+
}
|
|
2063
|
+
for (const [node, value] of modelSelectorOriginalToggleLabels) {
|
|
2064
|
+
if (node.isConnected) node.textContent = value;
|
|
2065
|
+
}
|
|
2066
|
+
for (const [toggle, handlers] of modelSelectorNativeToggleHandlers) {
|
|
2067
|
+
toggle.removeEventListener("click", handlers.openModels);
|
|
2068
|
+
toggle.removeEventListener("keydown", handlers.onKeyDown);
|
|
2069
|
+
toggle.removeAttribute("data-codex-model-slider-toggle");
|
|
2070
|
+
if (handlers.originalAriaExpanded == null) toggle.removeAttribute("aria-expanded");
|
|
2071
|
+
else toggle.setAttribute("aria-expanded", handlers.originalAriaExpanded);
|
|
2072
|
+
}
|
|
2073
|
+
for (const container of modelSelectorGenericContainers.keys()) {
|
|
2074
|
+
restoreGenericModelPicker(container);
|
|
2075
|
+
}
|
|
2076
|
+
document.querySelectorAll("[data-codex-model-slider-divider]")
|
|
2077
|
+
.forEach((element) => element.remove());
|
|
2078
|
+
document.querySelectorAll("[data-codex-model-slider-generic]").forEach((element) => element.remove());
|
|
2079
|
+
document.querySelectorAll("[data-codex-model-slider-generic-menu]")
|
|
2080
|
+
.forEach((element) => element.removeAttribute("data-codex-model-slider-generic-menu"));
|
|
2081
|
+
document.querySelectorAll("[data-codex-model-slider-menu]")
|
|
2082
|
+
.forEach((element) => element.removeAttribute("data-codex-model-slider-menu"));
|
|
2083
|
+
document.querySelectorAll("[data-codex-model-slider-trigger]").forEach((element) => {
|
|
2084
|
+
element.removeAttribute("data-codex-model-slider-trigger");
|
|
2085
|
+
element.removeAttribute("data-codex-model-slider-trigger-raw");
|
|
2086
|
+
});
|
|
2087
|
+
document.querySelectorAll("[data-codex-model-slider-trigger-label]").forEach((element) => element.remove());
|
|
2088
|
+
document.documentElement.removeAttribute("data-codex-model-slider-catalog-ready");
|
|
2089
|
+
modelSelectorStyle.remove();
|
|
2090
|
+
modelSelectorOriginalToggleLabels.clear();
|
|
2091
|
+
modelSelectorNativeToggleHandlers.clear();
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
document.addEventListener("click", event => {
|
|
2095
|
+
if (modelSelectorControllerMenu
|
|
2096
|
+
&& !modelSelectorControllerMenu.menu.contains(event.target)
|
|
2097
|
+
&& !modelSelectorControllerMenu.button.contains(event.target)) closeControllerModelMenu();
|
|
2098
|
+
}, {capture:true, signal});
|
|
2099
|
+
if (modelSelectorFeature) {
|
|
2100
|
+
// 启动阶段就解析 Fast 图标:先读持久化的原生克隆,再从 app bundle 静态提取兜底,
|
|
2101
|
+
// 避免首次会话必须等用户点开弹窗后 DOM 里才出现图标。
|
|
2102
|
+
const persistedIcons = loadPersistedNativeFastIcons(modelSelectorFastIconStorageKey);
|
|
2103
|
+
if (persistedIcons) {
|
|
2104
|
+
modelSelectorNativeFastIcons = persistedIcons;
|
|
2105
|
+
}
|
|
2106
|
+
void loadNativeFastIconFromBundle();
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
return { ensure: ensureModelSelector, dispose: restoreModelSelectorEnhancements };
|
|
2110
|
+
}
|