minecodex 0.1.0
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/LICENSE +21 -0
- package/README.md +56 -0
- package/apps/dashboard/app.js +387 -0
- package/apps/dashboard/favicon.svg +18 -0
- package/apps/dashboard/index.html +70 -0
- package/apps/dashboard/styles.css +297 -0
- package/features/images/README.md +106 -0
- package/features/images/codex-feature.json +24 -0
- package/features/images/src/http-server.mjs +302 -0
- package/features/images/src/image-library.mjs +582 -0
- package/features/images/src/main.mjs +52 -0
- package/features/images/src/prompt-index.mjs +182 -0
- package/features/images/src/save-as.mjs +40 -0
- package/features/images/src/thread-metadata.mjs +93 -0
- package/features/images/web/app.js +1045 -0
- package/features/images/web/index.html +162 -0
- package/features/images/web/styles.css +994 -0
- package/features/model-slider/README.md +30 -0
- package/features/model-slider/THIRD_PARTY_NOTICES.md +24 -0
- package/features/model-slider/assets/providers/anthropic.svg +1 -0
- package/features/model-slider/assets/providers/deepseek.svg +1 -0
- package/features/model-slider/assets/providers/gemini.svg +1 -0
- package/features/model-slider/assets/providers/grok.svg +1 -0
- package/features/model-slider/assets/providers/hunyuan.svg +1 -0
- package/features/model-slider/assets/providers/kimi.svg +1 -0
- package/features/model-slider/assets/providers/minimax.svg +1 -0
- package/features/model-slider/assets/providers/openai.svg +1 -0
- package/features/model-slider/assets/providers/qwen.svg +1 -0
- package/features/model-slider/assets/providers/xiaomimimo.svg +1 -0
- package/features/model-slider/assets/providers/zhipu.svg +1 -0
- package/features/model-slider/assets/ui/sparkles.svg +18 -0
- package/features/model-slider/assets/ui/star-off.svg +17 -0
- package/features/model-slider/assets/ui/star.svg +15 -0
- package/features/model-slider/codex-feature.json +56 -0
- package/features/model-slider/vendor/cc-switch-LICENSE +21 -0
- package/features/model-slider/vendor/lucide-LICENSE +35 -0
- package/features/notes/README.md +106 -0
- package/features/notes/assets/icons/circle.svg +15 -0
- package/features/notes/assets/icons/ellipsis.svg +17 -0
- package/features/notes/assets/icons/external-link.svg +17 -0
- package/features/notes/assets/icons/list-todo.svg +19 -0
- package/features/notes/assets/icons/message-square-quote.svg +17 -0
- package/features/notes/assets/icons/paperclip.svg +15 -0
- package/features/notes/assets/icons/star-off.svg +17 -0
- package/features/notes/assets/icons/star.svg +15 -0
- package/features/notes/assets/icons/sticky-note.svg +16 -0
- package/features/notes/assets/icons/trash-2.svg +19 -0
- package/features/notes/codex-feature.json +90 -0
- package/features/notes/src/http-server.mjs +541 -0
- package/features/notes/src/main.mjs +54 -0
- package/features/notes/src/product.mjs +771 -0
- package/features/notes/web/app.js +1495 -0
- package/features/notes/web/codex-artifact-document.svg +1 -0
- package/features/notes/web/codex-attachment.svg +1 -0
- package/features/notes/web/codex-build.svg +1 -0
- package/features/notes/web/codex-code.svg +1 -0
- package/features/notes/web/codex-cplusplus.svg +1 -0
- package/features/notes/web/codex-css.svg +1 -0
- package/features/notes/web/codex-document.svg +1 -0
- package/features/notes/web/codex-file.svg +1 -0
- package/features/notes/web/codex-folder.svg +1 -0
- package/features/notes/web/codex-hashes.svg +1 -0
- package/features/notes/web/codex-html.svg +1 -0
- package/features/notes/web/codex-image.svg +1 -0
- package/features/notes/web/codex-java.svg +1 -0
- package/features/notes/web/codex-javascript.svg +1 -0
- package/features/notes/web/codex-json.svg +1 -0
- package/features/notes/web/codex-notebook.svg +1 -0
- package/features/notes/web/codex-pdf.svg +1 -0
- package/features/notes/web/codex-pencil.svg +3 -0
- package/features/notes/web/codex-php.svg +1 -0
- package/features/notes/web/codex-plus.svg +3 -0
- package/features/notes/web/codex-presentation.svg +1 -0
- package/features/notes/web/codex-python.svg +1 -0
- package/features/notes/web/codex-react.svg +1 -0
- package/features/notes/web/codex-rust.svg +1 -0
- package/features/notes/web/codex-shell.svg +1 -0
- package/features/notes/web/codex-skill.svg +1 -0
- package/features/notes/web/codex-spreadsheet.svg +1 -0
- package/features/notes/web/codex-task.svg +3 -0
- package/features/notes/web/codex-terminal.svg +1 -0
- package/features/notes/web/codex-toml.svg +1 -0
- package/features/notes/web/codex-typescript.svg +1 -0
- package/features/notes/web/codex-view-all.svg +3 -0
- package/features/notes/web/index.html +15 -0
- package/features/notes/web/styles.css +1155 -0
- package/package.json +67 -0
- package/packages/cli/bin/mcx.mjs +7 -0
- package/packages/cli/src/browser-exchange.mjs +31 -0
- package/packages/cli/src/commands.mjs +389 -0
- package/packages/cli/src/config.mjs +101 -0
- package/packages/cli/src/control-server.mjs +371 -0
- package/packages/cli/src/npm-adapter.mjs +40 -0
- package/packages/cli/src/paths.mjs +33 -0
- package/packages/cli/src/platform.mjs +368 -0
- package/packages/cli/src/runtime-manager.mjs +338 -0
- package/packages/runtime-host/README.md +171 -0
- package/packages/runtime-host/src/codex-runtime.mjs +4311 -0
- package/packages/runtime-host/src/feature-registry.mjs +827 -0
- package/packages/runtime-host/src/main.mjs +98 -0
- package/packages/runtime-host/src/pid-file.mjs +23 -0
|
@@ -0,0 +1,4311 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { mkdir, stat } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const RUNTIME_VERSION = 99;
|
|
7
|
+
const HOST_BINDING_NAME = "__codexPersonalHostAction";
|
|
8
|
+
|
|
9
|
+
export const RESPONSIVE_SUMMARY_LAYOUT = Object.freeze({
|
|
10
|
+
contentBaseWidth: 736,
|
|
11
|
+
overlayClearance: 180,
|
|
12
|
+
gutterClearance: 400,
|
|
13
|
+
panelWidth: 300,
|
|
14
|
+
panelInset: 16,
|
|
15
|
+
verticalInset: 12,
|
|
16
|
+
popoverOffset: 8,
|
|
17
|
+
popoverBottomInset: 6,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export function responsiveSummaryDisplayMode(mainContentTargetWidth, layout = RESPONSIVE_SUMMARY_LAYOUT) {
|
|
21
|
+
const clearance = (Number(mainContentTargetWidth) - layout.contentBaseWidth) / 2;
|
|
22
|
+
if (clearance < layout.overlayClearance) return "overlay";
|
|
23
|
+
if (clearance < layout.gutterClearance) return "shift";
|
|
24
|
+
return "gutter";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function responsiveSummaryVisibleSurface({ displayMode, isPinned, isPopoverOpen }) {
|
|
28
|
+
if (displayMode !== "overlay" && isPinned) return "inline";
|
|
29
|
+
if (displayMode === "overlay" && isPopoverOpen) return "popover";
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function responsiveSummaryContentShift({ displayMode, isPinned }, layout = RESPONSIVE_SUMMARY_LAYOUT) {
|
|
34
|
+
return displayMode === "shift" && isPinned
|
|
35
|
+
? -(layout.panelWidth + layout.panelInset) / 2
|
|
36
|
+
: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createInjectionSource(features, {
|
|
40
|
+
bindingName = HOST_BINDING_NAME,
|
|
41
|
+
bindingToken = "test-binding-token",
|
|
42
|
+
runtimeSessionId = randomBytes(16).toString("hex"),
|
|
43
|
+
} = {}) {
|
|
44
|
+
function install(config) {
|
|
45
|
+
if (window.top !== window) return;
|
|
46
|
+
const currentRuntime = window.__codexPersonalRuntime;
|
|
47
|
+
if (currentRuntime?.version === config.version && currentRuntime?.sessionId === config.sessionId) {
|
|
48
|
+
currentRuntime.ensure();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
currentRuntime?.dispose?.();
|
|
52
|
+
|
|
53
|
+
const lifetime = new AbortController();
|
|
54
|
+
|
|
55
|
+
const entryMarker = "data-codex-personal-feature";
|
|
56
|
+
const pageSurfaceMarker = "data-codex-personal-surface";
|
|
57
|
+
const pinnedSurfaceMarker = "data-codex-personal-pinned-summary";
|
|
58
|
+
const modalSurfaceMarker = "data-codex-personal-modal";
|
|
59
|
+
const promptPreviewMarker = "data-codex-personal-prompt-preview";
|
|
60
|
+
const threadShiftStyle = document.createElement("style");
|
|
61
|
+
threadShiftStyle.setAttribute("data-codex-personal-summary-shift-style", "");
|
|
62
|
+
threadShiftStyle.textContent = "[data-codex-personal-summary-shift] { transform: none !important; --thread-wide-block-inline-shift: 0px !important; }";
|
|
63
|
+
const nativeLabels = {
|
|
64
|
+
sites: ["sites", "站点"],
|
|
65
|
+
scheduled: ["scheduled", "计划任务"],
|
|
66
|
+
plugins: ["plugins", "插件"],
|
|
67
|
+
};
|
|
68
|
+
const themeTokenNames = [
|
|
69
|
+
"--codex-base-accent",
|
|
70
|
+
"--codex-base-ink",
|
|
71
|
+
"--codex-base-surface",
|
|
72
|
+
"--codex-corner-radius-scale",
|
|
73
|
+
"--codex-corner-shape",
|
|
74
|
+
"--spacing",
|
|
75
|
+
"--radius-3xl-base",
|
|
76
|
+
"--elevation-prominent",
|
|
77
|
+
"--elevation-stroke",
|
|
78
|
+
"--color-token-main-surface-primary",
|
|
79
|
+
"--color-token-dropdown-background",
|
|
80
|
+
"--color-token-foreground",
|
|
81
|
+
"--color-token-text-secondary",
|
|
82
|
+
"--color-token-text-tertiary",
|
|
83
|
+
"--color-token-border",
|
|
84
|
+
"--color-token-focus-border",
|
|
85
|
+
"--color-token-conversation-summary-trailing",
|
|
86
|
+
"--color-token-bg-primary",
|
|
87
|
+
"--color-token-bg-secondary",
|
|
88
|
+
"--color-token-bg-tertiary",
|
|
89
|
+
"--color-background-elevated-primary-opaque",
|
|
90
|
+
"--color-background-panel",
|
|
91
|
+
"--color-background-control",
|
|
92
|
+
"--color-background-primary-solid",
|
|
93
|
+
"--color-text-primary-solid",
|
|
94
|
+
"--color-background-button-primary",
|
|
95
|
+
"--color-background-button-primary-hover",
|
|
96
|
+
"--color-background-button-secondary",
|
|
97
|
+
"--color-background-button-secondary-hover",
|
|
98
|
+
"--color-background-button-tertiary-hover",
|
|
99
|
+
"--color-text-button-tertiary",
|
|
100
|
+
"--color-token-button-background",
|
|
101
|
+
"--color-token-button-foreground",
|
|
102
|
+
"--color-text-foreground",
|
|
103
|
+
"--color-text-foreground-secondary",
|
|
104
|
+
"--color-text-foreground-tertiary",
|
|
105
|
+
"--color-text-on-accent",
|
|
106
|
+
"--color-text-button-primary",
|
|
107
|
+
"--color-text-accent",
|
|
108
|
+
"--color-token-text-link-foreground",
|
|
109
|
+
"--color-border",
|
|
110
|
+
"--color-border-heavy",
|
|
111
|
+
"--color-border-focus",
|
|
112
|
+
"--color-token-scrollbar-slider-background",
|
|
113
|
+
"--color-token-scrollbar-slider-hover-background",
|
|
114
|
+
"--color-token-scrollbar-slider-active-background",
|
|
115
|
+
"--color-icon-primary",
|
|
116
|
+
"--color-icon-secondary",
|
|
117
|
+
"--color-icon-tertiary",
|
|
118
|
+
"--color-token-list-hover-background",
|
|
119
|
+
"--color-token-input-background",
|
|
120
|
+
"--color-token-input-border",
|
|
121
|
+
"--color-token-input-foreground",
|
|
122
|
+
"--color-token-input-placeholder-foreground",
|
|
123
|
+
"--font-sans-default",
|
|
124
|
+
"--font-weight-normal",
|
|
125
|
+
"--vscode-font-weight",
|
|
126
|
+
"--default-mono-font-family",
|
|
127
|
+
"--text-heading-lg",
|
|
128
|
+
"--text-heading-md",
|
|
129
|
+
"--text-base",
|
|
130
|
+
"--text-base--line-height",
|
|
131
|
+
"--text-sm",
|
|
132
|
+
"--text-sm--line-height",
|
|
133
|
+
"--text-xs",
|
|
134
|
+
"--spacing-token-button-composer",
|
|
135
|
+
"--radius-sm",
|
|
136
|
+
"--radius-md",
|
|
137
|
+
"--radius-lg",
|
|
138
|
+
"--radius-xl",
|
|
139
|
+
"--radius-2xl",
|
|
140
|
+
"--shadow-md",
|
|
141
|
+
"--shadow-lg",
|
|
142
|
+
"--shadow-xl",
|
|
143
|
+
];
|
|
144
|
+
function normalizedLocale(value) {
|
|
145
|
+
return String(value ?? "").toLowerCase().startsWith("zh") ? "zh-CN" : "en";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const locale = normalizedLocale(
|
|
149
|
+
document.documentElement.lang || navigator.languages?.[0] || navigator.language,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
function localizedText(fallback, locales = {}) {
|
|
153
|
+
return locales[locale] ?? locales.en ?? fallback;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const features = config.features.map((feature) => ({
|
|
157
|
+
...feature,
|
|
158
|
+
label: localizedText(feature.label, feature.labels),
|
|
159
|
+
entry: feature.entry ? {
|
|
160
|
+
...feature.entry,
|
|
161
|
+
ariaLabel: localizedText(feature.entry.ariaLabel, feature.entry.ariaLabels),
|
|
162
|
+
} : { kind: "sidebar" },
|
|
163
|
+
pinnedSummary: feature.pinnedSummary ? {
|
|
164
|
+
...feature.pinnedSummary,
|
|
165
|
+
label: localizedText(feature.pinnedSummary.label, feature.pinnedSummary.labels),
|
|
166
|
+
overlayLabel: localizedText(feature.pinnedSummary.overlayLabel, feature.pinnedSummary.overlayLabels),
|
|
167
|
+
pinnedLabel: localizedText(feature.pinnedSummary.pinnedLabel, feature.pinnedSummary.pinnedLabels),
|
|
168
|
+
} : null,
|
|
169
|
+
detailTabs: (feature.detailTabs ?? []).map((detail) => ({
|
|
170
|
+
...detail,
|
|
171
|
+
label: localizedText(detail.label, detail.labels),
|
|
172
|
+
})),
|
|
173
|
+
modelSelector: feature.modelSelector ? {
|
|
174
|
+
...feature.modelSelector,
|
|
175
|
+
strings: Object.fromEntries(Object.entries(feature.modelSelector.strings ?? {}).map(
|
|
176
|
+
([key, definition]) => [key, localizedText(definition.value, definition.locales)],
|
|
177
|
+
)),
|
|
178
|
+
} : null,
|
|
179
|
+
hostActions: new Set(feature.hostActions ?? []),
|
|
180
|
+
}));
|
|
181
|
+
const featureById = new Map(features.map((feature) => [feature.id, feature]));
|
|
182
|
+
const sidebarFeatures = features.filter((feature) => feature.entry?.kind === "sidebar");
|
|
183
|
+
const toolbarFeatures = features.filter((feature) => feature.entry?.kind === "conversation-toolbar");
|
|
184
|
+
const modelSelectorFeature = features.find(
|
|
185
|
+
(feature) => feature.entry?.kind === "composer-model-selector",
|
|
186
|
+
) ?? null;
|
|
187
|
+
const pageSurfaces = new Map();
|
|
188
|
+
const pinnedSurfaces = new Map();
|
|
189
|
+
const surfaceRecords = new Map();
|
|
190
|
+
const openTabs = new Map();
|
|
191
|
+
const nativeDetailComponents = new Map();
|
|
192
|
+
const nativeDetailSurfaceKeys = new Map();
|
|
193
|
+
const nativeOpenTabSessions = new Set();
|
|
194
|
+
const summaryStates = new Map();
|
|
195
|
+
let activePageFeatureId = null;
|
|
196
|
+
let activePinnedFeatureId = null;
|
|
197
|
+
let activeTabKey = null;
|
|
198
|
+
let tabHost = null;
|
|
199
|
+
let ensureQueued = false;
|
|
200
|
+
let ensureFrame = null;
|
|
201
|
+
let ensureTimer = null;
|
|
202
|
+
let themeQueued = false;
|
|
203
|
+
let suppressedSelections = [];
|
|
204
|
+
let savedComposerRange = null;
|
|
205
|
+
let savedComposerThread = null;
|
|
206
|
+
let currentThread = null;
|
|
207
|
+
let currentSummaryDisplayMode = null;
|
|
208
|
+
let shiftedThreadContent = null;
|
|
209
|
+
let shiftedThreadOriginal = null;
|
|
210
|
+
let mainContentObserver = null;
|
|
211
|
+
const domObservers = [];
|
|
212
|
+
const pendingHostActions = new Map();
|
|
213
|
+
let activeModalRecordKey = null;
|
|
214
|
+
let activePromptPreview = null;
|
|
215
|
+
let promptPreviewDismissTimer = null;
|
|
216
|
+
let nativeTabCapabilityPromise = null;
|
|
217
|
+
let nativeTabCapability = null;
|
|
218
|
+
let nativeTabCapabilityError = null;
|
|
219
|
+
const modelSelectorOriginalItems = new Map();
|
|
220
|
+
const modelSelectorOriginalToggleLabels = new Map();
|
|
221
|
+
const modelSelectorGenericContainers = new Map();
|
|
222
|
+
const modelSelectorPositionObservers = new Map();
|
|
223
|
+
const modelSelectorMenuSizeFrames = new Map();
|
|
224
|
+
let modelSelectorReturnTimer = null;
|
|
225
|
+
let modelSelectorPendingReturn = false;
|
|
226
|
+
let modelSelectorEffortTransactionTimer = null;
|
|
227
|
+
let modelSelectorPendingOutsideDismiss = false;
|
|
228
|
+
let modelSelectorSuppressOutsidePointerSequence = false;
|
|
229
|
+
let modelSelectorTriggerObserver = null;
|
|
230
|
+
let modelSelectorObservedTrigger = null;
|
|
231
|
+
let modelSelectorNativeFastIcons = null;
|
|
232
|
+
const modelSelectorStyle = document.createElement("style");
|
|
233
|
+
modelSelectorStyle.setAttribute("data-codex-model-slider-style", "");
|
|
234
|
+
modelSelectorStyle.textContent = `
|
|
235
|
+
[data-codex-model-slider-menu] { width: 264px !important; min-width: 264px; overflow-x: hidden; }
|
|
236
|
+
[data-codex-model-slider-menu][data-codex-model-slider-overflow] {
|
|
237
|
+
max-height: var(--codex-model-slider-menu-max-height) !important;
|
|
238
|
+
overflow-y: auto;
|
|
239
|
+
}
|
|
240
|
+
[data-codex-model-slider-trigger] [class*="_ModelPickerTriggerModelLabel_"] {
|
|
241
|
+
max-width: none; flex-shrink: 0; overflow: visible; text-overflow: clip;
|
|
242
|
+
}
|
|
243
|
+
[data-codex-model-slider-trigger] [class*="_ModelPickerTriggerModelText_"] {
|
|
244
|
+
overflow: visible; text-overflow: clip;
|
|
245
|
+
}
|
|
246
|
+
html[data-codex-model-slider-catalog-ready]
|
|
247
|
+
[data-codex-intelligence-trigger]
|
|
248
|
+
[class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label]) {
|
|
249
|
+
display: none !important;
|
|
250
|
+
}
|
|
251
|
+
[data-codex-model-slider-trigger-label] {
|
|
252
|
+
display: inline; overflow: visible; text-overflow: clip; white-space: nowrap;
|
|
253
|
+
}
|
|
254
|
+
html[data-codex-model-slider-selecting-effort] [role="menu"][data-state="open"]:not(:has([data-reasoning-slider])) {
|
|
255
|
+
visibility: hidden !important; opacity: 0 !important; animation: none !important; pointer-events: none !important;
|
|
256
|
+
}
|
|
257
|
+
[data-codex-model-slider-row] { display: flex; width: 100%; min-width: 0; align-items: center; gap: 10px; }
|
|
258
|
+
[data-codex-model-slider-brand] { display: grid; width: 18px; height: 18px; flex: 0 0 18px; place-items: center; }
|
|
259
|
+
[data-codex-model-slider-brand] svg { width: 16px; height: 16px; display: block; }
|
|
260
|
+
[data-codex-model-slider-copy] { display: flex; min-width: 0; flex: 1; flex-direction: column; gap: 1px; }
|
|
261
|
+
[data-codex-model-slider-name] { overflow: hidden; color: var(--color-text-foreground); text-overflow: ellipsis; white-space: nowrap; }
|
|
262
|
+
[data-codex-model-slider-provider] { overflow: hidden; color: var(--color-text-foreground-tertiary); font-size: 11px; line-height: 14px; text-overflow: ellipsis; white-space: nowrap; }
|
|
263
|
+
[data-codex-model-slider-item] { position: relative; }
|
|
264
|
+
[data-codex-model-slider-item][data-codex-model-slider-selected] { background: var(--color-token-list-hover-background); }
|
|
265
|
+
[data-codex-model-slider-star] {
|
|
266
|
+
display: grid; width: 24px; height: 24px; flex: 0 0 24px; place-items: center;
|
|
267
|
+
border: 0; border-radius: var(--radius-md, 6px); padding: 0; background: transparent;
|
|
268
|
+
color: var(--color-text-foreground-tertiary); cursor: pointer; opacity: 0; pointer-events: none;
|
|
269
|
+
}
|
|
270
|
+
[data-codex-model-slider-item]:hover [data-codex-model-slider-star],
|
|
271
|
+
[data-codex-model-slider-item]:focus-within [data-codex-model-slider-star] { opacity: 1; pointer-events: auto; }
|
|
272
|
+
[data-codex-model-slider-star]:hover, [data-codex-model-slider-star]:focus-visible {
|
|
273
|
+
background: var(--color-token-list-hover-background); color: var(--color-text-foreground); outline: none;
|
|
274
|
+
}
|
|
275
|
+
[data-codex-model-slider-star]::after {
|
|
276
|
+
content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 40px; z-index: 1;
|
|
277
|
+
}
|
|
278
|
+
[data-codex-model-slider-star] svg { position: relative; z-index: 2; width: 16px; height: 16px; pointer-events: none; }
|
|
279
|
+
[data-codex-model-slider-divider] { height: 0.5px; margin: 4px 8px; background: var(--color-token-border, var(--color-border)); }
|
|
280
|
+
[data-codex-model-slider-generic-menu] { overflow-x: hidden; }
|
|
281
|
+
[data-codex-model-slider-generic] { box-sizing: border-box; width: 100%; }
|
|
282
|
+
[data-codex-model-slider-fast] { margin-left: auto; border: 0; background: transparent; cursor: pointer; }
|
|
283
|
+
[data-codex-model-slider-fast]:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 1px; }
|
|
284
|
+
[data-codex-model-slider-rail][data-codex-model-slider-fallback] { position: relative; height: 38px; margin: 1px 8px 0; outline: none; cursor: pointer; }
|
|
285
|
+
[data-codex-model-slider-track] { position: absolute; top: 12px; right: 0; left: 0; height: 16px; border-radius: 999px; background: var(--color-background-control); }
|
|
286
|
+
[data-codex-model-slider-range] { position: absolute; top: 0; bottom: 0; left: 0; width: var(--codex-model-slider-progress); border-radius: inherit; background: var(--color-text-accent); }
|
|
287
|
+
[data-codex-model-slider-fallback] [data-codex-model-slider-tick] {
|
|
288
|
+
position: absolute; top: 16px; width: 6px; height: 6px; transform: translate(-50%, -50%); border: 0; border-radius: 50%;
|
|
289
|
+
padding: 0; background: var(--color-text-foreground-tertiary); pointer-events: none;
|
|
290
|
+
}
|
|
291
|
+
[data-codex-model-slider-fallback] [data-codex-model-slider-tick][data-selected="true"] { background: var(--color-text-on-accent); }
|
|
292
|
+
[data-codex-model-slider-thumb] {
|
|
293
|
+
position: absolute; top: 7px; left: var(--codex-model-slider-progress); width: 26px; height: 26px;
|
|
294
|
+
transform: translateX(-50%); border: 0.5px solid var(--color-token-border, var(--color-border)); border-radius: 50%;
|
|
295
|
+
background: var(--color-background-elevated-primary-opaque, var(--color-background-panel)); box-shadow: var(--shadow-md);
|
|
296
|
+
pointer-events: none;
|
|
297
|
+
}
|
|
298
|
+
[data-codex-model-slider-fallback]:focus-visible [data-codex-model-slider-thumb] { box-shadow: 0 0 0 2px var(--color-border-focus); }
|
|
299
|
+
@media (prefers-reduced-motion: reduce) {
|
|
300
|
+
[data-codex-model-slider-star] { transition: none; }
|
|
301
|
+
}
|
|
302
|
+
`;
|
|
303
|
+
|
|
304
|
+
if (modelSelectorFeature) {
|
|
305
|
+
document.documentElement.setAttribute("data-codex-model-slider-catalog-ready", "");
|
|
306
|
+
document.documentElement.append(modelSelectorStyle);
|
|
307
|
+
void loadNativeFastIcon();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function findEntry(featureId) {
|
|
311
|
+
return Array.from(document.querySelectorAll(`[${entryMarker}]`)).find(
|
|
312
|
+
(entry) => entry.getAttribute(entryMarker) === featureId,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function findNativeEntry(key) {
|
|
317
|
+
const labels = nativeLabels[key] ?? [String(key).toLowerCase()];
|
|
318
|
+
return Array.from(document.querySelectorAll("a, button")).find((element) => (
|
|
319
|
+
labels.includes(element.textContent?.trim().toLowerCase())
|
|
320
|
+
));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function findReferenceEntry() {
|
|
324
|
+
return findNativeEntry("plugins");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function isVisibleToolbarControl(element) {
|
|
328
|
+
if (!(element instanceof HTMLElement)) return false;
|
|
329
|
+
const style = getComputedStyle(element);
|
|
330
|
+
const rect = element.getBoundingClientRect();
|
|
331
|
+
return style.display !== "none"
|
|
332
|
+
&& style.visibility !== "hidden"
|
|
333
|
+
&& rect.width > 0
|
|
334
|
+
&& rect.height > 0;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function nativeSummaryButton() {
|
|
338
|
+
return Array.from(document.querySelectorAll("button")).find((button) => (
|
|
339
|
+
(
|
|
340
|
+
button.getAttribute("aria-label") === "Toggle summary"
|
|
341
|
+
|| button.getAttribute("aria-label") === "Toggle pinned summary"
|
|
342
|
+
)
|
|
343
|
+
&& isVisibleToolbarControl(button)
|
|
344
|
+
)) ?? null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function nativeBottomPanelButton() {
|
|
348
|
+
return Array.from(document.querySelectorAll("button")).find((button) => {
|
|
349
|
+
if (button.getAttribute("aria-label") !== "Toggle bottom panel") return false;
|
|
350
|
+
return isVisibleToolbarControl(button);
|
|
351
|
+
}) ?? null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function toolbarControlRoot(button) {
|
|
355
|
+
return button?.parentElement?.parentElement ?? button?.parentElement ?? button ?? null;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function nativeSummaryToolbarGroup(nativeButton) {
|
|
359
|
+
return toolbarControlRoot(nativeButton)?.parentElement ?? null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function conversationToolbarHost(nativeButton) {
|
|
363
|
+
const header = nativeButton?.closest("header");
|
|
364
|
+
if (!header) return null;
|
|
365
|
+
return Array.from(header.children).find((child) => {
|
|
366
|
+
if (!(child instanceof HTMLElement) || !isVisibleToolbarControl(child)) return false;
|
|
367
|
+
return Number.parseFloat(getComputedStyle(child).flexGrow) > 0;
|
|
368
|
+
}) ?? null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function ensureFallbackSummaryToolbarGroup(nativeButton) {
|
|
372
|
+
const toolbarHost = conversationToolbarHost(nativeButton);
|
|
373
|
+
if (!toolbarHost) return null;
|
|
374
|
+
let group = toolbarHost.querySelector(":scope > [data-codex-personal-summary-toolbar-group]");
|
|
375
|
+
if (!group) {
|
|
376
|
+
group = document.createElement("div");
|
|
377
|
+
group.setAttribute("data-codex-personal-summary-toolbar-group", "fallback");
|
|
378
|
+
group.className = "ms-auto flex shrink-0 items-center gap-1.5";
|
|
379
|
+
toolbarHost.append(group);
|
|
380
|
+
}
|
|
381
|
+
return group;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function isNativeSummaryButton(element) {
|
|
385
|
+
if (!(element instanceof Element)) return false;
|
|
386
|
+
const button = element.closest("button");
|
|
387
|
+
return button?.getAttribute("aria-label") === "Toggle summary"
|
|
388
|
+
|| button?.getAttribute("aria-label") === "Toggle pinned summary";
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function mainContentViewport() {
|
|
392
|
+
return document.querySelector("[data-app-shell-main-content-layout]")
|
|
393
|
+
?? document.querySelector("main[data-app-shell-main-surface]")
|
|
394
|
+
?? document.querySelector("main");
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function visibleRightWorkspacePanels() {
|
|
398
|
+
return Array.from(document.querySelectorAll('[data-app-shell-tab-panel-controller="right"]'))
|
|
399
|
+
.filter((panel) => {
|
|
400
|
+
if (!(panel instanceof HTMLElement) || panel.hidden) return false;
|
|
401
|
+
const style = getComputedStyle(panel);
|
|
402
|
+
const rect = panel.getBoundingClientRect();
|
|
403
|
+
return style.display !== "none" && style.visibility !== "hidden" && rect.width > 0;
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function mainContentTargetGeometry() {
|
|
408
|
+
const viewport = mainContentViewport();
|
|
409
|
+
const rect = viewport?.getBoundingClientRect() ?? {
|
|
410
|
+
left: Math.max(0, sidebarRight()),
|
|
411
|
+
right: innerWidth,
|
|
412
|
+
top: 0,
|
|
413
|
+
bottom: innerHeight,
|
|
414
|
+
width: Math.max(0, innerWidth - sidebarRight()),
|
|
415
|
+
};
|
|
416
|
+
let right = rect.right;
|
|
417
|
+
for (const panel of visibleRightWorkspacePanels()) {
|
|
418
|
+
const panelRect = panel.getBoundingClientRect();
|
|
419
|
+
if (panelRect.left > rect.left && panelRect.left < right) right = panelRect.left;
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
viewport,
|
|
423
|
+
left: rect.left,
|
|
424
|
+
right,
|
|
425
|
+
top: rect.top,
|
|
426
|
+
bottom: rect.bottom,
|
|
427
|
+
width: Math.max(0, right - rect.left),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function summaryDisplayMode() {
|
|
432
|
+
const layout = config.summaryLayout;
|
|
433
|
+
const clearance = (mainContentTargetGeometry().width - layout.contentBaseWidth) / 2;
|
|
434
|
+
if (clearance < layout.overlayClearance) return "overlay";
|
|
435
|
+
if (clearance < layout.gutterClearance) return "shift";
|
|
436
|
+
return "gutter";
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function summaryState(featureId) {
|
|
440
|
+
let state = summaryStates.get(featureId);
|
|
441
|
+
if (!state) {
|
|
442
|
+
state = { isPinned: false, isPopoverOpen: false };
|
|
443
|
+
summaryStates.set(featureId, state);
|
|
444
|
+
}
|
|
445
|
+
return state;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function visibleSummarySurface(featureId, displayMode = summaryDisplayMode()) {
|
|
449
|
+
const state = summaryState(featureId);
|
|
450
|
+
if (displayMode !== "overlay" && state.isPinned) return "inline";
|
|
451
|
+
if (displayMode === "overlay" && state.isPopoverOpen) return "popover";
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function summaryButtonLabel(feature, displayMode = summaryDisplayMode()) {
|
|
456
|
+
const definition = feature.pinnedSummary ?? {};
|
|
457
|
+
return displayMode === "overlay"
|
|
458
|
+
? definition.overlayLabel ?? (locale === "zh-CN" ? "切换笔记" : "Toggle notes")
|
|
459
|
+
: definition.pinnedLabel
|
|
460
|
+
?? feature.entry.ariaLabel
|
|
461
|
+
?? (locale === "zh-CN" ? "切换固定笔记" : "Toggle pinned notes");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function reducedMotion() {
|
|
465
|
+
return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function createMarkupIcon(iconDefinition, className, size = 16) {
|
|
469
|
+
if (!iconDefinition?.markup) return null;
|
|
470
|
+
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
471
|
+
icon.setAttribute("width", String(size));
|
|
472
|
+
icon.setAttribute("height", String(size));
|
|
473
|
+
icon.setAttribute("viewBox", iconDefinition.viewBox ?? "0 0 24 24");
|
|
474
|
+
icon.setAttribute("fill", "none");
|
|
475
|
+
icon.setAttribute("stroke", "currentColor");
|
|
476
|
+
icon.setAttribute("stroke-width", "1.5");
|
|
477
|
+
icon.setAttribute("stroke-linecap", "round");
|
|
478
|
+
icon.setAttribute("stroke-linejoin", "round");
|
|
479
|
+
icon.setAttribute("aria-hidden", "true");
|
|
480
|
+
if (className) icon.setAttribute("class", className);
|
|
481
|
+
icon.innerHTML = iconDefinition.markup;
|
|
482
|
+
return icon;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function createFeatureIcon(feature, className, size = 16) {
|
|
486
|
+
return createMarkupIcon(feature.icon, className, size);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function createModelSelectorIcon(definition, { brand = false } = {}) {
|
|
490
|
+
const icon = createMarkupIcon(definition, "", 16);
|
|
491
|
+
if (!icon) return null;
|
|
492
|
+
if (brand) {
|
|
493
|
+
icon.setAttribute("fill", "currentColor");
|
|
494
|
+
icon.removeAttribute("stroke");
|
|
495
|
+
icon.removeAttribute("stroke-width");
|
|
496
|
+
icon.removeAttribute("stroke-linecap");
|
|
497
|
+
icon.removeAttribute("stroke-linejoin");
|
|
498
|
+
}
|
|
499
|
+
return icon;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async function loadNativeFastIcon() {
|
|
503
|
+
const sourceLink = document.querySelector('link[href*="/assets/app-initial-"][href$=".js"]');
|
|
504
|
+
if (!sourceLink?.href) return;
|
|
505
|
+
try {
|
|
506
|
+
const source = await fetch(sourceLink.href).then((response) => response.text());
|
|
507
|
+
const active = source.match(/d:`(M11\.9125 21\.4125[^`]+)`,fill:`currentColor`/);
|
|
508
|
+
const inactive = source.match(/d:`(M7\.38 16\.2207[^`]+)`,fill:`currentColor`/);
|
|
509
|
+
if (!active?.[1] || !inactive?.[1]) return;
|
|
510
|
+
modelSelectorNativeFastIcons = {
|
|
511
|
+
active: { viewBox: "0 0 24 24", markup: `<path d="${active[1]}" fill="currentColor" />` },
|
|
512
|
+
inactive: {
|
|
513
|
+
viewBox: "0 0 20 20",
|
|
514
|
+
markup: `<g transform="translate(2.43 1.609)"><path d="${inactive[1]}" fill="currentColor" /></g>`,
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
document.querySelectorAll("[data-codex-model-slider-fast]").forEach((button) => {
|
|
518
|
+
const icon = createNativeFastIcon(button.getAttribute("aria-pressed") === "true");
|
|
519
|
+
const content = button.querySelector("[data-codex-model-slider-fast-content]");
|
|
520
|
+
if (icon && content) content.replaceChildren(icon);
|
|
521
|
+
});
|
|
522
|
+
queueEnsure();
|
|
523
|
+
} catch {
|
|
524
|
+
// Leave the native control untouched when this private Codex asset moves.
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function createNativeFastIcon(active) {
|
|
529
|
+
const definition = modelSelectorNativeFastIcons?.[active ? "active" : "inactive"];
|
|
530
|
+
const icon = createModelSelectorIcon(definition, { brand: true });
|
|
531
|
+
if (icon) icon.classList.add("_FastModeIcon_1ryrb_423");
|
|
532
|
+
return icon;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function modelIdentity(rawValue) {
|
|
536
|
+
const raw = String(rawValue ?? "").trim();
|
|
537
|
+
const slash = raw.indexOf("/");
|
|
538
|
+
return {
|
|
539
|
+
raw,
|
|
540
|
+
provider: slash > 0 ? raw.slice(0, slash).trim() : "",
|
|
541
|
+
backend: slash > 0 ? raw.slice(slash + 1).trim() : raw,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function formatIdentifier(value) {
|
|
546
|
+
const raw = String(value ?? "").trim();
|
|
547
|
+
if (!raw) return "";
|
|
548
|
+
if (/\s/.test(raw) && !raw.includes("/") && !raw.includes("_") && !raw.includes("-")) return raw;
|
|
549
|
+
const canonical = new Map([
|
|
550
|
+
["ai", "AI"], ["api", "API"], ["chatglm", "ChatGLM"], ["claude", "Claude"],
|
|
551
|
+
["codex", "Codex"], ["deepseek", "DeepSeek"], ["gemini", "Gemini"], ["gemma", "Gemma"],
|
|
552
|
+
["glm", "GLM"], ["gpt", "GPT"], ["grok", "Grok"], ["kimi", "Kimi"],
|
|
553
|
+
["minimax", "MiniMax"], ["mimo", "MiMo"], ["moonshot", "Moonshot"], ["qwen", "Qwen"],
|
|
554
|
+
["qwq", "QwQ"], ["xai", "xAI"], ["xiaomi", "Xiaomi"], ["zhipu", "Zhipu"],
|
|
555
|
+
]);
|
|
556
|
+
return raw
|
|
557
|
+
.replace(/[_-]+/g, " ")
|
|
558
|
+
.split(/\s+/)
|
|
559
|
+
.filter(Boolean)
|
|
560
|
+
.map((token) => {
|
|
561
|
+
const lower = token.toLowerCase();
|
|
562
|
+
if (canonical.has(lower)) return canonical.get(lower);
|
|
563
|
+
if (/^v\d/i.test(token)) return `V${token.slice(1)}`;
|
|
564
|
+
if (/^o\d/i.test(token)) return token.toUpperCase();
|
|
565
|
+
return token.charAt(0).toUpperCase() + token.slice(1);
|
|
566
|
+
})
|
|
567
|
+
.join(" ");
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function providerLabel(provider, selector) {
|
|
571
|
+
if (!provider) return "";
|
|
572
|
+
return selector.providerAliases?.[provider.toLowerCase()] ?? formatIdentifier(provider);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function modelDisplayLabel(identity, selector) {
|
|
576
|
+
const model = catalogModelFor(identity, selector) ?? nativeCatalogModelFor(identity);
|
|
577
|
+
const source = model?.displayName || model?.slug || identity.backend;
|
|
578
|
+
return formatIdentifier(modelIdentity(source).backend);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function brandForModel(identity, selector) {
|
|
582
|
+
const haystack = `${identity.backend} ${identity.raw}`.toLowerCase();
|
|
583
|
+
return selector.brands?.find((brand) => (
|
|
584
|
+
brand.keywords.some((keyword) => haystack.includes(keyword))
|
|
585
|
+
)) ?? null;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function comparableModelLabel(value) {
|
|
589
|
+
return formatIdentifier(modelIdentity(value).backend)
|
|
590
|
+
.toLowerCase()
|
|
591
|
+
.replace(/^gpt\s+/, "")
|
|
592
|
+
.trim();
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function nativeComposerModelController() {
|
|
596
|
+
const trigger = document.querySelector("[data-codex-intelligence-trigger]");
|
|
597
|
+
const fiberKey = Object.getOwnPropertyNames(trigger ?? {}).find((key) => key.startsWith("__reactFiber$"));
|
|
598
|
+
let fiber = fiberKey ? trigger[fiberKey] : null;
|
|
599
|
+
for (let depth = 0; fiber && depth < 40; depth += 1, fiber = fiber.return) {
|
|
600
|
+
const props = fiber.memoizedProps;
|
|
601
|
+
if (Array.isArray(props?.models) && typeof props.onSelectReasoningEffort === "function") return props;
|
|
602
|
+
}
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function nativeCatalogModelFor(identity) {
|
|
607
|
+
const models = nativeComposerModelController()?.models ?? [];
|
|
608
|
+
const exact = models.find((model) => String(model.model ?? "").toLowerCase() === identity.raw.toLowerCase());
|
|
609
|
+
const target = comparableModelLabel(identity.backend);
|
|
610
|
+
const aliases = exact ? [] : models.filter((model) => (
|
|
611
|
+
comparableModelLabel(model.model) === target || comparableModelLabel(model.displayName) === target
|
|
612
|
+
));
|
|
613
|
+
const model = exact ?? (aliases.length === 1 ? aliases[0] : null);
|
|
614
|
+
if (!model) return null;
|
|
615
|
+
return {
|
|
616
|
+
slug: String(model.model ?? ""),
|
|
617
|
+
displayName: String(model.displayName ?? model.model ?? ""),
|
|
618
|
+
defaultReasoningLevel: String(model.defaultReasoningEffort ?? ""),
|
|
619
|
+
supportedReasoningLevels: (model.supportedReasoningEfforts ?? []).map((level) => ({
|
|
620
|
+
effort: String(level?.reasoningEffort ?? level?.effort ?? level ?? ""),
|
|
621
|
+
})).filter((level) => level.effort),
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function modelDefinitionFor(identity, selector) {
|
|
626
|
+
return nativeCatalogModelFor(identity) ?? catalogModelFor(identity, selector);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function nativeModelDescriptorForItem(item, observedRaw) {
|
|
630
|
+
const target = comparableModelLabel(observedRaw);
|
|
631
|
+
if (!target) return null;
|
|
632
|
+
const fiberKey = Object.getOwnPropertyNames(item).find((key) => key.startsWith("__reactFiber$"));
|
|
633
|
+
let fiber = fiberKey ? item[fiberKey] : null;
|
|
634
|
+
for (let depth = 0; fiber && depth < 35; depth += 1, fiber = fiber.return) {
|
|
635
|
+
if (!Array.isArray(fiber.memoizedProps)) continue;
|
|
636
|
+
const matches = fiber.memoizedProps.map((element) => {
|
|
637
|
+
const props = element?.props?.children?.props;
|
|
638
|
+
const slug = String(props?.model ?? element?.key ?? "").trim();
|
|
639
|
+
const displayName = String(props?.displayName ?? "").trim();
|
|
640
|
+
return slug && (
|
|
641
|
+
comparableModelLabel(slug) === target || comparableModelLabel(displayName) === target
|
|
642
|
+
) ? { slug, displayName } : null;
|
|
643
|
+
}).filter(Boolean);
|
|
644
|
+
if (matches.length === 1) return matches[0];
|
|
645
|
+
}
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function catalogModelFor(identity, selector) {
|
|
650
|
+
const raw = identity.raw.toLowerCase();
|
|
651
|
+
const backend = identity.backend.toLowerCase();
|
|
652
|
+
const exact = selector.models?.find((model) => (
|
|
653
|
+
model.slug.toLowerCase() === raw
|
|
654
|
+
|| model.slug.toLowerCase() === backend
|
|
655
|
+
|| model.displayName.toLowerCase() === raw
|
|
656
|
+
)) ?? null;
|
|
657
|
+
if (exact) return exact;
|
|
658
|
+
const target = comparableModelLabel(identity.backend);
|
|
659
|
+
if (!target) return null;
|
|
660
|
+
const aliases = (selector.models ?? []).filter((model) => (
|
|
661
|
+
comparableModelLabel(model.slug) === target || comparableModelLabel(model.displayName) === target
|
|
662
|
+
));
|
|
663
|
+
return aliases.length === 1 ? aliases[0] : null;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function loadModelFavorites(selector) {
|
|
667
|
+
try {
|
|
668
|
+
const value = JSON.parse(localStorage.getItem(selector.favoriteStorageKey) ?? "[]");
|
|
669
|
+
return new Set(Array.isArray(value) ? value.map(String) : []);
|
|
670
|
+
} catch {
|
|
671
|
+
return new Set();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function saveModelFavorites(selector, favorites) {
|
|
676
|
+
try {
|
|
677
|
+
localStorage.setItem(selector.favoriteStorageKey, JSON.stringify(Array.from(favorites)));
|
|
678
|
+
} catch {
|
|
679
|
+
// A disabled localStorage must not break the native picker.
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function nativeModelPickerMenu() {
|
|
684
|
+
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]')).find(
|
|
685
|
+
(menu) => menu.querySelector("[data-reasoning-slider]") && nativeModelPickerRow(menu),
|
|
686
|
+
) ?? null;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function nativeModelPickerRow(menu) {
|
|
690
|
+
const rows = Array.from(menu?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
691
|
+
return rows.find((row) => (
|
|
692
|
+
row.getAttribute("aria-haspopup") === "menu"
|
|
693
|
+
&& !row.hasAttribute("data-model-picker-view-toggle")
|
|
694
|
+
)) ?? rows.find((row) => {
|
|
695
|
+
const aria = String(row.getAttribute("aria-label") ?? "").trim();
|
|
696
|
+
const firstLine = String(row.innerText ?? "").split("\n", 1)[0].trim();
|
|
697
|
+
return /^model(?:\s|$)/i.test(aria) || /^model$/i.test(firstLine);
|
|
698
|
+
}) ?? null;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function nativeModelValue(menu) {
|
|
702
|
+
const row = nativeModelPickerRow(menu);
|
|
703
|
+
const values = String(row?.innerText ?? "").split("\n").map((value) => value.trim()).filter(Boolean);
|
|
704
|
+
if (values.length > 1) return values.at(-1);
|
|
705
|
+
const aria = String(row?.getAttribute("aria-label") ?? "").trim();
|
|
706
|
+
return aria.replace(/^\S+\s+/, "").trim();
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function firstVisibleTextNode(element) {
|
|
710
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
711
|
+
let node = walker.nextNode();
|
|
712
|
+
while (node) {
|
|
713
|
+
if (node.textContent.trim() && !node.parentElement?.closest("svg")) return node;
|
|
714
|
+
node = walker.nextNode();
|
|
715
|
+
}
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function modelMenuItems(menu) {
|
|
720
|
+
return Array.from(menu?.querySelectorAll(
|
|
721
|
+
':scope [role="menuitemradio"], :scope [role="menuitemcheckbox"], :scope [role="menuitem"]',
|
|
722
|
+
) ?? []).filter((item) => item.closest('[role="menu"]') === menu && !item.hasAttribute("aria-haspopup"));
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function nativeMenuItemLabel(item) {
|
|
726
|
+
return Array.from(item?.querySelectorAll("span") ?? [])
|
|
727
|
+
.map((span) => String(span.textContent ?? "").trim())
|
|
728
|
+
.find(Boolean) ?? String(item?.textContent ?? "").trim();
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function nativeModelSubmenu(parentMenu) {
|
|
732
|
+
if (nativeModelPickerRow(parentMenu)?.getAttribute("aria-expanded") !== "true") return null;
|
|
733
|
+
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]')).find((menu) => (
|
|
734
|
+
!menu.querySelector("[data-reasoning-slider]") && modelMenuItems(menu).length > 1
|
|
735
|
+
)) ?? null;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function isNativeEffortSubmenu(menu) {
|
|
739
|
+
const effortLabels = new Set([
|
|
740
|
+
"none", "minimal", "light", "low", "medium", "high", "extra high", "max", "ultra",
|
|
741
|
+
]);
|
|
742
|
+
if (!menu || menu.querySelector("[data-reasoning-slider]")) return false;
|
|
743
|
+
const labels = modelMenuItems(menu).map((item) => (
|
|
744
|
+
nativeMenuItemLabel(item).toLowerCase()
|
|
745
|
+
)).filter(Boolean);
|
|
746
|
+
return labels.length > 1 && labels.every((label) => effortLabels.has(label));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function nativeEffortSubmenu() {
|
|
750
|
+
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]'))
|
|
751
|
+
.find((menu) => isNativeEffortSubmenu(menu)) ?? null;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function nativeModelPickerSubmenus() {
|
|
755
|
+
return Array.from(document.querySelectorAll('[role="menu"][data-state="open"]')).filter((menu) => {
|
|
756
|
+
if (menu.querySelector("[data-reasoning-slider]")) return false;
|
|
757
|
+
if (menu.hasAttribute("data-codex-model-slider-menu")) return true;
|
|
758
|
+
return isNativeEffortSubmenu(menu);
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function setReactControlledMenuOpen(element, open) {
|
|
763
|
+
if (!element) return false;
|
|
764
|
+
const fiberKey = Object.getOwnPropertyNames(element).find((key) => key.startsWith("__reactFiber$"));
|
|
765
|
+
let fiber = fiberKey ? element[fiberKey] : null;
|
|
766
|
+
for (let depth = 0; fiber && depth < 40; depth += 1, fiber = fiber.return) {
|
|
767
|
+
const props = fiber.memoizedProps;
|
|
768
|
+
if (typeof props?.open === "boolean" && props.open !== open && typeof props.onOpenChange === "function") {
|
|
769
|
+
props.onOpenChange(open);
|
|
770
|
+
return true;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function closeReactControlledMenu(element) {
|
|
777
|
+
return setReactControlledMenuOpen(element, false);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function invokeNativeMenuItemSelect(item) {
|
|
781
|
+
if (!item) return false;
|
|
782
|
+
const fiberKey = Object.getOwnPropertyNames(item).find((key) => key.startsWith("__reactFiber$"));
|
|
783
|
+
let fiber = fiberKey ? item[fiberKey] : null;
|
|
784
|
+
for (let depth = 0; fiber && depth < 24; depth += 1, fiber = fiber.return) {
|
|
785
|
+
const onSelect = fiber.memoizedProps?.onSelect;
|
|
786
|
+
if (typeof onSelect !== "function") continue;
|
|
787
|
+
onSelect({ preventDefault() {}, defaultPrevented: false });
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function closeNativeModelPickerSubmenus() {
|
|
794
|
+
const submenus = nativeModelPickerSubmenus();
|
|
795
|
+
const parentMenu = nativeModelPickerMenu();
|
|
796
|
+
const expandedRows = Array.from(
|
|
797
|
+
parentMenu?.querySelectorAll('[role="menuitem"][aria-haspopup="menu"][aria-expanded="true"]') ?? [],
|
|
798
|
+
);
|
|
799
|
+
let controlledMenuClosed = false;
|
|
800
|
+
for (const row of expandedRows) {
|
|
801
|
+
controlledMenuClosed = closeReactControlledMenu(row) || controlledMenuClosed;
|
|
802
|
+
}
|
|
803
|
+
for (const submenu of submenus.reverse()) {
|
|
804
|
+
if (controlledMenuClosed || closeReactControlledMenu(submenu)) continue;
|
|
805
|
+
submenu.dispatchEvent(new KeyboardEvent("keydown", {
|
|
806
|
+
key: "Escape",
|
|
807
|
+
code: "Escape",
|
|
808
|
+
bubbles: true,
|
|
809
|
+
cancelable: true,
|
|
810
|
+
}));
|
|
811
|
+
}
|
|
812
|
+
return submenus.length > 0;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function endNativeEffortTransaction() {
|
|
816
|
+
clearTimeout(modelSelectorEffortTransactionTimer);
|
|
817
|
+
modelSelectorEffortTransactionTimer = null;
|
|
818
|
+
modelSelectorPendingOutsideDismiss = false;
|
|
819
|
+
document.documentElement.removeAttribute("data-codex-model-slider-selecting-effort");
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function beginNativeEffortTransaction() {
|
|
823
|
+
clearTimeout(modelSelectorEffortTransactionTimer);
|
|
824
|
+
document.documentElement.setAttribute("data-codex-model-slider-selecting-effort", "");
|
|
825
|
+
modelSelectorEffortTransactionTimer = setTimeout(() => {
|
|
826
|
+
closeNativeModelPickerSubmenus();
|
|
827
|
+
endNativeEffortTransaction();
|
|
828
|
+
}, 2_000);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function deferNativeEffortOutsideDismiss(event) {
|
|
832
|
+
const insideMenu = event.target.closest?.('[role="menu"]');
|
|
833
|
+
if (insideMenu) return;
|
|
834
|
+
if (event.type === "pointerdown" && modelSelectorEffortTransactionTimer != null) {
|
|
835
|
+
modelSelectorPendingOutsideDismiss = true;
|
|
836
|
+
modelSelectorSuppressOutsidePointerSequence = true;
|
|
837
|
+
}
|
|
838
|
+
if (!modelSelectorSuppressOutsidePointerSequence) return;
|
|
839
|
+
event.preventDefault();
|
|
840
|
+
event.stopPropagation();
|
|
841
|
+
event.stopImmediatePropagation();
|
|
842
|
+
if (event.type === "click") modelSelectorSuppressOutsidePointerSequence = false;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
for (const type of ["pointerdown", "mousedown", "pointerup", "mouseup", "click"]) {
|
|
846
|
+
document.addEventListener(type, deferNativeEffortOutsideDismiss, {
|
|
847
|
+
capture: true,
|
|
848
|
+
signal: lifetime.signal,
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function modelItemRawValue(item) {
|
|
853
|
+
if (item.dataset.codexModelSliderRaw) return item.dataset.codexModelSliderRaw;
|
|
854
|
+
return String(item.innerText ?? "").split("\n").map((value) => value.trim()).filter(Boolean).join(" ");
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function visibleBlockingDialog() {
|
|
858
|
+
return Array.from(document.querySelectorAll('[role="dialog"]')).some((dialog) => {
|
|
859
|
+
const rect = dialog.getBoundingClientRect();
|
|
860
|
+
const style = getComputedStyle(dialog);
|
|
861
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function dispatchPointerClick(element) {
|
|
866
|
+
if (!element) return;
|
|
867
|
+
const rect = element.getBoundingClientRect();
|
|
868
|
+
const init = {
|
|
869
|
+
bubbles: true,
|
|
870
|
+
cancelable: true,
|
|
871
|
+
view: window,
|
|
872
|
+
pointerType: "mouse",
|
|
873
|
+
button: 0,
|
|
874
|
+
clientX: rect.left + rect.width / 2,
|
|
875
|
+
clientY: rect.top + rect.height / 2,
|
|
876
|
+
};
|
|
877
|
+
for (const type of [
|
|
878
|
+
"pointermove", "pointerover", "mouseover", "mousemove",
|
|
879
|
+
"pointerdown", "mousedown", "pointerup", "mouseup", "click",
|
|
880
|
+
]) {
|
|
881
|
+
const EventType = type.startsWith("pointer") ? PointerEvent : MouseEvent;
|
|
882
|
+
const pressed = type === "pointerdown" || type === "mousedown";
|
|
883
|
+
element.dispatchEvent(new EventType(type, {
|
|
884
|
+
...init,
|
|
885
|
+
buttons: pressed ? 1 : 0,
|
|
886
|
+
detail: type === "click" ? 1 : 0,
|
|
887
|
+
}));
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function returnToNativeModelSlider(attempt = 0) {
|
|
892
|
+
if (visibleBlockingDialog()) {
|
|
893
|
+
closeNativeModelPickerSubmenus();
|
|
894
|
+
setTimeout(endNativeEffortTransaction, 120);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (closeNativeModelPickerSubmenus()) {
|
|
898
|
+
setTimeout(() => returnToNativeModelSlider(attempt), 100);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
let parentMenu = nativeModelPickerMenu();
|
|
902
|
+
if (parentMenu) {
|
|
903
|
+
const toggle = parentMenu.querySelector("[data-model-picker-view-toggle]");
|
|
904
|
+
if (toggle?.getAttribute("aria-expanded") === "true") toggle.click();
|
|
905
|
+
setTimeout(() => {
|
|
906
|
+
if (!nativeModelPickerMenu() && attempt < 3) {
|
|
907
|
+
returnToNativeModelSlider(attempt + 1);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
modelSelectorPendingReturn = false;
|
|
911
|
+
if (nativeModelPickerSubmenus().length === 0) endNativeEffortTransaction();
|
|
912
|
+
queueEnsure();
|
|
913
|
+
}, 140);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const trigger = document.querySelector("[data-codex-intelligence-trigger]");
|
|
917
|
+
if (!trigger) return;
|
|
918
|
+
dispatchPointerClick(trigger);
|
|
919
|
+
setTimeout(() => {
|
|
920
|
+
if (!nativeModelPickerMenu() && attempt < 3) {
|
|
921
|
+
returnToNativeModelSlider(attempt + 1);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
modelSelectorPendingReturn = false;
|
|
925
|
+
if (nativeModelPickerSubmenus().length === 0) endNativeEffortTransaction();
|
|
926
|
+
queueEnsure();
|
|
927
|
+
}, 140);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function scheduleModelSliderReturn() {
|
|
931
|
+
modelSelectorPendingReturn = true;
|
|
932
|
+
clearTimeout(modelSelectorReturnTimer);
|
|
933
|
+
modelSelectorReturnTimer = setTimeout(() => {
|
|
934
|
+
modelSelectorReturnTimer = null;
|
|
935
|
+
returnToNativeModelSlider();
|
|
936
|
+
}, 260);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function renderEnhancedModelMenu(menu, selector) {
|
|
940
|
+
const items = modelMenuItems(menu).filter((item) => item.dataset.codexModelSliderRaw);
|
|
941
|
+
if (!items.length) return;
|
|
942
|
+
const favorites = loadModelFavorites(selector);
|
|
943
|
+
items.forEach((item) => { item.style.display = ""; });
|
|
944
|
+
const starred = items.filter((item) => favorites.has(item.dataset.codexModelSliderRaw));
|
|
945
|
+
const ordinary = items.filter((item) => !favorites.has(item.dataset.codexModelSliderRaw));
|
|
946
|
+
const desired = [...starred, ...ordinary];
|
|
947
|
+
const parent = items[0].parentElement;
|
|
948
|
+
if (!parent || !desired.every((item) => item.parentElement === parent)) return;
|
|
949
|
+
const current = Array.from(parent.children).filter((child) => child.hasAttribute?.("data-codex-model-slider-item"));
|
|
950
|
+
if (desired.some((item, index) => current[index] !== item)) desired.forEach((item) => parent.append(item));
|
|
951
|
+
|
|
952
|
+
let divider = parent.querySelector(":scope > [data-codex-model-slider-divider]");
|
|
953
|
+
if (starred.length && ordinary.length) {
|
|
954
|
+
if (!divider) {
|
|
955
|
+
divider = document.createElement("div");
|
|
956
|
+
divider.setAttribute("data-codex-model-slider-divider", "");
|
|
957
|
+
divider.setAttribute("role", "separator");
|
|
958
|
+
}
|
|
959
|
+
if (divider.nextElementSibling !== ordinary[0]) parent.insertBefore(divider, ordinary[0]);
|
|
960
|
+
} else {
|
|
961
|
+
divider?.remove();
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (!modelSelectorMenuSizeFrames.has(menu)) {
|
|
965
|
+
const frame = requestAnimationFrame(() => {
|
|
966
|
+
modelSelectorMenuSizeFrames.delete(menu);
|
|
967
|
+
sizeModelMenuViewport(menu, selector.maxVisibleItems);
|
|
968
|
+
});
|
|
969
|
+
modelSelectorMenuSizeFrames.set(menu, frame);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function sizeModelMenuViewport(menu, maxVisibleItems) {
|
|
974
|
+
if (!menu.isConnected) return;
|
|
975
|
+
const items = modelMenuItems(menu).filter((item) => item.dataset.codexModelSliderRaw);
|
|
976
|
+
if (items.length <= maxVisibleItems) {
|
|
977
|
+
menu.removeAttribute("data-codex-model-slider-overflow");
|
|
978
|
+
menu.style.removeProperty("--codex-model-slider-menu-max-height");
|
|
979
|
+
menu.style.removeProperty("max-height");
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const lastVisibleItem = items[maxVisibleItems - 1];
|
|
983
|
+
const menuRect = menu.getBoundingClientRect();
|
|
984
|
+
const itemRect = lastVisibleItem.getBoundingClientRect();
|
|
985
|
+
const paddingBottom = Number.parseFloat(getComputedStyle(menu).paddingBottom) || 0;
|
|
986
|
+
const maxHeight = Math.ceil(itemRect.bottom - menuRect.top + menu.scrollTop + paddingBottom);
|
|
987
|
+
menu.setAttribute("data-codex-model-slider-overflow", "");
|
|
988
|
+
menu.style.setProperty("--codex-model-slider-menu-max-height", `${maxHeight}px`);
|
|
989
|
+
menu.style.overflowY = "auto";
|
|
990
|
+
positionModelSubmenu(menu);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function toggleModelFavorite(event, menu, selector, raw, label) {
|
|
994
|
+
event.preventDefault();
|
|
995
|
+
event.stopPropagation();
|
|
996
|
+
event.stopImmediatePropagation();
|
|
997
|
+
const favorites = loadModelFavorites(selector);
|
|
998
|
+
if (favorites.has(raw)) favorites.delete(raw);
|
|
999
|
+
else favorites.add(raw);
|
|
1000
|
+
saveModelFavorites(selector, favorites);
|
|
1001
|
+
for (const item of modelMenuItems(menu)) {
|
|
1002
|
+
if (!item.dataset.codexModelSliderRaw) continue;
|
|
1003
|
+
const starred = favorites.has(item.dataset.codexModelSliderRaw);
|
|
1004
|
+
const button = item.querySelector("[data-codex-model-slider-star]");
|
|
1005
|
+
if (!button) continue;
|
|
1006
|
+
button.replaceChildren(createModelSelectorIcon(starred ? selector.icons.unstar : selector.icons.star));
|
|
1007
|
+
const itemLabel = item.querySelector("[data-codex-model-slider-name]")?.textContent ?? label;
|
|
1008
|
+
button.setAttribute("aria-label", selector.strings[starred ? "unstar" : "star"].replace("{model}", itemLabel));
|
|
1009
|
+
button.setAttribute("title", selector.strings[starred ? "unstar" : "star"].replace("{model}", itemLabel));
|
|
1010
|
+
}
|
|
1011
|
+
renderEnhancedModelMenu(menu, selector);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function enhanceModelItem(item, menu, selector, index) {
|
|
1015
|
+
const observedRaw = modelItemRawValue(item);
|
|
1016
|
+
if (!observedRaw) return;
|
|
1017
|
+
if (!modelSelectorOriginalItems.has(item)) modelSelectorOriginalItems.set(item, item.innerHTML);
|
|
1018
|
+
const observedIdentity = modelIdentity(observedRaw);
|
|
1019
|
+
const nativeDescriptor = nativeModelDescriptorForItem(item, observedRaw);
|
|
1020
|
+
const catalogModel = catalogModelFor(observedIdentity, selector);
|
|
1021
|
+
const raw = nativeDescriptor?.slug ?? catalogModel?.slug ?? observedRaw;
|
|
1022
|
+
item.dataset.codexModelSliderRaw = raw;
|
|
1023
|
+
item.dataset.codexModelSliderItem = "";
|
|
1024
|
+
item.dataset.codexModelSliderOrder = String(index);
|
|
1025
|
+
item.dataset.codexModelSliderIdentitySource = nativeDescriptor ? "native" : catalogModel ? "catalog" : "visible";
|
|
1026
|
+
if (item.querySelector("[data-codex-model-slider-row]")) return;
|
|
1027
|
+
|
|
1028
|
+
const identity = modelIdentity(raw);
|
|
1029
|
+
const display = modelDisplayLabel(identity, selector);
|
|
1030
|
+
const provider = providerLabel(identity.provider, selector);
|
|
1031
|
+
const brand = brandForModel(identity, selector);
|
|
1032
|
+
item.toggleAttribute("data-codex-model-slider-selected", Boolean(item.querySelector("svg")));
|
|
1033
|
+
const favorites = loadModelFavorites(selector);
|
|
1034
|
+
const starred = favorites.has(raw);
|
|
1035
|
+
const row = document.createElement("span");
|
|
1036
|
+
row.setAttribute("data-codex-model-slider-row", "");
|
|
1037
|
+
const brandRoot = document.createElement("span");
|
|
1038
|
+
brandRoot.setAttribute("data-codex-model-slider-brand", brand?.id ?? "fallback");
|
|
1039
|
+
brandRoot.append(createModelSelectorIcon(brand?.icon ?? selector.icons.fallback, { brand: Boolean(brand) }));
|
|
1040
|
+
const copy = document.createElement("span");
|
|
1041
|
+
copy.setAttribute("data-codex-model-slider-copy", "");
|
|
1042
|
+
const name = document.createElement("span");
|
|
1043
|
+
name.setAttribute("data-codex-model-slider-name", "");
|
|
1044
|
+
name.textContent = display;
|
|
1045
|
+
copy.append(name);
|
|
1046
|
+
if (provider) {
|
|
1047
|
+
const secondary = document.createElement("span");
|
|
1048
|
+
secondary.setAttribute("data-codex-model-slider-provider", "");
|
|
1049
|
+
secondary.textContent = provider;
|
|
1050
|
+
copy.append(secondary);
|
|
1051
|
+
}
|
|
1052
|
+
row.append(brandRoot, copy);
|
|
1053
|
+
const star = document.createElement("button");
|
|
1054
|
+
star.type = "button";
|
|
1055
|
+
star.setAttribute("data-codex-model-slider-star", starred ? "unstar" : "star");
|
|
1056
|
+
star.setAttribute("aria-label", selector.strings[starred ? "unstar" : "star"].replace("{model}", display));
|
|
1057
|
+
star.setAttribute("title", selector.strings[starred ? "unstar" : "star"].replace("{model}", display));
|
|
1058
|
+
star.append(createModelSelectorIcon(starred ? selector.icons.unstar : selector.icons.star));
|
|
1059
|
+
row.append(star);
|
|
1060
|
+
item.replaceChildren(row);
|
|
1061
|
+
if (!item.hasAttribute("data-codex-model-slider-return-listener")) {
|
|
1062
|
+
item.setAttribute("data-codex-model-slider-return-listener", "");
|
|
1063
|
+
item.addEventListener("click", (event) => {
|
|
1064
|
+
if (!event.target.closest?.("[data-codex-model-slider-star]")) scheduleModelSliderReturn();
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function enhanceNativeModelSubmenu(menu, selector) {
|
|
1070
|
+
menu.setAttribute("data-codex-model-slider-menu", "");
|
|
1071
|
+
observeModelSubmenuPosition(menu);
|
|
1072
|
+
positionModelSubmenu(menu);
|
|
1073
|
+
const items = modelMenuItems(menu);
|
|
1074
|
+
items.forEach((item, index) => enhanceModelItem(item, menu, selector, index));
|
|
1075
|
+
if (!menu.hasAttribute("data-codex-model-slider-events")) {
|
|
1076
|
+
menu.setAttribute("data-codex-model-slider-events", "");
|
|
1077
|
+
menu.addEventListener("click", (event) => {
|
|
1078
|
+
const star = event.target.closest?.("[data-codex-model-slider-star]");
|
|
1079
|
+
if (star) {
|
|
1080
|
+
const item = star.closest("[data-codex-model-slider-item]");
|
|
1081
|
+
const raw = item?.dataset.codexModelSliderRaw;
|
|
1082
|
+
if (!raw) return;
|
|
1083
|
+
const label = item.querySelector("[data-codex-model-slider-name]")?.textContent ?? raw;
|
|
1084
|
+
toggleModelFavorite(event, menu, selector, raw, label);
|
|
1085
|
+
event.stopImmediatePropagation();
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
if (event.target.closest?.("[data-codex-model-slider-item]")) scheduleModelSliderReturn();
|
|
1089
|
+
}, { capture: true });
|
|
1090
|
+
menu.addEventListener("pointerdown", (event) => {
|
|
1091
|
+
const star = event.target.closest?.("[data-codex-model-slider-star]");
|
|
1092
|
+
if (star) {
|
|
1093
|
+
event.stopPropagation();
|
|
1094
|
+
event.stopImmediatePropagation();
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
if (event.target.closest?.("[data-codex-model-slider-item]")) scheduleModelSliderReturn();
|
|
1098
|
+
}, { capture: true });
|
|
1099
|
+
for (const type of ["mousedown", "pointerup", "mouseup"]) {
|
|
1100
|
+
menu.addEventListener(type, (event) => {
|
|
1101
|
+
if (!event.target.closest?.("[data-codex-model-slider-star]")) return;
|
|
1102
|
+
event.stopPropagation();
|
|
1103
|
+
event.stopImmediatePropagation();
|
|
1104
|
+
}, { capture: true });
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
renderEnhancedModelMenu(menu, selector);
|
|
1108
|
+
if (!menu.hasAttribute("data-codex-model-slider-positioned")) menu.style.visibility = "hidden";
|
|
1109
|
+
positionModelSubmenu(menu);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function positionModelSubmenu(menu) {
|
|
1113
|
+
const trigger = document.getElementById(menu?.getAttribute("aria-labelledby") ?? "");
|
|
1114
|
+
const parentMenu = trigger?.closest('[role="menu"]') ?? nativeModelPickerMenu();
|
|
1115
|
+
const wrapper = menu?.parentElement;
|
|
1116
|
+
if (
|
|
1117
|
+
!parentMenu?.hasAttribute("data-codex-model-slider-generic-menu")
|
|
1118
|
+
|| !wrapper?.hasAttribute("data-radix-popper-content-wrapper")
|
|
1119
|
+
) return;
|
|
1120
|
+
const parentRect = parentMenu.getBoundingClientRect();
|
|
1121
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
1122
|
+
if (!(parentRect.width > 0 && wrapperRect.width > 0 && wrapperRect.height > 0)) return;
|
|
1123
|
+
const inset = 8;
|
|
1124
|
+
const left = Math.min(
|
|
1125
|
+
innerWidth - wrapperRect.width - inset,
|
|
1126
|
+
Math.max(inset, parentRect.right - wrapperRect.width),
|
|
1127
|
+
);
|
|
1128
|
+
const top = Math.min(
|
|
1129
|
+
innerHeight - wrapperRect.height - inset,
|
|
1130
|
+
Math.max(inset, parentRect.bottom - wrapperRect.height),
|
|
1131
|
+
);
|
|
1132
|
+
const transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;
|
|
1133
|
+
if (wrapper.style.transform !== transform) wrapper.style.transform = transform;
|
|
1134
|
+
if (wrapper.style.getPropertyValue("--radix-popper-transform-origin") !== "100% 100%") {
|
|
1135
|
+
wrapper.style.setProperty("--radix-popper-transform-origin", "100% 100%");
|
|
1136
|
+
}
|
|
1137
|
+
const record = modelSelectorPositionObservers.get(wrapper);
|
|
1138
|
+
if (record) record.lastAppliedStyle = wrapper.getAttribute("style") ?? "";
|
|
1139
|
+
requestAnimationFrame(() => {
|
|
1140
|
+
if (!menu.isConnected) return;
|
|
1141
|
+
const positionedRect = wrapper.getBoundingClientRect();
|
|
1142
|
+
if (
|
|
1143
|
+
Math.abs(positionedRect.right - parentRect.right) <= 2
|
|
1144
|
+
&& Math.abs(positionedRect.bottom - parentRect.bottom) <= 2
|
|
1145
|
+
) {
|
|
1146
|
+
menu.setAttribute("data-codex-model-slider-positioned", "");
|
|
1147
|
+
menu.style.removeProperty("visibility");
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function observeModelSubmenuPosition(menu) {
|
|
1153
|
+
const wrapper = menu?.parentElement;
|
|
1154
|
+
if (!wrapper?.hasAttribute("data-radix-popper-content-wrapper")) return;
|
|
1155
|
+
const existing = modelSelectorPositionObservers.get(wrapper);
|
|
1156
|
+
if (existing) {
|
|
1157
|
+
existing.menu = menu;
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const record = {
|
|
1161
|
+
menu,
|
|
1162
|
+
queued: false,
|
|
1163
|
+
observer: null,
|
|
1164
|
+
resizeObserver: null,
|
|
1165
|
+
lastAppliedStyle: null,
|
|
1166
|
+
};
|
|
1167
|
+
const schedule = (mutations = []) => {
|
|
1168
|
+
if (
|
|
1169
|
+
mutations.length
|
|
1170
|
+
&& wrapper.getAttribute("style") === record.lastAppliedStyle
|
|
1171
|
+
) return;
|
|
1172
|
+
if (record.queued) return;
|
|
1173
|
+
record.queued = true;
|
|
1174
|
+
requestAnimationFrame(() => {
|
|
1175
|
+
record.queued = false;
|
|
1176
|
+
if (record.menu.isConnected) positionModelSubmenu(record.menu);
|
|
1177
|
+
});
|
|
1178
|
+
};
|
|
1179
|
+
record.observer = new MutationObserver(schedule);
|
|
1180
|
+
record.observer.observe(wrapper, { attributes: true, attributeFilter: ["style"] });
|
|
1181
|
+
record.resizeObserver = new ResizeObserver(schedule);
|
|
1182
|
+
record.resizeObserver.observe(menu);
|
|
1183
|
+
const trigger = document.getElementById(menu.getAttribute("aria-labelledby") ?? "");
|
|
1184
|
+
const parentMenu = trigger?.closest('[role="menu"]');
|
|
1185
|
+
if (parentMenu) record.resizeObserver.observe(parentMenu);
|
|
1186
|
+
modelSelectorPositionObservers.set(wrapper, record);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function pruneModelSubmenuPositionObservers() {
|
|
1190
|
+
for (const [wrapper, record] of modelSelectorPositionObservers) {
|
|
1191
|
+
if (wrapper.isConnected) continue;
|
|
1192
|
+
record.observer.disconnect();
|
|
1193
|
+
record.resizeObserver.disconnect();
|
|
1194
|
+
modelSelectorPositionObservers.delete(wrapper);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function openNativeModelSubmenu(parentMenu) {
|
|
1199
|
+
let attempts = 0;
|
|
1200
|
+
const open = () => {
|
|
1201
|
+
attempts += 1;
|
|
1202
|
+
const currentMenu = nativeModelPickerMenu() ?? parentMenu;
|
|
1203
|
+
const row = nativeModelPickerRow(currentMenu);
|
|
1204
|
+
if (row) {
|
|
1205
|
+
setReactControlledMenuOpen(row, true);
|
|
1206
|
+
setTimeout(queueEnsure, 0);
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
if (attempts < 12) requestAnimationFrame(open);
|
|
1210
|
+
};
|
|
1211
|
+
setTimeout(open, 80);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function nativeEffortRow(parentMenu) {
|
|
1215
|
+
return Array.from(parentMenu?.querySelectorAll('[role="menuitem"][aria-haspopup="menu"]') ?? [])
|
|
1216
|
+
.filter((row) => !row.hasAttribute("data-model-picker-view-toggle"))[1] ?? null;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function effortLabel(effort) {
|
|
1220
|
+
return new Map([
|
|
1221
|
+
["none", "None"], ["minimal", "Minimal"], ["low", "Light"], ["medium", "Medium"],
|
|
1222
|
+
["high", "High"], ["xhigh", "Extra High"], ["max", "Max"], ["ultra", "Ultra"],
|
|
1223
|
+
]).get(String(effort).toLowerCase()) ?? formatIdentifier(effort);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function selectNativeEffort(parentMenu, effort) {
|
|
1227
|
+
const controller = nativeComposerModelController();
|
|
1228
|
+
if (typeof controller?.onSelectReasoningEffort === "function") {
|
|
1229
|
+
controller.onSelectReasoningEffort(effort);
|
|
1230
|
+
if (modelSelectorPendingOutsideDismiss) {
|
|
1231
|
+
closeReactControlledMenu(document.querySelector("[data-codex-intelligence-trigger]"));
|
|
1232
|
+
modelSelectorPendingOutsideDismiss = false;
|
|
1233
|
+
}
|
|
1234
|
+
queueEnsure();
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
const row = nativeEffortRow(parentMenu);
|
|
1238
|
+
if (!row) return;
|
|
1239
|
+
beginNativeEffortTransaction();
|
|
1240
|
+
if (!setReactControlledMenuOpen(row, true)) {
|
|
1241
|
+
endNativeEffortTransaction();
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
let observer = null;
|
|
1245
|
+
let selectionTimer = null;
|
|
1246
|
+
const select = () => {
|
|
1247
|
+
const submenu = nativeEffortSubmenu();
|
|
1248
|
+
const desired = effortLabel(effort).toLowerCase();
|
|
1249
|
+
const item = modelMenuItems(submenu).find((candidate) => (
|
|
1250
|
+
nativeMenuItemLabel(candidate).toLowerCase() === desired
|
|
1251
|
+
));
|
|
1252
|
+
if (!item) return;
|
|
1253
|
+
observer?.disconnect();
|
|
1254
|
+
clearTimeout(selectionTimer);
|
|
1255
|
+
const dismissAfterCommit = modelSelectorPendingOutsideDismiss;
|
|
1256
|
+
if (!invokeNativeMenuItemSelect(item)) {
|
|
1257
|
+
endNativeEffortTransaction();
|
|
1258
|
+
queueEnsure();
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
if (dismissAfterCommit) {
|
|
1262
|
+
closeReactControlledMenu(item);
|
|
1263
|
+
closeReactControlledMenu(document.querySelector("[data-codex-intelligence-trigger]"));
|
|
1264
|
+
modelSelectorPendingReturn = false;
|
|
1265
|
+
endNativeEffortTransaction();
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
scheduleModelSliderReturn();
|
|
1269
|
+
};
|
|
1270
|
+
observer = new MutationObserver(select);
|
|
1271
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1272
|
+
selectionTimer = setTimeout(() => {
|
|
1273
|
+
observer.disconnect();
|
|
1274
|
+
endNativeEffortTransaction();
|
|
1275
|
+
queueEnsure();
|
|
1276
|
+
}, 1_500);
|
|
1277
|
+
select();
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function setGenericSliderVisual(rail, index, count) {
|
|
1281
|
+
const progress = count <= 1 ? 0 : (index / (count - 1)) * 100;
|
|
1282
|
+
const nativeRange = rail.querySelector("[data-codex-model-slider-native-range]");
|
|
1283
|
+
const nativeThumb = rail.querySelector("[data-codex-model-slider-native-thumb]");
|
|
1284
|
+
if (nativeRange && nativeThumb) {
|
|
1285
|
+
const thumbRadius = 13;
|
|
1286
|
+
const correction = thumbRadius - (progress / 50) * thumbRadius;
|
|
1287
|
+
const segment = count <= 1 ? 100 : 100 / (count - 1);
|
|
1288
|
+
const edgeScale = Math.min(progress / segment, (100 - progress) / segment, 1);
|
|
1289
|
+
const edgeCorrection = correction * Math.max(0, edgeScale);
|
|
1290
|
+
nativeRange.style.transform = `translateX(calc(${progress - 100}% + ${edgeCorrection}px))`;
|
|
1291
|
+
nativeThumb.style.left = `calc(${progress}% + ${correction}px)`;
|
|
1292
|
+
const particleClip = rail.querySelector('[class*="_FastParticleClip_"]');
|
|
1293
|
+
if (particleClip) particleClip.style.clipPath = `inset(0 calc(${100 - progress}% - ${edgeCorrection}px) 0 0)`;
|
|
1294
|
+
}
|
|
1295
|
+
rail.style.setProperty("--codex-model-slider-progress", `${progress}%`);
|
|
1296
|
+
rail.querySelectorAll("[data-codex-model-slider-tick]").forEach((tick, tickIndex) => {
|
|
1297
|
+
tick.setAttribute("data-selected", String(tickIndex <= index));
|
|
1298
|
+
});
|
|
1299
|
+
rail.setAttribute("aria-valuenow", String(index));
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function nativeParticleSeed(index, salt) {
|
|
1303
|
+
const value = Math.sin((index + 1) * 12.9898 + salt * 78.233) * 43758.5453;
|
|
1304
|
+
return value - Math.floor(value);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function syncNativeFastParticles(rail, enabled) {
|
|
1308
|
+
const track = Array.from(rail.children).find((element) => String(element.className).includes("_Track_"));
|
|
1309
|
+
if (!track) return;
|
|
1310
|
+
track.querySelectorAll('[class*="_FastParticleClip_"]').forEach((element) => element.remove());
|
|
1311
|
+
if (!enabled || matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
1312
|
+
const clip = document.createElement("span");
|
|
1313
|
+
clip.className = "_FastParticleClip_3jngk_242";
|
|
1314
|
+
clip.setAttribute("aria-hidden", "true");
|
|
1315
|
+
clip.style.opacity = "1";
|
|
1316
|
+
const particles = document.createElement("span");
|
|
1317
|
+
particles.className = "_FastTrackParticles_1pz9e_1";
|
|
1318
|
+
particles.setAttribute("aria-hidden", "true");
|
|
1319
|
+
particles.setAttribute("data-animation-active", "true");
|
|
1320
|
+
const travelDuration = 1.9;
|
|
1321
|
+
const stagger = travelDuration / 14;
|
|
1322
|
+
const valueMax = Number(rail.getAttribute("aria-valuemax") ?? 0);
|
|
1323
|
+
const valueNow = Number(rail.getAttribute("aria-valuenow") ?? 0);
|
|
1324
|
+
const initialStartPercent = valueMax > 0 ? (valueNow / valueMax) * 100 : 0;
|
|
1325
|
+
const remainingTravel = 1 - Math.min(Math.max(initialStartPercent, 0), 100) / 100;
|
|
1326
|
+
particles.append(...Array.from({ length: 14 }, (_, index) => {
|
|
1327
|
+
const speed = 1 + (nativeParticleSeed(index, 21) - 0.5) * 0.4;
|
|
1328
|
+
const duration = travelDuration / speed;
|
|
1329
|
+
const path = document.createElement("span");
|
|
1330
|
+
path.className = "_FastTrackParticlePath_1pz9e_7";
|
|
1331
|
+
path.style.animationDelay = `${index * stagger - duration * remainingTravel}s`;
|
|
1332
|
+
path.style.animationDuration = `${duration}s`;
|
|
1333
|
+
path.style.top = `${12 + nativeParticleSeed(index, 23) * 76}%`;
|
|
1334
|
+
const particle = document.createElement("span");
|
|
1335
|
+
particle.className = "_TrackParticle_1pz9e_22";
|
|
1336
|
+
particle.style.opacity = String(0.4 + nativeParticleSeed(index, 11) * 0.6);
|
|
1337
|
+
particle.style.transform = `translate(-50%, -50%) scale(${0.5 + nativeParticleSeed(index, 12) * 0.45})`;
|
|
1338
|
+
path.append(particle);
|
|
1339
|
+
return path;
|
|
1340
|
+
}));
|
|
1341
|
+
clip.append(particles);
|
|
1342
|
+
const tickRail = Array.from(track.children).find((element) => String(element.className).includes("_TickRail_"));
|
|
1343
|
+
track.insertBefore(clip, tickRail ?? null);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function syncGenericFastVisual(shell, enabled, maxPowerSelection, animate = false) {
|
|
1347
|
+
const button = shell.querySelector("[data-codex-model-slider-fast]");
|
|
1348
|
+
const rail = shell.querySelector("[data-codex-model-slider-rail]");
|
|
1349
|
+
if (!button || !rail) return;
|
|
1350
|
+
button.setAttribute("aria-pressed", String(enabled));
|
|
1351
|
+
button.setAttribute("data-fast-mode-enabled", String(enabled));
|
|
1352
|
+
button.setAttribute("data-max-power-selection", String(maxPowerSelection));
|
|
1353
|
+
button.setAttribute("aria-label", enabled
|
|
1354
|
+
? (locale === "zh-CN" ? "关闭 Fast 模式" : "Disable Fast mode")
|
|
1355
|
+
: (locale === "zh-CN" ? "开启 Fast 模式" : "Enable Fast mode"));
|
|
1356
|
+
const icon = createNativeFastIcon(enabled);
|
|
1357
|
+
const content = button.querySelector("[data-codex-model-slider-fast-content]");
|
|
1358
|
+
if (icon && content) content.replaceChildren(icon);
|
|
1359
|
+
rail.setAttribute("data-fast-mode", String(enabled));
|
|
1360
|
+
rail.setAttribute("data-fast-mode-dot-transition", animate ? (enabled ? "entering" : "exiting") : (enabled ? "active" : "inactive"));
|
|
1361
|
+
syncNativeFastParticles(rail, enabled);
|
|
1362
|
+
setGenericSliderVisual(rail, Number(rail.getAttribute("aria-valuenow") ?? 0), Number(rail.getAttribute("aria-valuemax") ?? 0) + 1);
|
|
1363
|
+
if (animate) {
|
|
1364
|
+
setTimeout(() => {
|
|
1365
|
+
if (rail.isConnected && rail.getAttribute("data-fast-mode") === String(enabled)) {
|
|
1366
|
+
rail.setAttribute("data-fast-mode-dot-transition", enabled ? "active" : "inactive");
|
|
1367
|
+
}
|
|
1368
|
+
}, enabled ? 1_200 : 350);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function cloneNativePowerSlider(parentMenu, count, fastEnabled) {
|
|
1373
|
+
const template = parentMenu.firstElementChild?.querySelector("[data-model-picker-power-slider]");
|
|
1374
|
+
if (!template) return null;
|
|
1375
|
+
const clone = template.cloneNode(true);
|
|
1376
|
+
clone.setAttribute("data-codex-model-slider-native-slider", "");
|
|
1377
|
+
clone.removeAttribute("data-keyboard-focused");
|
|
1378
|
+
const root = clone.firstElementChild;
|
|
1379
|
+
if (!root) return null;
|
|
1380
|
+
const nativeInput = clone.querySelector('[role="slider"]');
|
|
1381
|
+
nativeInput?.removeAttribute("role");
|
|
1382
|
+
nativeInput?.removeAttribute("tabindex");
|
|
1383
|
+
nativeInput?.removeAttribute("aria-valuemin");
|
|
1384
|
+
nativeInput?.removeAttribute("aria-valuemax");
|
|
1385
|
+
nativeInput?.removeAttribute("aria-valuenow");
|
|
1386
|
+
root.setAttribute("role", "slider");
|
|
1387
|
+
root.removeAttribute("aria-hidden");
|
|
1388
|
+
root.removeAttribute("data-disabled");
|
|
1389
|
+
root.removeAttribute("disabled");
|
|
1390
|
+
root.setAttribute("tabindex", "0");
|
|
1391
|
+
root.setAttribute("aria-valuemax", String(Math.max(0, count - 1)));
|
|
1392
|
+
root.setAttribute("data-fast-mode", String(fastEnabled));
|
|
1393
|
+
root.setAttribute("data-fast-mode-dot-transition", fastEnabled ? "active" : "inactive");
|
|
1394
|
+
root.setAttribute("data-max", "false");
|
|
1395
|
+
root.setAttribute("data-endpoint-labels-visible", "false");
|
|
1396
|
+
const range = Array.from(root.querySelectorAll("span")).find((element) => (
|
|
1397
|
+
String(element.className).includes("_Range_")
|
|
1398
|
+
));
|
|
1399
|
+
const thumbScale = Array.from(root.querySelectorAll("span")).find((element) => (
|
|
1400
|
+
String(element.className).includes("_ThumbScale_")
|
|
1401
|
+
));
|
|
1402
|
+
const tickRail = Array.from(root.querySelectorAll("div")).find((element) => (
|
|
1403
|
+
String(element.className).includes("_TickRail_")
|
|
1404
|
+
));
|
|
1405
|
+
const tickTemplate = tickRail?.querySelector("span");
|
|
1406
|
+
if (!range || !thumbScale || !tickRail || !tickTemplate) return null;
|
|
1407
|
+
range.setAttribute("data-codex-model-slider-native-range", "");
|
|
1408
|
+
thumbScale.setAttribute("data-codex-model-slider-native-thumb", "");
|
|
1409
|
+
tickRail.replaceChildren(...Array.from({ length: count }, (_, index) => {
|
|
1410
|
+
const tick = tickTemplate.cloneNode(true);
|
|
1411
|
+
tick.setAttribute("data-codex-model-slider-tick", "");
|
|
1412
|
+
const progress = count <= 1 ? 0 : (index / (count - 1)) * 100;
|
|
1413
|
+
const correction = 13 - (progress / 50) * 13;
|
|
1414
|
+
tick.style.left = `calc(${progress}% + ${correction}px)`;
|
|
1415
|
+
return tick;
|
|
1416
|
+
}));
|
|
1417
|
+
syncNativeFastParticles(root, fastEnabled);
|
|
1418
|
+
return clone;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function createGenericModelSlider(parentMenu, selector, identity, model, selectedEffort) {
|
|
1422
|
+
const efforts = model.supportedReasoningLevels.map((level) => level.effort);
|
|
1423
|
+
const selectedIndex = Math.max(0, efforts.indexOf(selectedEffort));
|
|
1424
|
+
const shell = document.createElement("div");
|
|
1425
|
+
shell.setAttribute("data-codex-model-slider-generic", `${identity.raw}:${selectedEffort}`);
|
|
1426
|
+
shell.className = "_Menu_1ryrb_2";
|
|
1427
|
+
shell.setAttribute("data-view", "simple");
|
|
1428
|
+
shell.setAttribute("data-reduced-motion", String(matchMedia("(prefers-reduced-motion: reduce)").matches));
|
|
1429
|
+
shell.setAttribute("data-transitions-ready", "false");
|
|
1430
|
+
const nativeRow = nativeModelPickerRow(parentMenu);
|
|
1431
|
+
const modelButton = document.createElement("div");
|
|
1432
|
+
modelButton.className = "_ViewToggle_1ryrb_13";
|
|
1433
|
+
modelButton.setAttribute("role", "button");
|
|
1434
|
+
modelButton.setAttribute("tabindex", "0");
|
|
1435
|
+
modelButton.setAttribute("data-codex-model-slider-model-button", "");
|
|
1436
|
+
modelButton.setAttribute("aria-label", `Model ${modelDisplayLabel(identity, selector)}`);
|
|
1437
|
+
const buttonContent = document.createElement("span");
|
|
1438
|
+
buttonContent.className = "_ViewToggleContent_1ryrb_334";
|
|
1439
|
+
const buttonLabel = document.createElement("span");
|
|
1440
|
+
buttonLabel.textContent = modelDisplayLabel(identity, selector);
|
|
1441
|
+
const nativeChevron = nativeRow?.querySelector("svg")?.cloneNode(true);
|
|
1442
|
+
if (nativeChevron) {
|
|
1443
|
+
nativeChevron.className.baseVal = "_ViewToggleIcon_1ryrb_85";
|
|
1444
|
+
nativeChevron.setAttribute("aria-hidden", "true");
|
|
1445
|
+
buttonContent.append(buttonLabel, nativeChevron);
|
|
1446
|
+
} else {
|
|
1447
|
+
buttonContent.append(buttonLabel);
|
|
1448
|
+
}
|
|
1449
|
+
modelButton.append(buttonContent);
|
|
1450
|
+
const openModels = () => openNativeModelSubmenu(parentMenu);
|
|
1451
|
+
modelButton.addEventListener("click", openModels);
|
|
1452
|
+
modelButton.addEventListener("keydown", (event) => {
|
|
1453
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1454
|
+
event.preventDefault();
|
|
1455
|
+
openModels();
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
const viewControls = document.createElement("div");
|
|
1459
|
+
viewControls.className = "_ViewControls_1ryrb_12";
|
|
1460
|
+
viewControls.setAttribute("data-expanded", "false");
|
|
1461
|
+
viewControls.setAttribute("data-ultra-warning-visible", "false");
|
|
1462
|
+
const controller = nativeComposerModelController();
|
|
1463
|
+
const fastOption = controller?.serviceTierOptions?.find((option) => option?.value === "priority") ?? null;
|
|
1464
|
+
const nativeModel = nativeCatalogModelFor(identity);
|
|
1465
|
+
const supportsFast = Boolean(nativeModel?.slug.toLowerCase().startsWith("gpt-") && fastOption);
|
|
1466
|
+
const fastEnabled = Boolean(
|
|
1467
|
+
supportsFast && (
|
|
1468
|
+
controller.selectedServiceTier?.id === (fastOption.tier?.id ?? "priority")
|
|
1469
|
+
|| controller.selectedServiceTier === fastOption.value
|
|
1470
|
+
),
|
|
1471
|
+
);
|
|
1472
|
+
if (supportsFast) {
|
|
1473
|
+
const fastButton = document.createElement("button");
|
|
1474
|
+
fastButton.type = "button";
|
|
1475
|
+
fastButton.className = "_FastModeToggle_1ryrb_14";
|
|
1476
|
+
fastButton.setAttribute("data-codex-model-slider-fast", "");
|
|
1477
|
+
fastButton.setAttribute("aria-pressed", String(fastEnabled));
|
|
1478
|
+
fastButton.setAttribute("data-fast-mode-enabled", String(fastEnabled));
|
|
1479
|
+
fastButton.setAttribute("data-max-power-selection", String(selectedEffort === "ultra"));
|
|
1480
|
+
fastButton.setAttribute("data-visible", "true");
|
|
1481
|
+
fastButton.setAttribute("aria-label", fastEnabled
|
|
1482
|
+
? (locale === "zh-CN" ? "关闭 Fast 模式" : "Disable Fast mode")
|
|
1483
|
+
: (locale === "zh-CN" ? "开启 Fast 模式" : "Enable Fast mode"));
|
|
1484
|
+
const fastIcon = createNativeFastIcon(fastEnabled);
|
|
1485
|
+
const fastContent = document.createElement("span");
|
|
1486
|
+
fastContent.className = "_FastModeToggleContent_1ryrb_399";
|
|
1487
|
+
fastContent.setAttribute("data-codex-model-slider-fast-content", "");
|
|
1488
|
+
if (fastIcon) fastContent.append(fastIcon);
|
|
1489
|
+
fastButton.append(fastContent);
|
|
1490
|
+
fastButton.addEventListener("click", () => {
|
|
1491
|
+
const nextEnabled = fastButton.getAttribute("aria-pressed") !== "true";
|
|
1492
|
+
syncGenericFastVisual(shell, nextEnabled, selectedEffort === "ultra", true);
|
|
1493
|
+
controller.onSelectServiceTier?.(nextEnabled ? fastOption.value : null);
|
|
1494
|
+
queueEnsure();
|
|
1495
|
+
});
|
|
1496
|
+
viewControls.append(modelButton, fastButton);
|
|
1497
|
+
} else {
|
|
1498
|
+
viewControls.append(modelButton);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
const nativeSlider = cloneNativePowerSlider(parentMenu, efforts.length, fastEnabled);
|
|
1502
|
+
const rail = nativeSlider?.querySelector('[role="slider"]') ?? document.createElement("div");
|
|
1503
|
+
rail.setAttribute("data-codex-model-slider-rail", "");
|
|
1504
|
+
if (!nativeSlider) rail.setAttribute("data-codex-model-slider-fallback", "");
|
|
1505
|
+
rail.setAttribute("role", "slider");
|
|
1506
|
+
rail.setAttribute("tabindex", "0");
|
|
1507
|
+
rail.setAttribute("aria-label", locale === "zh-CN" ? "推理强度" : "Reasoning effort");
|
|
1508
|
+
rail.setAttribute("aria-valuemin", "0");
|
|
1509
|
+
rail.setAttribute("aria-valuemax", String(efforts.length - 1));
|
|
1510
|
+
rail.setAttribute("aria-valuetext", effortLabel(efforts[selectedIndex]));
|
|
1511
|
+
if (!nativeSlider) {
|
|
1512
|
+
const track = document.createElement("div");
|
|
1513
|
+
track.setAttribute("data-codex-model-slider-track", "");
|
|
1514
|
+
const range = document.createElement("div");
|
|
1515
|
+
range.setAttribute("data-codex-model-slider-range", "");
|
|
1516
|
+
track.append(range);
|
|
1517
|
+
efforts.forEach((_effort, index) => {
|
|
1518
|
+
const tick = document.createElement("span");
|
|
1519
|
+
tick.setAttribute("data-codex-model-slider-tick", "");
|
|
1520
|
+
tick.style.left = `${efforts.length <= 1 ? 0 : (index / (efforts.length - 1)) * 100}%`;
|
|
1521
|
+
track.append(tick);
|
|
1522
|
+
});
|
|
1523
|
+
const thumb = document.createElement("span");
|
|
1524
|
+
thumb.setAttribute("data-codex-model-slider-thumb", "");
|
|
1525
|
+
rail.append(track, thumb);
|
|
1526
|
+
}
|
|
1527
|
+
setGenericSliderVisual(rail, selectedIndex, efforts.length);
|
|
1528
|
+
syncNativeFastParticles(rail, fastEnabled);
|
|
1529
|
+
setGenericSliderVisual(rail, selectedIndex, efforts.length);
|
|
1530
|
+
|
|
1531
|
+
let pendingIndex = selectedIndex;
|
|
1532
|
+
let dragging = false;
|
|
1533
|
+
let dragRect = null;
|
|
1534
|
+
let previewFrame = null;
|
|
1535
|
+
let pendingClientX = null;
|
|
1536
|
+
const indexAt = (clientX) => {
|
|
1537
|
+
const rect = dragRect ?? rail.getBoundingClientRect();
|
|
1538
|
+
const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / Math.max(1, rect.width)));
|
|
1539
|
+
return Math.round(ratio * (efforts.length - 1));
|
|
1540
|
+
};
|
|
1541
|
+
const preview = (clientX) => {
|
|
1542
|
+
pendingIndex = indexAt(clientX);
|
|
1543
|
+
setGenericSliderVisual(rail, pendingIndex, efforts.length);
|
|
1544
|
+
rail.setAttribute("aria-valuetext", effortLabel(efforts[pendingIndex]));
|
|
1545
|
+
};
|
|
1546
|
+
rail.addEventListener("pointerdown", (event) => {
|
|
1547
|
+
dragging = true;
|
|
1548
|
+
dragRect = rail.getBoundingClientRect();
|
|
1549
|
+
rail.setPointerCapture?.(event.pointerId);
|
|
1550
|
+
preview(event.clientX);
|
|
1551
|
+
});
|
|
1552
|
+
rail.addEventListener("pointermove", (event) => {
|
|
1553
|
+
if (!dragging) return;
|
|
1554
|
+
pendingClientX = event.clientX;
|
|
1555
|
+
if (previewFrame != null) return;
|
|
1556
|
+
previewFrame = requestAnimationFrame(() => {
|
|
1557
|
+
previewFrame = null;
|
|
1558
|
+
if (dragging && pendingClientX != null) preview(pendingClientX);
|
|
1559
|
+
});
|
|
1560
|
+
});
|
|
1561
|
+
rail.addEventListener("pointerup", (event) => {
|
|
1562
|
+
if (!dragging) return;
|
|
1563
|
+
dragging = false;
|
|
1564
|
+
if (previewFrame != null) cancelAnimationFrame(previewFrame);
|
|
1565
|
+
previewFrame = null;
|
|
1566
|
+
pendingClientX = null;
|
|
1567
|
+
preview(event.clientX);
|
|
1568
|
+
dragRect = null;
|
|
1569
|
+
selectNativeEffort(parentMenu, efforts[pendingIndex]);
|
|
1570
|
+
});
|
|
1571
|
+
rail.addEventListener("pointercancel", () => {
|
|
1572
|
+
dragging = false;
|
|
1573
|
+
if (previewFrame != null) cancelAnimationFrame(previewFrame);
|
|
1574
|
+
previewFrame = null;
|
|
1575
|
+
pendingClientX = null;
|
|
1576
|
+
dragRect = null;
|
|
1577
|
+
});
|
|
1578
|
+
rail.addEventListener("keydown", (event) => {
|
|
1579
|
+
if (!new Set(["ArrowLeft", "ArrowRight", "Home", "End"]).has(event.key)) return;
|
|
1580
|
+
event.preventDefault();
|
|
1581
|
+
if (event.key === "Home") pendingIndex = 0;
|
|
1582
|
+
else if (event.key === "End") pendingIndex = efforts.length - 1;
|
|
1583
|
+
else pendingIndex = Math.min(
|
|
1584
|
+
efforts.length - 1,
|
|
1585
|
+
Math.max(0, pendingIndex + (event.key === "ArrowRight" ? 1 : -1)),
|
|
1586
|
+
);
|
|
1587
|
+
setGenericSliderVisual(rail, pendingIndex, efforts.length);
|
|
1588
|
+
rail.setAttribute("aria-valuetext", effortLabel(efforts[pendingIndex]));
|
|
1589
|
+
selectNativeEffort(parentMenu, efforts[pendingIndex]);
|
|
1590
|
+
});
|
|
1591
|
+
const simpleView = document.createElement("div");
|
|
1592
|
+
simpleView.className = "_SimpleView_1ryrb_50";
|
|
1593
|
+
simpleView.append(nativeSlider ?? rail);
|
|
1594
|
+
shell.append(viewControls, simpleView);
|
|
1595
|
+
return shell;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
function enhanceGenericModelPicker(parentMenu, selector) {
|
|
1599
|
+
const identity = modelIdentity(nativeModelValue(parentMenu));
|
|
1600
|
+
const model = modelDefinitionFor(identity, selector);
|
|
1601
|
+
const efforts = model?.supportedReasoningLevels ?? [];
|
|
1602
|
+
if (!model || efforts.length < 2) return false;
|
|
1603
|
+
const selectedEffort = document.querySelector("[data-codex-intelligence-trigger]")
|
|
1604
|
+
?.getAttribute("data-selected-reasoning-effort") ?? model.defaultReasoningLevel ?? efforts[0].effort;
|
|
1605
|
+
const selectedServiceTier = nativeComposerModelController()?.selectedServiceTier;
|
|
1606
|
+
const serviceTierKey = selectedServiceTier?.id ?? selectedServiceTier ?? "standard";
|
|
1607
|
+
const key = `${identity.raw}:${selectedEffort}:${serviceTierKey}:${efforts.map((level) => level.effort).join(",")}`;
|
|
1608
|
+
const existing = parentMenu.querySelector(":scope > [data-codex-model-slider-generic]");
|
|
1609
|
+
const nativeTemplateReady = Boolean(
|
|
1610
|
+
parentMenu.firstElementChild
|
|
1611
|
+
?.querySelector("[data-model-picker-power-slider] [class*='_TickRail_'] > span"),
|
|
1612
|
+
);
|
|
1613
|
+
const nativeModel = nativeCatalogModelFor(identity);
|
|
1614
|
+
const templateWaitCount = Number(parentMenu.getAttribute("data-codex-model-slider-template-wait") ?? 0);
|
|
1615
|
+
if (nativeModel?.slug.toLowerCase().startsWith("gpt-") && !nativeTemplateReady && templateWaitCount < 3) {
|
|
1616
|
+
parentMenu.setAttribute("data-codex-model-slider-template-wait", String(templateWaitCount + 1));
|
|
1617
|
+
parentMenu.style.visibility = "hidden";
|
|
1618
|
+
requestAnimationFrame(queueEnsure);
|
|
1619
|
+
return true;
|
|
1620
|
+
}
|
|
1621
|
+
parentMenu.removeAttribute("data-codex-model-slider-template-wait");
|
|
1622
|
+
parentMenu.style.removeProperty("visibility");
|
|
1623
|
+
const canUpgradeFallback = Boolean(
|
|
1624
|
+
existing?.querySelector("[data-codex-model-slider-fallback]") && nativeTemplateReady,
|
|
1625
|
+
);
|
|
1626
|
+
if (existing?.getAttribute("data-codex-model-slider-generic") === key && !canUpgradeFallback) return true;
|
|
1627
|
+
existing?.remove();
|
|
1628
|
+
const nativeContainer = parentMenu.firstElementChild;
|
|
1629
|
+
if (!nativeContainer) return false;
|
|
1630
|
+
if (!modelSelectorGenericContainers.has(nativeContainer)) {
|
|
1631
|
+
modelSelectorGenericContainers.set(nativeContainer, nativeContainer.style.display);
|
|
1632
|
+
}
|
|
1633
|
+
parentMenu.setAttribute("data-codex-model-slider-generic-menu", "");
|
|
1634
|
+
nativeContainer.style.display = "none";
|
|
1635
|
+
const shell = createGenericModelSlider(parentMenu, selector, identity, model, selectedEffort);
|
|
1636
|
+
shell.setAttribute("data-codex-model-slider-generic", key);
|
|
1637
|
+
parentMenu.append(shell);
|
|
1638
|
+
return true;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
function enhanceNativeModelPicker(parentMenu, selector) {
|
|
1642
|
+
if (enhanceGenericModelPicker(parentMenu, selector)) return;
|
|
1643
|
+
const toggle = parentMenu.querySelector("[data-model-picker-view-toggle]");
|
|
1644
|
+
if (!toggle) {
|
|
1645
|
+
enhanceGenericModelPicker(parentMenu, selector);
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
parentMenu.querySelector(":scope > [data-codex-model-slider-generic]")?.remove();
|
|
1649
|
+
parentMenu.removeAttribute("data-codex-model-slider-generic-menu");
|
|
1650
|
+
const nativeContainer = parentMenu.firstElementChild;
|
|
1651
|
+
if (nativeContainer && modelSelectorGenericContainers.has(nativeContainer)) {
|
|
1652
|
+
nativeContainer.style.display = modelSelectorGenericContainers.get(nativeContainer);
|
|
1653
|
+
modelSelectorGenericContainers.delete(nativeContainer);
|
|
1654
|
+
}
|
|
1655
|
+
const raw = nativeModelValue(parentMenu);
|
|
1656
|
+
const textNode = firstVisibleTextNode(toggle);
|
|
1657
|
+
if (raw && textNode) {
|
|
1658
|
+
if (!modelSelectorOriginalToggleLabels.has(textNode)) {
|
|
1659
|
+
modelSelectorOriginalToggleLabels.set(textNode, textNode.textContent);
|
|
1660
|
+
}
|
|
1661
|
+
const label = modelDisplayLabel(modelIdentity(raw), selector);
|
|
1662
|
+
if (textNode.textContent !== label) textNode.textContent = label;
|
|
1663
|
+
}
|
|
1664
|
+
if (!toggle.hasAttribute("data-codex-model-slider-toggle")) {
|
|
1665
|
+
toggle.setAttribute("data-codex-model-slider-toggle", "");
|
|
1666
|
+
toggle.addEventListener("click", () => openNativeModelSubmenu(parentMenu));
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
function enhanceComposerModelTrigger(selector) {
|
|
1671
|
+
const trigger = document.querySelector("[data-codex-intelligence-trigger]");
|
|
1672
|
+
if (!trigger) return;
|
|
1673
|
+
if (modelSelectorObservedTrigger !== trigger) {
|
|
1674
|
+
modelSelectorTriggerObserver?.disconnect();
|
|
1675
|
+
modelSelectorObservedTrigger = trigger;
|
|
1676
|
+
modelSelectorTriggerObserver = new MutationObserver(queueEnsure);
|
|
1677
|
+
modelSelectorTriggerObserver.observe(trigger, { childList: true, subtree: true, characterData: true });
|
|
1678
|
+
}
|
|
1679
|
+
const nativeLabels = Array.from(trigger.querySelectorAll(
|
|
1680
|
+
'[class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label])',
|
|
1681
|
+
));
|
|
1682
|
+
if (!nativeLabels.length) return;
|
|
1683
|
+
const visibleValue = String(nativeLabels.at(-1)?.textContent ?? "").trim();
|
|
1684
|
+
const previousRaw = trigger.getAttribute("data-codex-model-slider-trigger-raw") ?? "";
|
|
1685
|
+
const previousLabel = previousRaw
|
|
1686
|
+
? modelDisplayLabel(modelIdentity(previousRaw), selector)
|
|
1687
|
+
: "";
|
|
1688
|
+
const raw = !previousRaw || visibleValue !== previousLabel ? visibleValue : previousRaw;
|
|
1689
|
+
if (!raw) return;
|
|
1690
|
+
const label = modelDisplayLabel(modelIdentity(raw), selector);
|
|
1691
|
+
trigger.setAttribute("data-codex-model-slider-trigger", "");
|
|
1692
|
+
trigger.setAttribute("data-codex-model-slider-trigger-raw", raw);
|
|
1693
|
+
for (const nativeLabel of nativeLabels) {
|
|
1694
|
+
let displayLabel = nativeLabel.nextElementSibling;
|
|
1695
|
+
if (!displayLabel?.hasAttribute("data-codex-model-slider-trigger-label")) {
|
|
1696
|
+
displayLabel = document.createElement("span");
|
|
1697
|
+
displayLabel.className = nativeLabel.className;
|
|
1698
|
+
displayLabel.setAttribute("data-codex-model-slider-trigger-label", "");
|
|
1699
|
+
nativeLabel.after(displayLabel);
|
|
1700
|
+
}
|
|
1701
|
+
if (displayLabel.textContent !== label) displayLabel.textContent = label;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
function ensureModelSelector() {
|
|
1706
|
+
const selector = modelSelectorFeature?.modelSelector;
|
|
1707
|
+
if (!selector) return;
|
|
1708
|
+
pruneModelSubmenuPositionObservers();
|
|
1709
|
+
if (!modelSelectorStyle.isConnected) document.head?.append(modelSelectorStyle);
|
|
1710
|
+
enhanceComposerModelTrigger(selector);
|
|
1711
|
+
const parentMenu = nativeModelPickerMenu();
|
|
1712
|
+
if (!parentMenu) {
|
|
1713
|
+
if (modelSelectorPendingReturn && !visibleBlockingDialog() && modelSelectorReturnTimer == null) {
|
|
1714
|
+
modelSelectorReturnTimer = setTimeout(() => {
|
|
1715
|
+
modelSelectorReturnTimer = null;
|
|
1716
|
+
returnToNativeModelSlider();
|
|
1717
|
+
}, 80);
|
|
1718
|
+
}
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
enhanceNativeModelPicker(parentMenu, selector);
|
|
1722
|
+
const submenu = nativeModelSubmenu(parentMenu);
|
|
1723
|
+
if (submenu) enhanceNativeModelSubmenu(submenu, selector);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
function restoreModelSelectorEnhancements() {
|
|
1727
|
+
clearTimeout(modelSelectorReturnTimer);
|
|
1728
|
+
clearTimeout(modelSelectorEffortTransactionTimer);
|
|
1729
|
+
modelSelectorTriggerObserver?.disconnect();
|
|
1730
|
+
for (const record of modelSelectorPositionObservers.values()) {
|
|
1731
|
+
record.observer.disconnect();
|
|
1732
|
+
record.resizeObserver.disconnect();
|
|
1733
|
+
}
|
|
1734
|
+
modelSelectorPositionObservers.clear();
|
|
1735
|
+
for (const frame of modelSelectorMenuSizeFrames.values()) cancelAnimationFrame(frame);
|
|
1736
|
+
modelSelectorMenuSizeFrames.clear();
|
|
1737
|
+
modelSelectorTriggerObserver = null;
|
|
1738
|
+
modelSelectorObservedTrigger = null;
|
|
1739
|
+
modelSelectorReturnTimer = null;
|
|
1740
|
+
modelSelectorEffortTransactionTimer = null;
|
|
1741
|
+
modelSelectorPendingOutsideDismiss = false;
|
|
1742
|
+
modelSelectorSuppressOutsidePointerSequence = false;
|
|
1743
|
+
modelSelectorPendingReturn = false;
|
|
1744
|
+
document.documentElement.removeAttribute("data-codex-model-slider-selecting-effort");
|
|
1745
|
+
for (const [item, html] of modelSelectorOriginalItems) {
|
|
1746
|
+
if (!item.isConnected) continue;
|
|
1747
|
+
item.innerHTML = html;
|
|
1748
|
+
item.style.removeProperty("display");
|
|
1749
|
+
for (const name of [
|
|
1750
|
+
"data-codex-model-slider-raw",
|
|
1751
|
+
"data-codex-model-slider-item",
|
|
1752
|
+
"data-codex-model-slider-order",
|
|
1753
|
+
"data-codex-model-slider-identity-source",
|
|
1754
|
+
"data-codex-model-slider-return-listener",
|
|
1755
|
+
]) item.removeAttribute(name);
|
|
1756
|
+
}
|
|
1757
|
+
for (const [node, value] of modelSelectorOriginalToggleLabels) {
|
|
1758
|
+
if (node.isConnected) node.textContent = value;
|
|
1759
|
+
}
|
|
1760
|
+
for (const [container, display] of modelSelectorGenericContainers) {
|
|
1761
|
+
if (container.isConnected) container.style.display = display;
|
|
1762
|
+
}
|
|
1763
|
+
document.querySelectorAll("[data-codex-model-slider-divider]")
|
|
1764
|
+
.forEach((element) => element.remove());
|
|
1765
|
+
document.querySelectorAll("[data-codex-model-slider-generic]").forEach((element) => element.remove());
|
|
1766
|
+
document.querySelectorAll("[data-codex-model-slider-generic-menu]")
|
|
1767
|
+
.forEach((element) => element.removeAttribute("data-codex-model-slider-generic-menu"));
|
|
1768
|
+
document.querySelectorAll("[data-codex-model-slider-menu]")
|
|
1769
|
+
.forEach((element) => element.removeAttribute("data-codex-model-slider-menu"));
|
|
1770
|
+
document.querySelectorAll("[data-codex-model-slider-trigger]").forEach((element) => {
|
|
1771
|
+
element.removeAttribute("data-codex-model-slider-trigger");
|
|
1772
|
+
element.removeAttribute("data-codex-model-slider-trigger-raw");
|
|
1773
|
+
});
|
|
1774
|
+
document.querySelectorAll("[data-codex-model-slider-trigger-label]").forEach((element) => element.remove());
|
|
1775
|
+
document.documentElement.removeAttribute("data-codex-model-slider-catalog-ready");
|
|
1776
|
+
modelSelectorStyle.remove();
|
|
1777
|
+
modelSelectorOriginalItems.clear();
|
|
1778
|
+
modelSelectorOriginalToggleLabels.clear();
|
|
1779
|
+
modelSelectorGenericContainers.clear();
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
function surfaceKey(featureId, kind, detailId = "") {
|
|
1783
|
+
return `${featureId}:${kind}:${detailId}`;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
function postSurfaceActive(record, active) {
|
|
1787
|
+
if (!record || record.kind !== "page") return;
|
|
1788
|
+
record.frame.contentWindow?.postMessage({
|
|
1789
|
+
type: "codex-personal:surface-active",
|
|
1790
|
+
active: Boolean(active),
|
|
1791
|
+
}, record.origin);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
function registerSurface(key, feature, kind, surfaceUrl, element, frame, detailId = null) {
|
|
1795
|
+
const record = {
|
|
1796
|
+
key,
|
|
1797
|
+
featureId: feature.id,
|
|
1798
|
+
kind,
|
|
1799
|
+
detailId,
|
|
1800
|
+
origin: new URL(surfaceUrl).origin,
|
|
1801
|
+
element,
|
|
1802
|
+
frame,
|
|
1803
|
+
ready: false,
|
|
1804
|
+
preferredHeight: null,
|
|
1805
|
+
};
|
|
1806
|
+
surfaceRecords.set(key, record);
|
|
1807
|
+
frame.addEventListener("load", () => {
|
|
1808
|
+
queueTheme();
|
|
1809
|
+
postSurfaceActive(record, activePageFeatureId === record.featureId && !record.element.hidden);
|
|
1810
|
+
});
|
|
1811
|
+
return record;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
function reloadSurfaceIfUnready(key, surfaceUrl) {
|
|
1815
|
+
const record = surfaceRecords.get(key);
|
|
1816
|
+
if (!record || record.ready) return false;
|
|
1817
|
+
record.frame.src = surfaceUrl;
|
|
1818
|
+
return true;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
function removeSurfaceRecord(key) {
|
|
1822
|
+
if (activePromptPreview?.recordKey === key) closePromptPreview({ immediate: true });
|
|
1823
|
+
surfaceRecords.delete(key);
|
|
1824
|
+
pendingHostActions.forEach((pending, requestId) => {
|
|
1825
|
+
if (pending.recordKey !== key) return;
|
|
1826
|
+
pendingHostActions.delete(requestId);
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
function resolvedTheme() {
|
|
1831
|
+
const root = document.documentElement;
|
|
1832
|
+
const style = getComputedStyle(root);
|
|
1833
|
+
const tokens = Object.fromEntries(
|
|
1834
|
+
themeTokenNames
|
|
1835
|
+
.map((name) => [name, style.getPropertyValue(name).trim()])
|
|
1836
|
+
.filter(([, value]) => value),
|
|
1837
|
+
);
|
|
1838
|
+
const mode = root.classList.contains("electron-dark") || style.colorScheme === "dark"
|
|
1839
|
+
? "dark"
|
|
1840
|
+
: "light";
|
|
1841
|
+
return { root, style, tokens, mode };
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
function sendTheme() {
|
|
1845
|
+
themeQueued = false;
|
|
1846
|
+
const { style, tokens, mode } = resolvedTheme();
|
|
1847
|
+
const radiusToken = tokens["--radius-3xl-base"] ?? "20px";
|
|
1848
|
+
const radiusBase = Number.parseFloat(radiusToken) || 20;
|
|
1849
|
+
const radiusUnit = radiusToken.match(/[a-z%]+$/i)?.[0] ?? "px";
|
|
1850
|
+
const radiusScale = Number.parseFloat(tokens["--codex-corner-radius-scale"] ?? "1.25") || 1.25;
|
|
1851
|
+
const panelRadius = `${radiusBase * radiusScale}${radiusUnit}`;
|
|
1852
|
+
const panelCornerShape = tokens["--codex-corner-shape"] ?? "superellipse(1.5)";
|
|
1853
|
+
const dialogRadius = tokens["--radius-2xl"] ?? "20px";
|
|
1854
|
+
const dialogShadow = "0 16px 32px -8px rgba(0,0,0,.19)";
|
|
1855
|
+
const surfaceTokens = {
|
|
1856
|
+
...tokens,
|
|
1857
|
+
"--codex-summary-section-title-font-size": "14px",
|
|
1858
|
+
"--codex-summary-section-title-line-height": "21px",
|
|
1859
|
+
"--codex-summary-section-title-font-weight": tokens["--font-weight-normal"] ?? "400",
|
|
1860
|
+
"--codex-summary-row-font-size": "16px",
|
|
1861
|
+
"--codex-summary-row-line-height": "24px",
|
|
1862
|
+
"--codex-summary-label-font-size": tokens["--text-base"] ?? "14px",
|
|
1863
|
+
"--codex-summary-label-line-height": tokens["--text-base--line-height"] ?? "1.5",
|
|
1864
|
+
"--codex-summary-font-weight": tokens["--vscode-font-weight"] ?? (style.fontWeight || "445"),
|
|
1865
|
+
"--codex-summary-icon-size": "18px",
|
|
1866
|
+
"--codex-summary-icon-stroke-width": "1.5px",
|
|
1867
|
+
"--codex-summary-panel-radius": panelRadius,
|
|
1868
|
+
"--codex-summary-panel-corner-shape": panelCornerShape,
|
|
1869
|
+
"--codex-dialog-radius": dialogRadius,
|
|
1870
|
+
"--codex-dialog-corner-shape": "round",
|
|
1871
|
+
"--codex-dialog-shadow": dialogShadow,
|
|
1872
|
+
"--codex-summary-row-radius": tokens["--radius-sm"] ?? "7.5px",
|
|
1873
|
+
"--codex-summary-control-radius": tokens["--radius-md"] ?? "10px",
|
|
1874
|
+
"--codex-summary-section-title-color": tokens["--color-token-text-tertiary"] ?? "rgba(13,13,13,.484)",
|
|
1875
|
+
"--codex-summary-icon-primary": tokens["--color-icon-primary"] ?? tokens["--color-token-foreground"] ?? style.color,
|
|
1876
|
+
"--codex-summary-icon-secondary": tokens["--color-icon-secondary"] ?? tokens["--color-token-text-secondary"] ?? style.color,
|
|
1877
|
+
"--codex-summary-icon-tertiary": tokens["--color-icon-tertiary"] ?? tokens["--color-token-text-tertiary"] ?? style.color,
|
|
1878
|
+
};
|
|
1879
|
+
for (const record of surfaceRecords.values()) {
|
|
1880
|
+
if (!record.element.isConnected) continue;
|
|
1881
|
+
if (record.kind === "pinned") {
|
|
1882
|
+
record.element.style.borderRadius = panelRadius;
|
|
1883
|
+
record.element.style.setProperty("corner-shape", panelCornerShape);
|
|
1884
|
+
record.element.style.boxShadow = tokens["--elevation-prominent"]
|
|
1885
|
+
?? "0 0 0 .5px rgba(13,13,13,.11),0 3px 7.5px rgba(0,0,0,.04),0 0 20px rgba(0,0,0,.05)";
|
|
1886
|
+
}
|
|
1887
|
+
if (record.kind === "modal") {
|
|
1888
|
+
record.element.style.borderRadius = dialogRadius;
|
|
1889
|
+
record.element.style.setProperty("corner-shape", "round");
|
|
1890
|
+
record.element.style.boxShadow = dialogShadow;
|
|
1891
|
+
}
|
|
1892
|
+
record.element.style.background = new Set(["pinned", "modal"]).has(record.kind)
|
|
1893
|
+
? tokens["--color-token-dropdown-background"]
|
|
1894
|
+
?? tokens["--color-token-main-surface-primary"]
|
|
1895
|
+
?? style.backgroundColor
|
|
1896
|
+
: tokens["--color-token-main-surface-primary"] ?? style.backgroundColor;
|
|
1897
|
+
record.frame.contentWindow?.postMessage(
|
|
1898
|
+
{ type: "codex-personal:theme", theme: mode, mode, locale, tokens: surfaceTokens },
|
|
1899
|
+
record.origin,
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
function queueTheme() {
|
|
1905
|
+
if (themeQueued) return;
|
|
1906
|
+
themeQueued = true;
|
|
1907
|
+
queueMicrotask(sendTheme);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
function sidebarRight() {
|
|
1911
|
+
const sidebar = document.querySelector("[data-app-action-sidebar-scroll]");
|
|
1912
|
+
return sidebar?.getBoundingClientRect().right ?? 0;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
function updatePageSurfacePositions() {
|
|
1916
|
+
const left = `${Math.max(0, sidebarRight())}px`;
|
|
1917
|
+
for (const surface of pageSurfaces.values()) surface.style.left = left;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
function threadIdentity() {
|
|
1921
|
+
const selected = document.querySelector('[data-app-action-sidebar-thread-selected="true"]');
|
|
1922
|
+
const id = selected?.getAttribute("data-app-action-sidebar-thread-id");
|
|
1923
|
+
const project = selected?.closest?.("[data-app-action-sidebar-project-id]")
|
|
1924
|
+
?.getAttribute("data-app-action-sidebar-project-id");
|
|
1925
|
+
return `${project ?? ""}:${id ?? ""}:${location.pathname}:${location.search}`;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
function checkThreadChange() {
|
|
1929
|
+
const next = threadIdentity();
|
|
1930
|
+
if (currentThread === null) {
|
|
1931
|
+
currentThread = next;
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
if (next === currentThread) return;
|
|
1935
|
+
currentThread = next;
|
|
1936
|
+
savedComposerRange = null;
|
|
1937
|
+
savedComposerThread = null;
|
|
1938
|
+
hidePinnedSurfaces();
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
function currentComposer() {
|
|
1942
|
+
return document.querySelector('[data-codex-composer="true"][contenteditable="true"]');
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
function rememberComposerSelection() {
|
|
1946
|
+
const composer = currentComposer();
|
|
1947
|
+
const selection = document.getSelection();
|
|
1948
|
+
if (!composer || !selection?.rangeCount) return;
|
|
1949
|
+
const range = selection.getRangeAt(0);
|
|
1950
|
+
if (!composer.contains(range.startContainer) || !composer.contains(range.endContainer)) return;
|
|
1951
|
+
savedComposerRange = range.cloneRange();
|
|
1952
|
+
savedComposerThread = threadIdentity();
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
function insertComposerText(text) {
|
|
1956
|
+
if (typeof text !== "string" || !text) throw new Error("Text is required");
|
|
1957
|
+
const composer = currentComposer();
|
|
1958
|
+
if (!composer) throw new Error("The active Composer was not found");
|
|
1959
|
+
const selection = document.getSelection();
|
|
1960
|
+
let fallback = true;
|
|
1961
|
+
if (
|
|
1962
|
+
savedComposerRange
|
|
1963
|
+
&& savedComposerThread === threadIdentity()
|
|
1964
|
+
&& savedComposerRange.startContainer?.isConnected
|
|
1965
|
+
&& composer.contains(savedComposerRange.startContainer)
|
|
1966
|
+
&& composer.contains(savedComposerRange.endContainer)
|
|
1967
|
+
) {
|
|
1968
|
+
selection.removeAllRanges();
|
|
1969
|
+
selection.addRange(savedComposerRange);
|
|
1970
|
+
fallback = false;
|
|
1971
|
+
} else {
|
|
1972
|
+
const range = document.createRange();
|
|
1973
|
+
range.selectNodeContents(composer);
|
|
1974
|
+
range.collapse(false);
|
|
1975
|
+
selection.removeAllRanges();
|
|
1976
|
+
selection.addRange(range);
|
|
1977
|
+
}
|
|
1978
|
+
composer.focus({ preventScroll: true });
|
|
1979
|
+
const existing = composer.innerText.replace(/\n+$/, "");
|
|
1980
|
+
const insertedText = fallback && existing ? `\n${text}` : text;
|
|
1981
|
+
const inserted = document.execCommand("insertText", false, insertedText);
|
|
1982
|
+
if (!inserted) throw new Error("Codex rejected Composer insertion");
|
|
1983
|
+
rememberComposerSelection();
|
|
1984
|
+
return { mode: "inserted", fallback };
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
function suppressNativeSelections() {
|
|
1988
|
+
const sidebar = document.querySelector("[data-app-action-sidebar-scroll]");
|
|
1989
|
+
if (!sidebar) return;
|
|
1990
|
+
const alreadySuppressed = new Set(suppressedSelections.map(({ element }) => element));
|
|
1991
|
+
const selected = new Set([
|
|
1992
|
+
...sidebar.querySelectorAll('[aria-current="page"]'),
|
|
1993
|
+
...sidebar.querySelectorAll('[data-app-action-sidebar-thread-selected="true"]'),
|
|
1994
|
+
]);
|
|
1995
|
+
for (const element of selected) {
|
|
1996
|
+
if (element.hasAttribute(entryMarker) || alreadySuppressed.has(element)) continue;
|
|
1997
|
+
suppressedSelections.push({
|
|
1998
|
+
element,
|
|
1999
|
+
ariaCurrent: element.getAttribute("aria-current"),
|
|
2000
|
+
threadSelected: element.getAttribute("data-app-action-sidebar-thread-selected"),
|
|
2001
|
+
hadActiveClass: element.classList.contains("bg-token-list-hover-background"),
|
|
2002
|
+
hadHoverClass: element.classList.contains("hover:bg-token-list-hover-background"),
|
|
2003
|
+
});
|
|
2004
|
+
element.removeAttribute("aria-current");
|
|
2005
|
+
element.removeAttribute("data-app-action-sidebar-thread-selected");
|
|
2006
|
+
element.classList.remove("bg-token-list-hover-background");
|
|
2007
|
+
element.classList.add("hover:bg-token-list-hover-background");
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
function restoreNativeSelections() {
|
|
2012
|
+
for (const selection of suppressedSelections) {
|
|
2013
|
+
const { element } = selection;
|
|
2014
|
+
if (!element.isConnected) continue;
|
|
2015
|
+
if (selection.ariaCurrent !== null) element.setAttribute("aria-current", selection.ariaCurrent);
|
|
2016
|
+
if (selection.threadSelected !== null) {
|
|
2017
|
+
element.setAttribute("data-app-action-sidebar-thread-selected", selection.threadSelected);
|
|
2018
|
+
}
|
|
2019
|
+
element.classList.toggle("bg-token-list-hover-background", selection.hadActiveClass);
|
|
2020
|
+
element.classList.toggle("hover:bg-token-list-hover-background", selection.hadHoverClass);
|
|
2021
|
+
}
|
|
2022
|
+
suppressedSelections = [];
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
function updateSidebarSelectedState() {
|
|
2026
|
+
for (const feature of sidebarFeatures) {
|
|
2027
|
+
const entry = findEntry(feature.id);
|
|
2028
|
+
if (!entry) continue;
|
|
2029
|
+
const selected = feature.id === activePageFeatureId;
|
|
2030
|
+
entry.classList.toggle("bg-token-list-hover-background", selected);
|
|
2031
|
+
entry.classList.toggle("hover:bg-token-list-hover-background", !selected);
|
|
2032
|
+
if (selected) entry.setAttribute("aria-current", "page");
|
|
2033
|
+
else entry.removeAttribute("aria-current");
|
|
2034
|
+
}
|
|
2035
|
+
if (activePageFeatureId) suppressNativeSelections();
|
|
2036
|
+
else restoreNativeSelections();
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
function updateToolbarState() {
|
|
2040
|
+
const displayMode = summaryDisplayMode();
|
|
2041
|
+
for (const feature of toolbarFeatures) {
|
|
2042
|
+
const entry = findEntry(feature.id);
|
|
2043
|
+
if (!entry) continue;
|
|
2044
|
+
const open = visibleSummarySurface(feature.id, displayMode) !== null;
|
|
2045
|
+
const label = summaryButtonLabel(feature, displayMode);
|
|
2046
|
+
const pressed = String(open);
|
|
2047
|
+
const state = open ? "open" : "closed";
|
|
2048
|
+
if (entry.getAttribute("aria-label") !== label) entry.setAttribute("aria-label", label);
|
|
2049
|
+
if (entry.getAttribute("title") !== label) entry.setAttribute("title", label);
|
|
2050
|
+
if (entry.getAttribute("aria-pressed") !== pressed) entry.setAttribute("aria-pressed", pressed);
|
|
2051
|
+
if (entry.getAttribute("aria-expanded") !== pressed) entry.setAttribute("aria-expanded", pressed);
|
|
2052
|
+
if (entry.getAttribute("data-state") !== state) entry.setAttribute("data-state", state);
|
|
2053
|
+
entry.setAttribute("data-codex-personal-summary-mode", displayMode);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function createFrame(surfaceUrl, title) {
|
|
2058
|
+
const frame = document.createElement("iframe");
|
|
2059
|
+
frame.src = surfaceUrl;
|
|
2060
|
+
frame.title = title;
|
|
2061
|
+
frame.allow = "clipboard-write";
|
|
2062
|
+
frame.style.cssText = "width:100%;height:100%;border:0;display:block;background:transparent";
|
|
2063
|
+
return frame;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
function createPageSurface(feature) {
|
|
2067
|
+
const surface = document.createElement("section");
|
|
2068
|
+
surface.setAttribute(pageSurfaceMarker, feature.id);
|
|
2069
|
+
surface.style.cssText = [
|
|
2070
|
+
"position:fixed",
|
|
2071
|
+
"top:0",
|
|
2072
|
+
"right:0",
|
|
2073
|
+
"bottom:0",
|
|
2074
|
+
"z-index:40",
|
|
2075
|
+
"background:transparent",
|
|
2076
|
+
].join(";");
|
|
2077
|
+
const frame = createFrame(feature.surfaceUrl, feature.label);
|
|
2078
|
+
surface.append(frame);
|
|
2079
|
+
document.body.append(surface);
|
|
2080
|
+
pageSurfaces.set(feature.id, surface);
|
|
2081
|
+
registerSurface(surfaceKey(feature.id, "page"), feature, "page", feature.surfaceUrl, surface, frame);
|
|
2082
|
+
return surface;
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
function showPageSurface(featureId) {
|
|
2086
|
+
const feature = featureById.get(featureId);
|
|
2087
|
+
if (!feature) return;
|
|
2088
|
+
hidePinnedSurfaces();
|
|
2089
|
+
for (const [otherFeatureId, surface] of pageSurfaces) {
|
|
2090
|
+
if (otherFeatureId === featureId) continue;
|
|
2091
|
+
surface.hidden = true;
|
|
2092
|
+
const record = surfaceRecords.get(surfaceKey(otherFeatureId, "page"));
|
|
2093
|
+
postSurfaceActive(record, false);
|
|
2094
|
+
}
|
|
2095
|
+
const existing = pageSurfaces.get(featureId);
|
|
2096
|
+
const surface = existing ?? createPageSurface(feature);
|
|
2097
|
+
if (existing) reloadSurfaceIfUnready(surfaceKey(feature.id, "page"), feature.surfaceUrl);
|
|
2098
|
+
surface.hidden = false;
|
|
2099
|
+
activePageFeatureId = featureId;
|
|
2100
|
+
const record = surfaceRecords.get(surfaceKey(feature.id, "page"));
|
|
2101
|
+
postSurfaceActive(record, true);
|
|
2102
|
+
updatePageSurfacePositions();
|
|
2103
|
+
updateSidebarSelectedState();
|
|
2104
|
+
queueTheme();
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
function hidePageSurfaces() {
|
|
2108
|
+
for (const [featureId, surface] of pageSurfaces) {
|
|
2109
|
+
surface.hidden = true;
|
|
2110
|
+
const record = surfaceRecords.get(surfaceKey(featureId, "page"));
|
|
2111
|
+
postSurfaceActive(record, false);
|
|
2112
|
+
}
|
|
2113
|
+
activePageFeatureId = null;
|
|
2114
|
+
updateSidebarSelectedState();
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
function closeNativeSummary() {
|
|
2118
|
+
const toggle = nativeSummaryButton();
|
|
2119
|
+
if (!toggle) return;
|
|
2120
|
+
if (toggle.getAttribute("aria-expanded") === "true" || toggle.getAttribute("aria-pressed") === "true") {
|
|
2121
|
+
toggle.click();
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
function threadScrollContent() {
|
|
2126
|
+
const scrollContainer = mainContentViewport()?.querySelector(".thread-scroll-container");
|
|
2127
|
+
return scrollContainer?.firstElementChild instanceof HTMLElement
|
|
2128
|
+
? scrollContainer.firstElementChild
|
|
2129
|
+
: null;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
function restoreThreadContentShift() {
|
|
2133
|
+
if (!shiftedThreadContent || !shiftedThreadOriginal) return;
|
|
2134
|
+
const { element, position, left, transition } = shiftedThreadOriginal;
|
|
2135
|
+
if (element.isConnected) {
|
|
2136
|
+
element.style.setProperty("position", position.value, position.priority);
|
|
2137
|
+
element.style.setProperty("left", left.value, left.priority);
|
|
2138
|
+
element.style.setProperty("transition", transition.value, transition.priority);
|
|
2139
|
+
element.removeAttribute("data-codex-personal-summary-shift");
|
|
2140
|
+
}
|
|
2141
|
+
shiftedThreadContent = null;
|
|
2142
|
+
shiftedThreadOriginal = null;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
function applyThreadContentShift(value) {
|
|
2146
|
+
if (value === 0) {
|
|
2147
|
+
restoreThreadContentShift();
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
if (!threadShiftStyle.isConnected) {
|
|
2151
|
+
(document.head ?? document.documentElement)?.append(threadShiftStyle);
|
|
2152
|
+
}
|
|
2153
|
+
const content = threadScrollContent();
|
|
2154
|
+
if (!content) {
|
|
2155
|
+
restoreThreadContentShift();
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
if (shiftedThreadContent !== content) {
|
|
2159
|
+
restoreThreadContentShift();
|
|
2160
|
+
shiftedThreadContent = content;
|
|
2161
|
+
shiftedThreadOriginal = {
|
|
2162
|
+
element: content,
|
|
2163
|
+
position: {
|
|
2164
|
+
value: content.style.getPropertyValue("position"),
|
|
2165
|
+
priority: content.style.getPropertyPriority("position"),
|
|
2166
|
+
},
|
|
2167
|
+
left: {
|
|
2168
|
+
value: content.style.getPropertyValue("left"),
|
|
2169
|
+
priority: content.style.getPropertyPriority("left"),
|
|
2170
|
+
},
|
|
2171
|
+
transition: {
|
|
2172
|
+
value: content.style.getPropertyValue("transition"),
|
|
2173
|
+
priority: content.style.getPropertyPriority("transition"),
|
|
2174
|
+
},
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
const duration = reducedMotion() ? 0 : 300;
|
|
2178
|
+
content.style.setProperty("transition", duration
|
|
2179
|
+
? "left 300ms cubic-bezier(.2,.8,.2,1)"
|
|
2180
|
+
: "none", "important");
|
|
2181
|
+
content.style.setProperty("position", "relative", "important");
|
|
2182
|
+
content.style.setProperty("left", `${value}px`, "important");
|
|
2183
|
+
content.setAttribute("data-codex-personal-summary-shift", String(value));
|
|
2184
|
+
if (duration) window.setTimeout(() => {
|
|
2185
|
+
if (content.getAttribute("data-codex-personal-summary-shift") !== String(value)) return;
|
|
2186
|
+
content.style.setProperty("transition", "none", "important");
|
|
2187
|
+
content.style.setProperty("left", `${value}px`, "important");
|
|
2188
|
+
}, duration + 50);
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
function setSummarySurfaceVisible(surface, visible, mode) {
|
|
2192
|
+
const wasVisible = surface.dataset.codexPersonalVisible === "true";
|
|
2193
|
+
const previousMode = surface.dataset.codexPersonalMode;
|
|
2194
|
+
const duration = reducedMotion() ? 0 : 300;
|
|
2195
|
+
surface.dataset.codexPersonalVisible = String(visible);
|
|
2196
|
+
surface.dataset.codexPersonalMode = mode;
|
|
2197
|
+
surface.style.transition = duration
|
|
2198
|
+
? "opacity 180ms ease, transform 300ms cubic-bezier(.2,.8,.2,1)"
|
|
2199
|
+
: "none";
|
|
2200
|
+
if (!visible) {
|
|
2201
|
+
closePromptPreview({ immediate: true });
|
|
2202
|
+
surface.style.pointerEvents = "none";
|
|
2203
|
+
surface.style.opacity = "0";
|
|
2204
|
+
surface.style.transform = mode === "overlay"
|
|
2205
|
+
? "translateY(-4px) scale(.98)"
|
|
2206
|
+
: "translateX(100%) scale(.8)";
|
|
2207
|
+
const transitionId = `${performance.now()}-${Math.random()}`;
|
|
2208
|
+
surface.dataset.codexPersonalTransition = transitionId;
|
|
2209
|
+
if (duration === 0) surface.hidden = true;
|
|
2210
|
+
else window.setTimeout(() => {
|
|
2211
|
+
if (
|
|
2212
|
+
surface.dataset.codexPersonalTransition === transitionId
|
|
2213
|
+
&& surface.dataset.codexPersonalVisible !== "true"
|
|
2214
|
+
) surface.hidden = true;
|
|
2215
|
+
}, duration);
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
surface.dataset.codexPersonalTransition = "visible";
|
|
2220
|
+
surface.hidden = false;
|
|
2221
|
+
surface.style.pointerEvents = "auto";
|
|
2222
|
+
if (!wasVisible || previousMode !== mode) {
|
|
2223
|
+
surface.style.opacity = "0";
|
|
2224
|
+
surface.style.transform = mode === "overlay"
|
|
2225
|
+
? "translateY(-4px) scale(.98)"
|
|
2226
|
+
: "translateX(100%) scale(.8)";
|
|
2227
|
+
const reveal = () => {
|
|
2228
|
+
if (surface.dataset.codexPersonalVisible !== "true") return;
|
|
2229
|
+
surface.style.opacity = "1";
|
|
2230
|
+
surface.style.transform = "translate(0, 0) scale(1)";
|
|
2231
|
+
};
|
|
2232
|
+
requestAnimationFrame(reveal);
|
|
2233
|
+
window.setTimeout(reveal, 50);
|
|
2234
|
+
if (duration) window.setTimeout(() => {
|
|
2235
|
+
if (surface.dataset.codexPersonalVisible !== "true") return;
|
|
2236
|
+
surface.style.transition = "none";
|
|
2237
|
+
surface.style.opacity = "1";
|
|
2238
|
+
surface.style.transform = "translate(0, 0) scale(1)";
|
|
2239
|
+
}, duration + 50);
|
|
2240
|
+
} else {
|
|
2241
|
+
surface.style.opacity = "1";
|
|
2242
|
+
surface.style.transform = "translate(0, 0) scale(1)";
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
function updatePinnedPosition(featureId) {
|
|
2247
|
+
const surface = pinnedSurfaces.get(featureId);
|
|
2248
|
+
const entry = findEntry(featureId);
|
|
2249
|
+
const feature = featureById.get(featureId);
|
|
2250
|
+
if (!surface || !entry || !feature) return;
|
|
2251
|
+
const layout = config.summaryLayout;
|
|
2252
|
+
const displayMode = summaryDisplayMode();
|
|
2253
|
+
const visibleSurface = visibleSummarySurface(featureId, displayMode);
|
|
2254
|
+
if (!visibleSurface) {
|
|
2255
|
+
setSummarySurfaceVisible(surface, false, displayMode);
|
|
2256
|
+
if (activePinnedFeatureId === featureId) applyThreadContentShift(0);
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
const target = mainContentTargetGeometry();
|
|
2261
|
+
const entryRect = entry.getBoundingClientRect();
|
|
2262
|
+
const scrollRect = mainContentViewport()?.querySelector(".thread-scroll-container")
|
|
2263
|
+
?.getBoundingClientRect();
|
|
2264
|
+
const top = displayMode === "overlay"
|
|
2265
|
+
? entryRect.bottom + layout.popoverOffset
|
|
2266
|
+
: (scrollRect?.top ?? 46) + layout.verticalInset;
|
|
2267
|
+
const right = displayMode === "overlay"
|
|
2268
|
+
? Math.max(layout.popoverOffset, innerWidth - entryRect.right)
|
|
2269
|
+
: Math.max(layout.panelInset, innerWidth - target.right + layout.panelInset);
|
|
2270
|
+
const bottomInset = displayMode === "overlay"
|
|
2271
|
+
? layout.popoverBottomInset
|
|
2272
|
+
: layout.verticalInset;
|
|
2273
|
+
const available = Math.max(120, innerHeight - top - bottomInset);
|
|
2274
|
+
const record = surfaceRecords.get(surfaceKey(featureId, "pinned"));
|
|
2275
|
+
const height = Math.min(record?.preferredHeight ?? 620, available);
|
|
2276
|
+
Object.assign(surface.style, {
|
|
2277
|
+
top: `${top}px`,
|
|
2278
|
+
right: `${right}px`,
|
|
2279
|
+
width: `${layout.panelWidth}px`,
|
|
2280
|
+
height: `${height}px`,
|
|
2281
|
+
maxHeight: `${available}px`,
|
|
2282
|
+
});
|
|
2283
|
+
surface.setAttribute("data-codex-personal-summary-presentation", displayMode);
|
|
2284
|
+
setSummarySurfaceVisible(surface, true, displayMode);
|
|
2285
|
+
|
|
2286
|
+
const shift = displayMode === "shift"
|
|
2287
|
+
? -(layout.panelWidth + layout.panelInset) / 2
|
|
2288
|
+
: 0;
|
|
2289
|
+
applyThreadContentShift(shift);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
function createPinnedSurface(feature) {
|
|
2293
|
+
const definition = feature.pinnedSummary;
|
|
2294
|
+
const surface = document.createElement("section");
|
|
2295
|
+
surface.setAttribute(pinnedSurfaceMarker, feature.id);
|
|
2296
|
+
surface.setAttribute("role", "dialog");
|
|
2297
|
+
surface.setAttribute("aria-label", definition.label ?? feature.label);
|
|
2298
|
+
surface.hidden = true;
|
|
2299
|
+
surface.style.cssText = [
|
|
2300
|
+
"position:fixed",
|
|
2301
|
+
"z-index:40",
|
|
2302
|
+
"overflow:hidden",
|
|
2303
|
+
"border-radius:25px",
|
|
2304
|
+
"corner-shape:var(--codex-corner-shape,superellipse(1.5))",
|
|
2305
|
+
"background:var(--color-token-dropdown-background,var(--color-token-main-surface-primary))",
|
|
2306
|
+
"box-shadow:var(--elevation-prominent,0 0 0 .5px rgba(13,13,13,.11),0 3px 7.5px rgba(0,0,0,.04),0 0 20px rgba(0,0,0,.05))",
|
|
2307
|
+
"transform-origin:top right",
|
|
2308
|
+
"opacity:0",
|
|
2309
|
+
"pointer-events:none",
|
|
2310
|
+
"will-change:transform,opacity",
|
|
2311
|
+
].join(";");
|
|
2312
|
+
const frame = createFrame(definition.surfaceUrl, definition.label ?? feature.label);
|
|
2313
|
+
surface.append(frame);
|
|
2314
|
+
document.body.append(surface);
|
|
2315
|
+
pinnedSurfaces.set(feature.id, surface);
|
|
2316
|
+
registerSurface(
|
|
2317
|
+
surfaceKey(feature.id, "pinned"),
|
|
2318
|
+
feature,
|
|
2319
|
+
"pinned",
|
|
2320
|
+
definition.surfaceUrl,
|
|
2321
|
+
surface,
|
|
2322
|
+
frame,
|
|
2323
|
+
);
|
|
2324
|
+
return surface;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
function closePromptPreview({ immediate = false } = {}) {
|
|
2328
|
+
clearTimeout(promptPreviewDismissTimer);
|
|
2329
|
+
promptPreviewDismissTimer = null;
|
|
2330
|
+
const current = activePromptPreview;
|
|
2331
|
+
if (!current) return false;
|
|
2332
|
+
activePromptPreview = null;
|
|
2333
|
+
const remove = () => current.element.remove();
|
|
2334
|
+
if (immediate || reducedMotion()) remove();
|
|
2335
|
+
else {
|
|
2336
|
+
current.element.style.pointerEvents = "none";
|
|
2337
|
+
current.element.style.opacity = "0";
|
|
2338
|
+
window.setTimeout(remove, 80);
|
|
2339
|
+
}
|
|
2340
|
+
return true;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
function requestPromptPreviewClose(record) {
|
|
2344
|
+
if (!activePromptPreview || activePromptPreview.recordKey !== record.key) return false;
|
|
2345
|
+
clearTimeout(promptPreviewDismissTimer);
|
|
2346
|
+
promptPreviewDismissTimer = window.setTimeout(() => {
|
|
2347
|
+
if (!activePromptPreview || activePromptPreview.recordKey !== record.key) return;
|
|
2348
|
+
if (activePromptPreview.element.matches(":hover")) return;
|
|
2349
|
+
closePromptPreview();
|
|
2350
|
+
}, 120);
|
|
2351
|
+
return true;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
function showPromptPreview(record, payload = {}) {
|
|
2355
|
+
if (record.kind !== "pinned" || record.element.hidden) return false;
|
|
2356
|
+
const body = String(payload.prompt?.body ?? "");
|
|
2357
|
+
if (!body.trim()) return false;
|
|
2358
|
+
const title = String(payload.prompt?.title ?? "").trim();
|
|
2359
|
+
const surfaceLeft = record.element.offsetLeft;
|
|
2360
|
+
const surfaceTop = record.element.offsetTop;
|
|
2361
|
+
const anchorTop = Number(payload.anchor?.top);
|
|
2362
|
+
if (!Number.isFinite(anchorTop) || record.element.offsetWidth <= 0 || record.element.offsetHeight <= 0) return false;
|
|
2363
|
+
|
|
2364
|
+
closePromptPreview({ immediate: true });
|
|
2365
|
+
const preview = document.createElement("aside");
|
|
2366
|
+
preview.setAttribute(promptPreviewMarker, record.featureId);
|
|
2367
|
+
preview.setAttribute("role", "tooltip");
|
|
2368
|
+
preview.setAttribute("aria-label", locale === "zh-CN" ? "提示词预览" : "Prompt preview");
|
|
2369
|
+
if (payload.prompt?.id) preview.setAttribute("data-codex-personal-prompt-id", String(payload.prompt.id));
|
|
2370
|
+
preview.style.cssText = [
|
|
2371
|
+
"position:fixed",
|
|
2372
|
+
"z-index:41",
|
|
2373
|
+
"box-sizing:border-box",
|
|
2374
|
+
"display:flex",
|
|
2375
|
+
"flex-direction:column",
|
|
2376
|
+
"gap:8px",
|
|
2377
|
+
"max-height:min(420px,calc(100vh - 24px))",
|
|
2378
|
+
"overflow:hidden",
|
|
2379
|
+
"padding:14px 16px 15px",
|
|
2380
|
+
"border:.5px solid var(--color-token-border,var(--color-border,rgba(13,13,13,.11)))",
|
|
2381
|
+
"border-radius:16px",
|
|
2382
|
+
"corner-shape:var(--codex-corner-shape,superellipse(1.5))",
|
|
2383
|
+
"color:var(--color-token-foreground,var(--color-text-foreground,#0d0d0d))",
|
|
2384
|
+
"background:var(--color-token-dropdown-background,var(--color-token-main-surface-primary,#fff))",
|
|
2385
|
+
"box-shadow:var(--elevation-prominent,0 0 0 .5px rgba(13,13,13,.08),0 8px 24px rgba(0,0,0,.10))",
|
|
2386
|
+
"font-family:var(--font-sans-default,ui-sans-serif,system-ui,sans-serif)",
|
|
2387
|
+
"pointer-events:auto",
|
|
2388
|
+
"transition:opacity 80ms ease",
|
|
2389
|
+
].join(";");
|
|
2390
|
+
|
|
2391
|
+
if (title) {
|
|
2392
|
+
const heading = document.createElement("strong");
|
|
2393
|
+
heading.textContent = title.slice(0, 500);
|
|
2394
|
+
heading.style.cssText = [
|
|
2395
|
+
"display:-webkit-box",
|
|
2396
|
+
"overflow:hidden",
|
|
2397
|
+
"-webkit-box-orient:vertical",
|
|
2398
|
+
"-webkit-line-clamp:2",
|
|
2399
|
+
"font-size:15px",
|
|
2400
|
+
"font-weight:550",
|
|
2401
|
+
"line-height:21px",
|
|
2402
|
+
"overflow-wrap:anywhere",
|
|
2403
|
+
].join(";");
|
|
2404
|
+
preview.append(heading);
|
|
2405
|
+
}
|
|
2406
|
+
const content = document.createElement("div");
|
|
2407
|
+
content.textContent = body;
|
|
2408
|
+
content.style.cssText = [
|
|
2409
|
+
"min-height:0",
|
|
2410
|
+
"overflow:auto",
|
|
2411
|
+
"padding-right:2px",
|
|
2412
|
+
"white-space:pre-wrap",
|
|
2413
|
+
"overflow-wrap:anywhere",
|
|
2414
|
+
"font-size:14px",
|
|
2415
|
+
"font-weight:400",
|
|
2416
|
+
"line-height:20px",
|
|
2417
|
+
].join(";");
|
|
2418
|
+
preview.append(content);
|
|
2419
|
+
|
|
2420
|
+
const inset = 8;
|
|
2421
|
+
const gap = 10;
|
|
2422
|
+
const availableLeft = Math.max(160, surfaceLeft - gap - inset);
|
|
2423
|
+
const width = Math.min(360, availableLeft);
|
|
2424
|
+
preview.style.width = `${width}px`;
|
|
2425
|
+
document.body.append(preview);
|
|
2426
|
+
const previewRect = preview.getBoundingClientRect();
|
|
2427
|
+
const left = Math.max(inset, surfaceLeft - gap - width);
|
|
2428
|
+
const desiredTop = surfaceTop + anchorTop - 8;
|
|
2429
|
+
const top = Math.min(
|
|
2430
|
+
Math.max(12, desiredTop),
|
|
2431
|
+
Math.max(12, innerHeight - previewRect.height - 12),
|
|
2432
|
+
);
|
|
2433
|
+
preview.style.left = `${left}px`;
|
|
2434
|
+
preview.style.top = `${top}px`;
|
|
2435
|
+
activePromptPreview = { element: preview, recordKey: record.key };
|
|
2436
|
+
|
|
2437
|
+
preview.addEventListener("pointerenter", () => {
|
|
2438
|
+
clearTimeout(promptPreviewDismissTimer);
|
|
2439
|
+
promptPreviewDismissTimer = null;
|
|
2440
|
+
}, { signal: lifetime.signal });
|
|
2441
|
+
preview.addEventListener("pointerleave", () => closePromptPreview(), { signal: lifetime.signal });
|
|
2442
|
+
return true;
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
function closeModalSurface() {
|
|
2446
|
+
if (!activeModalRecordKey) return false;
|
|
2447
|
+
const record = surfaceRecords.get(activeModalRecordKey);
|
|
2448
|
+
record?.overlay?.remove();
|
|
2449
|
+
record?.element?.remove();
|
|
2450
|
+
if (record) removeSurfaceRecord(record.key);
|
|
2451
|
+
activeModalRecordKey = null;
|
|
2452
|
+
return true;
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
function openEditorModal(sourceRecord, payload = {}) {
|
|
2456
|
+
const feature = featureById.get(sourceRecord.featureId);
|
|
2457
|
+
const kind = payload.kind === "todo"
|
|
2458
|
+
? "todo"
|
|
2459
|
+
: new Set(["prompt", "note"]).has(payload.kind)
|
|
2460
|
+
? "prompt"
|
|
2461
|
+
: null;
|
|
2462
|
+
if (!feature || !kind) throw new Error("Editor kind must be prompt or todo");
|
|
2463
|
+
|
|
2464
|
+
closePromptPreview({ immediate: true });
|
|
2465
|
+
closeModalSurface();
|
|
2466
|
+
const editorUrl = new URL(sourceRecord.frame.src);
|
|
2467
|
+
editorUrl.searchParams.set("surface", "editor");
|
|
2468
|
+
editorUrl.searchParams.set("kind", kind);
|
|
2469
|
+
if (typeof payload.id === "string" && payload.id) editorUrl.searchParams.set("id", payload.id);
|
|
2470
|
+
else editorUrl.searchParams.delete("id");
|
|
2471
|
+
|
|
2472
|
+
const overlay = document.createElement("div");
|
|
2473
|
+
overlay.setAttribute("data-codex-personal-modal-overlay", feature.id);
|
|
2474
|
+
overlay.setAttribute("aria-hidden", "true");
|
|
2475
|
+
overlay.style.cssText = [
|
|
2476
|
+
"position:fixed",
|
|
2477
|
+
"inset:0",
|
|
2478
|
+
"z-index:50",
|
|
2479
|
+
"background:rgba(0,0,0,.133)",
|
|
2480
|
+
].join(";");
|
|
2481
|
+
|
|
2482
|
+
const dialog = document.createElement("section");
|
|
2483
|
+
dialog.setAttribute(modalSurfaceMarker, feature.id);
|
|
2484
|
+
dialog.setAttribute("role", "dialog");
|
|
2485
|
+
dialog.setAttribute("aria-modal", "true");
|
|
2486
|
+
dialog.setAttribute("aria-label", `${payload.id ? "Edit" : "Add"} ${kind}`);
|
|
2487
|
+
dialog.setAttribute("tabindex", "-1");
|
|
2488
|
+
dialog.style.cssText = [
|
|
2489
|
+
"position:fixed",
|
|
2490
|
+
"left:50%",
|
|
2491
|
+
"top:50%",
|
|
2492
|
+
"z-index:51",
|
|
2493
|
+
"width:min(420px,92vw)",
|
|
2494
|
+
`height:${kind === "prompt" ? "250px" : "198px"}`,
|
|
2495
|
+
"max-height:88vh",
|
|
2496
|
+
"transform:translate(-50%,-50%)",
|
|
2497
|
+
"overflow:hidden",
|
|
2498
|
+
"border:1px solid transparent",
|
|
2499
|
+
"border-radius:20px",
|
|
2500
|
+
"corner-shape:round",
|
|
2501
|
+
"background:var(--color-token-dropdown-background,var(--color-token-main-surface-primary))",
|
|
2502
|
+
"box-shadow:0 16px 32px -8px rgba(0,0,0,.19)",
|
|
2503
|
+
"outline:none",
|
|
2504
|
+
].join(";");
|
|
2505
|
+
|
|
2506
|
+
const frame = createFrame(editorUrl.href, `${payload.id ? "Edit" : "Add"} ${kind}`);
|
|
2507
|
+
dialog.append(frame);
|
|
2508
|
+
document.body.append(overlay, dialog);
|
|
2509
|
+
const key = surfaceKey(feature.id, "modal");
|
|
2510
|
+
const record = registerSurface(key, feature, "modal", editorUrl.href, dialog, frame);
|
|
2511
|
+
record.overlay = overlay;
|
|
2512
|
+
activeModalRecordKey = key;
|
|
2513
|
+
overlay.addEventListener("click", closeModalSurface, { signal: lifetime.signal });
|
|
2514
|
+
queueTheme();
|
|
2515
|
+
return { opened: true };
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
function showPinnedSurface(featureId) {
|
|
2519
|
+
const feature = featureById.get(featureId);
|
|
2520
|
+
if (!feature?.pinnedSummary) return;
|
|
2521
|
+
hidePageSurfaces();
|
|
2522
|
+
closeNativeSummary();
|
|
2523
|
+
for (const [id, state] of summaryStates) {
|
|
2524
|
+
if (id === featureId) continue;
|
|
2525
|
+
state.isPinned = false;
|
|
2526
|
+
state.isPopoverOpen = false;
|
|
2527
|
+
const other = pinnedSurfaces.get(id);
|
|
2528
|
+
if (other) setSummarySurfaceVisible(other, false, summaryDisplayMode());
|
|
2529
|
+
}
|
|
2530
|
+
const existing = pinnedSurfaces.get(featureId);
|
|
2531
|
+
const surface = existing ?? createPinnedSurface(feature);
|
|
2532
|
+
if (existing) {
|
|
2533
|
+
reloadSurfaceIfUnready(
|
|
2534
|
+
surfaceKey(feature.id, "pinned"),
|
|
2535
|
+
feature.pinnedSummary.surfaceUrl,
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2538
|
+
const displayMode = summaryDisplayMode();
|
|
2539
|
+
const state = summaryState(featureId);
|
|
2540
|
+
if (displayMode === "overlay") state.isPopoverOpen = true;
|
|
2541
|
+
else state.isPinned = true;
|
|
2542
|
+
activePinnedFeatureId = featureId;
|
|
2543
|
+
updateToolbarState();
|
|
2544
|
+
updatePinnedPosition(featureId);
|
|
2545
|
+
queueTheme();
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
function hidePinnedSurfaces() {
|
|
2549
|
+
const displayMode = summaryDisplayMode();
|
|
2550
|
+
for (const [featureId, state] of summaryStates) {
|
|
2551
|
+
state.isPinned = false;
|
|
2552
|
+
state.isPopoverOpen = false;
|
|
2553
|
+
const surface = pinnedSurfaces.get(featureId);
|
|
2554
|
+
if (surface) setSummarySurfaceVisible(surface, false, displayMode);
|
|
2555
|
+
}
|
|
2556
|
+
activePinnedFeatureId = null;
|
|
2557
|
+
restoreThreadContentShift();
|
|
2558
|
+
updateToolbarState();
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
function togglePinnedSurface(featureId) {
|
|
2562
|
+
const feature = featureById.get(featureId);
|
|
2563
|
+
if (!feature?.pinnedSummary) return;
|
|
2564
|
+
const displayMode = summaryDisplayMode();
|
|
2565
|
+
const state = summaryState(featureId);
|
|
2566
|
+
const isOpen = visibleSummarySurface(featureId, displayMode) !== null;
|
|
2567
|
+
if (!isOpen) {
|
|
2568
|
+
showPinnedSurface(featureId);
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
if (displayMode === "overlay") state.isPopoverOpen = false;
|
|
2572
|
+
else state.isPinned = false;
|
|
2573
|
+
if (!state.isPinned && !state.isPopoverOpen) activePinnedFeatureId = null;
|
|
2574
|
+
updatePinnedPosition(featureId);
|
|
2575
|
+
if (!activePinnedFeatureId) restoreThreadContentShift();
|
|
2576
|
+
updateToolbarState();
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
function updateResponsiveSummaryPresentation() {
|
|
2580
|
+
const nextMode = summaryDisplayMode();
|
|
2581
|
+
if (currentSummaryDisplayMode === "overlay" && nextMode !== "overlay") {
|
|
2582
|
+
for (const state of summaryStates.values()) state.isPopoverOpen = false;
|
|
2583
|
+
}
|
|
2584
|
+
currentSummaryDisplayMode = nextMode;
|
|
2585
|
+
for (const feature of toolbarFeatures) updatePinnedPosition(feature.id);
|
|
2586
|
+
if (!toolbarFeatures.some((feature) => (
|
|
2587
|
+
visibleSummarySurface(feature.id, nextMode) === "inline"
|
|
2588
|
+
))) applyThreadContentShift(0);
|
|
2589
|
+
updateToolbarState();
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
function ensureMainContentObserver() {
|
|
2593
|
+
const viewport = mainContentViewport();
|
|
2594
|
+
if (!viewport || mainContentObserver?.target === viewport) return;
|
|
2595
|
+
mainContentObserver?.observer.disconnect();
|
|
2596
|
+
const observer = new ResizeObserver(() => updateResponsiveSummaryPresentation());
|
|
2597
|
+
observer.observe(viewport);
|
|
2598
|
+
mainContentObserver = { target: viewport, observer };
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
function nativeTabModuleUrl() {
|
|
2602
|
+
return Array.from(document.querySelectorAll('link[rel="modulepreload"]'))
|
|
2603
|
+
.map((link) => link.href)
|
|
2604
|
+
.find((url) => /\/assets\/app-initial-[^/]+\.js(?:\?|$)/.test(url)) ?? null;
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
function reactFiberFor(element) {
|
|
2608
|
+
if (!(element instanceof Element)) return null;
|
|
2609
|
+
const key = Object.getOwnPropertyNames(element).find((name) => (
|
|
2610
|
+
name.startsWith("__reactFiber$")
|
|
2611
|
+
|| name.startsWith("__reactInternalInstance$")
|
|
2612
|
+
));
|
|
2613
|
+
return key ? element[key] : null;
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
function currentTaskScope() {
|
|
2617
|
+
let anchor = document.querySelector('[data-app-shell-tabs="true"]');
|
|
2618
|
+
let anchorFiber = reactFiberFor(anchor);
|
|
2619
|
+
if (!anchorFiber) {
|
|
2620
|
+
for (const element of document.querySelectorAll("body *")) {
|
|
2621
|
+
const fiber = reactFiberFor(element);
|
|
2622
|
+
if (!fiber) continue;
|
|
2623
|
+
anchor = element;
|
|
2624
|
+
anchorFiber = fiber;
|
|
2625
|
+
break;
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
if (!anchorFiber) return null;
|
|
2629
|
+
|
|
2630
|
+
let rootFiber = anchorFiber;
|
|
2631
|
+
while (rootFiber.return) rootFiber = rootFiber.return;
|
|
2632
|
+
|
|
2633
|
+
const isTaskScope = (value) => (
|
|
2634
|
+
value
|
|
2635
|
+
&& typeof value === "object"
|
|
2636
|
+
&& typeof value.get === "function"
|
|
2637
|
+
&& typeof value.set === "function"
|
|
2638
|
+
&& value.value?.routeKind === "local-thread"
|
|
2639
|
+
&& typeof value.query?.getOrFetch === "function"
|
|
2640
|
+
);
|
|
2641
|
+
const inspected = new Set();
|
|
2642
|
+
const inspect = (value) => {
|
|
2643
|
+
if (
|
|
2644
|
+
value == null
|
|
2645
|
+
|| (typeof value !== "object" && typeof value !== "function")
|
|
2646
|
+
|| inspected.has(value)
|
|
2647
|
+
) return null;
|
|
2648
|
+
inspected.add(value);
|
|
2649
|
+
if (isTaskScope(value)) return value;
|
|
2650
|
+
if (isTaskScope(value.current)) return value.current;
|
|
2651
|
+
return null;
|
|
2652
|
+
};
|
|
2653
|
+
|
|
2654
|
+
const stack = [rootFiber];
|
|
2655
|
+
let visitedFibers = 0;
|
|
2656
|
+
while (stack.length > 0 && visitedFibers < 50_000) {
|
|
2657
|
+
const fiber = stack.pop();
|
|
2658
|
+
visitedFibers += 1;
|
|
2659
|
+
let scope = inspect(fiber.memoizedProps);
|
|
2660
|
+
if (!scope && fiber.memoizedProps && typeof fiber.memoizedProps === "object") {
|
|
2661
|
+
for (const value of Object.values(fiber.memoizedProps)) {
|
|
2662
|
+
scope = inspect(value);
|
|
2663
|
+
if (scope) break;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
let hook = fiber.memoizedState;
|
|
2668
|
+
let hookIndex = 0;
|
|
2669
|
+
while (!scope && hook && typeof hook === "object" && hookIndex < 140) {
|
|
2670
|
+
scope = inspect(hook.memoizedState);
|
|
2671
|
+
if (!scope && hook.memoizedState && typeof hook.memoizedState === "object") {
|
|
2672
|
+
scope = inspect(hook.memoizedState.current);
|
|
2673
|
+
}
|
|
2674
|
+
hook = hook.next;
|
|
2675
|
+
hookIndex += 1;
|
|
2676
|
+
}
|
|
2677
|
+
if (scope) return scope;
|
|
2678
|
+
if (fiber.sibling) stack.push(fiber.sibling);
|
|
2679
|
+
if (fiber.child) stack.push(fiber.child);
|
|
2680
|
+
}
|
|
2681
|
+
return null;
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
async function loadNativeTabCapability() {
|
|
2685
|
+
if (nativeTabCapability) return nativeTabCapability;
|
|
2686
|
+
if (nativeTabCapabilityPromise) return nativeTabCapabilityPromise;
|
|
2687
|
+
nativeTabCapabilityPromise = (async () => {
|
|
2688
|
+
try {
|
|
2689
|
+
const moduleUrl = nativeTabModuleUrl();
|
|
2690
|
+
if (!moduleUrl) throw new Error("Codex app-initial modulepreload was not found");
|
|
2691
|
+
const appModule = await import(moduleUrl);
|
|
2692
|
+
if (typeof appModule.Gtt !== "function") {
|
|
2693
|
+
throw new Error("Codex right-panel controller factory is unavailable");
|
|
2694
|
+
}
|
|
2695
|
+
if (typeof appModule.hkt !== "function" || typeof appModule.Skt !== "function") {
|
|
2696
|
+
throw new Error("Codex React runtime factories are unavailable");
|
|
2697
|
+
}
|
|
2698
|
+
const controller = appModule.Gtt("right");
|
|
2699
|
+
const jsx = appModule.hkt();
|
|
2700
|
+
const React = appModule.Skt();
|
|
2701
|
+
if (
|
|
2702
|
+
typeof controller?.openTab !== "function"
|
|
2703
|
+
|| typeof controller?.activateTab !== "function"
|
|
2704
|
+
|| typeof controller?.closeTab !== "function"
|
|
2705
|
+
|| typeof jsx?.jsx !== "function"
|
|
2706
|
+
|| typeof React?.useLayoutEffect !== "function"
|
|
2707
|
+
|| typeof React?.useRef !== "function"
|
|
2708
|
+
) throw new Error("Codex native tab capability is incomplete");
|
|
2709
|
+
nativeTabCapability = {
|
|
2710
|
+
controller,
|
|
2711
|
+
jsx,
|
|
2712
|
+
React,
|
|
2713
|
+
moduleAsset: moduleUrl.split("/").pop(),
|
|
2714
|
+
};
|
|
2715
|
+
nativeTabCapabilityError = null;
|
|
2716
|
+
return nativeTabCapability;
|
|
2717
|
+
} catch (error) {
|
|
2718
|
+
nativeTabCapabilityError = error;
|
|
2719
|
+
return null;
|
|
2720
|
+
}
|
|
2721
|
+
})();
|
|
2722
|
+
return nativeTabCapabilityPromise;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
function nativeDetailIcon(feature, detail, jsx) {
|
|
2726
|
+
const icon = detail.icon ?? feature.icon;
|
|
2727
|
+
if (!icon?.markup) return undefined;
|
|
2728
|
+
return jsx.jsx("svg", {
|
|
2729
|
+
width: 16,
|
|
2730
|
+
height: 16,
|
|
2731
|
+
viewBox: icon.viewBox ?? "0 0 24 24",
|
|
2732
|
+
fill: "none",
|
|
2733
|
+
stroke: "currentColor",
|
|
2734
|
+
strokeWidth: 1.5,
|
|
2735
|
+
strokeLinecap: "round",
|
|
2736
|
+
strokeLinejoin: "round",
|
|
2737
|
+
"aria-hidden": true,
|
|
2738
|
+
className: "icon-xs shrink-0",
|
|
2739
|
+
dangerouslySetInnerHTML: { __html: icon.markup },
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
function nativeDetailComponent(feature, detail, capability) {
|
|
2744
|
+
const detailKey = `${feature.id}:${detail.id}`;
|
|
2745
|
+
let Component = nativeDetailComponents.get(detailKey);
|
|
2746
|
+
if (Component) return Component;
|
|
2747
|
+
const { jsx, React } = capability;
|
|
2748
|
+
Component = function CodexPersonalDetailTab() {
|
|
2749
|
+
const elementRef = React.useRef(null);
|
|
2750
|
+
const frameRef = React.useRef(null);
|
|
2751
|
+
const recordKeyRef = React.useRef(null);
|
|
2752
|
+
React.useLayoutEffect(() => {
|
|
2753
|
+
const element = elementRef.current;
|
|
2754
|
+
const frame = frameRef.current;
|
|
2755
|
+
if (!element || !frame) return undefined;
|
|
2756
|
+
const instanceId = crypto.randomUUID?.()
|
|
2757
|
+
?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2758
|
+
const recordKey = surfaceKey(feature.id, "native-detail", `${detail.id}:${instanceId}`);
|
|
2759
|
+
recordKeyRef.current = recordKey;
|
|
2760
|
+
registerSurface(recordKey, feature, "detail", detail.surfaceUrl, element, frame, detail.id);
|
|
2761
|
+
let keys = nativeDetailSurfaceKeys.get(detailKey);
|
|
2762
|
+
if (!keys) {
|
|
2763
|
+
keys = new Set();
|
|
2764
|
+
nativeDetailSurfaceKeys.set(detailKey, keys);
|
|
2765
|
+
}
|
|
2766
|
+
keys.add(recordKey);
|
|
2767
|
+
queueTheme();
|
|
2768
|
+
return () => {
|
|
2769
|
+
keys.delete(recordKey);
|
|
2770
|
+
if (keys.size === 0) nativeDetailSurfaceKeys.delete(detailKey);
|
|
2771
|
+
removeSurfaceRecord(recordKey);
|
|
2772
|
+
recordKeyRef.current = null;
|
|
2773
|
+
};
|
|
2774
|
+
}, []);
|
|
2775
|
+
return jsx.jsx("div", {
|
|
2776
|
+
ref: elementRef,
|
|
2777
|
+
"data-codex-personal-native-detail": detailKey,
|
|
2778
|
+
style: {
|
|
2779
|
+
height: "100%",
|
|
2780
|
+
minHeight: 0,
|
|
2781
|
+
overflow: "hidden",
|
|
2782
|
+
background: "var(--color-token-main-surface-primary)",
|
|
2783
|
+
},
|
|
2784
|
+
children: jsx.jsx("iframe", {
|
|
2785
|
+
ref: frameRef,
|
|
2786
|
+
src: detail.surfaceUrl,
|
|
2787
|
+
title: detail.label,
|
|
2788
|
+
allow: "clipboard-write",
|
|
2789
|
+
style: {
|
|
2790
|
+
display: "block",
|
|
2791
|
+
width: "100%",
|
|
2792
|
+
height: "100%",
|
|
2793
|
+
border: 0,
|
|
2794
|
+
background: "transparent",
|
|
2795
|
+
},
|
|
2796
|
+
}),
|
|
2797
|
+
});
|
|
2798
|
+
};
|
|
2799
|
+
nativeDetailComponents.set(detailKey, Component);
|
|
2800
|
+
return Component;
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
function reloadNativeDetailIfUnready(feature, detail) {
|
|
2804
|
+
const keys = nativeDetailSurfaceKeys.get(`${feature.id}:${detail.id}`);
|
|
2805
|
+
if (!keys) return false;
|
|
2806
|
+
let reloaded = false;
|
|
2807
|
+
for (const key of keys) {
|
|
2808
|
+
if (reloadSurfaceIfUnready(key, detail.surfaceUrl)) reloaded = true;
|
|
2809
|
+
}
|
|
2810
|
+
return reloaded;
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
async function openNativeDetailTab(feature, detail) {
|
|
2814
|
+
const capability = await loadNativeTabCapability();
|
|
2815
|
+
if (!capability) return null;
|
|
2816
|
+
const scope = currentTaskScope();
|
|
2817
|
+
if (!scope) {
|
|
2818
|
+
nativeTabCapabilityError = new Error("The current Codex Task scope was not found");
|
|
2819
|
+
return null;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
const { controller, jsx } = capability;
|
|
2823
|
+
const tabId = `${feature.id}-${detail.id}`;
|
|
2824
|
+
let session = Array.from(nativeOpenTabSessions).find((candidate) => (
|
|
2825
|
+
candidate.scope === scope && candidate.tabId === tabId
|
|
2826
|
+
));
|
|
2827
|
+
let focusedExisting = Boolean(session);
|
|
2828
|
+
if (!focusedExisting && controller.tabById$) {
|
|
2829
|
+
try {
|
|
2830
|
+
focusedExisting = scope.get(controller.tabById$, tabId) != null;
|
|
2831
|
+
} catch {}
|
|
2832
|
+
}
|
|
2833
|
+
const isNewSession = !session;
|
|
2834
|
+
if (!session) {
|
|
2835
|
+
session = { scope, controller, tabId };
|
|
2836
|
+
nativeOpenTabSessions.add(session);
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
try {
|
|
2840
|
+
controller.openTab(scope, nativeDetailComponent(feature, detail, capability), {
|
|
2841
|
+
id: tabId,
|
|
2842
|
+
kind: tabId,
|
|
2843
|
+
title: detail.label,
|
|
2844
|
+
tooltip: detail.label,
|
|
2845
|
+
icon: nativeDetailIcon(feature, detail, jsx),
|
|
2846
|
+
isClosable: true,
|
|
2847
|
+
props: {},
|
|
2848
|
+
onClose: () => nativeOpenTabSessions.delete(session),
|
|
2849
|
+
});
|
|
2850
|
+
} catch (error) {
|
|
2851
|
+
if (isNewSession) nativeOpenTabSessions.delete(session);
|
|
2852
|
+
nativeTabCapabilityError = error;
|
|
2853
|
+
return null;
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
nativeTabCapabilityError = null;
|
|
2857
|
+
if (focusedExisting) reloadNativeDetailIfUnready(feature, detail);
|
|
2858
|
+
const fallbackKey = `${feature.id}:${detail.id}`;
|
|
2859
|
+
if (openTabs.has(fallbackKey)) closeDetailTab(fallbackKey);
|
|
2860
|
+
return { tabId, focusedExisting, presentation: "native" };
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
function detailTabCapability() {
|
|
2864
|
+
return {
|
|
2865
|
+
presentation: nativeTabCapability ? "native" : "fallback",
|
|
2866
|
+
moduleAsset: nativeTabCapability?.moduleAsset ?? null,
|
|
2867
|
+
error: nativeTabCapabilityError?.message ?? null,
|
|
2868
|
+
openNativeTabs: nativeOpenTabSessions.size,
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
function closeNativeDetailTabs() {
|
|
2873
|
+
for (const session of Array.from(nativeOpenTabSessions)) {
|
|
2874
|
+
try {
|
|
2875
|
+
session.controller.closeTab(session.scope, session.tabId);
|
|
2876
|
+
} catch {}
|
|
2877
|
+
}
|
|
2878
|
+
nativeOpenTabSessions.clear();
|
|
2879
|
+
nativeDetailComponents.clear();
|
|
2880
|
+
nativeDetailSurfaceKeys.clear();
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
function updateTabHostGeometry() {
|
|
2884
|
+
if (!tabHost?.isConnected) return;
|
|
2885
|
+
const width = Math.min(620, Math.max(360, innerWidth * 0.44));
|
|
2886
|
+
tabHost.style.width = `${Math.min(width, Math.max(300, innerWidth - 72))}px`;
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
function createTabHost() {
|
|
2890
|
+
const host = document.createElement("aside");
|
|
2891
|
+
host.setAttribute("data-codex-personal-tab-host", "right");
|
|
2892
|
+
host.setAttribute("aria-label", locale === "zh-CN" ? "CodexNotes 工作区标签页" : "CodexNotes workspace tabs");
|
|
2893
|
+
host.style.cssText = [
|
|
2894
|
+
"position:fixed",
|
|
2895
|
+
"top:46px",
|
|
2896
|
+
"bottom:0",
|
|
2897
|
+
"right:0",
|
|
2898
|
+
"z-index:42",
|
|
2899
|
+
"display:flex",
|
|
2900
|
+
"flex-direction:column",
|
|
2901
|
+
"min-width:300px",
|
|
2902
|
+
"background:var(--color-token-main-surface-primary)",
|
|
2903
|
+
"border-left:0.5px solid var(--color-border)",
|
|
2904
|
+
"box-shadow:-8px 0 16px -12px rgba(0,0,0,.24)",
|
|
2905
|
+
].join(";");
|
|
2906
|
+
const header = document.createElement("div");
|
|
2907
|
+
header.style.cssText = [
|
|
2908
|
+
"height:46px",
|
|
2909
|
+
"display:flex",
|
|
2910
|
+
"align-items:center",
|
|
2911
|
+
"padding:0 8px",
|
|
2912
|
+
"flex:0 0 auto",
|
|
2913
|
+
"background:var(--color-token-main-surface-primary)",
|
|
2914
|
+
].join(";");
|
|
2915
|
+
const strip = document.createElement("div");
|
|
2916
|
+
strip.setAttribute("data-app-shell-tab-strip-controller", "right");
|
|
2917
|
+
strip.setAttribute("data-codex-personal-tab-strip", "true");
|
|
2918
|
+
strip.setAttribute("role", "tablist");
|
|
2919
|
+
strip.style.cssText = "display:flex;align-items:center;gap:4px;min-width:0;flex:1;overflow-x:auto;height:100%";
|
|
2920
|
+
header.append(strip);
|
|
2921
|
+
const panels = document.createElement("div");
|
|
2922
|
+
panels.setAttribute("data-codex-personal-tab-panels", "true");
|
|
2923
|
+
panels.style.cssText = "position:relative;min-height:0;flex:1;background:var(--color-token-main-surface-primary)";
|
|
2924
|
+
host.append(header, panels);
|
|
2925
|
+
document.body.append(host);
|
|
2926
|
+
tabHost = host;
|
|
2927
|
+
updateTabHostGeometry();
|
|
2928
|
+
return host;
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
function activateTab(key) {
|
|
2932
|
+
const record = openTabs.get(key);
|
|
2933
|
+
if (!record) return;
|
|
2934
|
+
activeTabKey = key;
|
|
2935
|
+
for (const [candidateKey, candidate] of openTabs) {
|
|
2936
|
+
const active = candidateKey === key;
|
|
2937
|
+
candidate.tabButton.setAttribute("aria-selected", String(active));
|
|
2938
|
+
candidate.tab.style.setProperty(
|
|
2939
|
+
"--app-shell-tab-background",
|
|
2940
|
+
active
|
|
2941
|
+
? "color-mix(in srgb, var(--color-text-foreground) 5%, var(--color-token-main-surface-primary))"
|
|
2942
|
+
: "transparent",
|
|
2943
|
+
);
|
|
2944
|
+
candidate.panel.hidden = !active;
|
|
2945
|
+
}
|
|
2946
|
+
record.tabButton.focus({ preventScroll: true });
|
|
2947
|
+
queueTheme();
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
function closeDetailTab(key) {
|
|
2951
|
+
const record = openTabs.get(key);
|
|
2952
|
+
if (!record) return;
|
|
2953
|
+
const keys = Array.from(openTabs.keys());
|
|
2954
|
+
const index = keys.indexOf(key);
|
|
2955
|
+
record.tab.remove();
|
|
2956
|
+
record.panel.remove();
|
|
2957
|
+
removeSurfaceRecord(record.surfaceRecordKey);
|
|
2958
|
+
openTabs.delete(key);
|
|
2959
|
+
if (openTabs.size === 0) {
|
|
2960
|
+
tabHost?.remove();
|
|
2961
|
+
tabHost = null;
|
|
2962
|
+
activeTabKey = null;
|
|
2963
|
+
updateResponsiveSummaryPresentation();
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
if (activeTabKey === key) {
|
|
2967
|
+
const remainingKeys = Array.from(openTabs.keys());
|
|
2968
|
+
activateTab(remainingKeys[Math.min(index, remainingKeys.length - 1)]);
|
|
2969
|
+
}
|
|
2970
|
+
updateResponsiveSummaryPresentation();
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
function createDetailTab(feature, detail) {
|
|
2974
|
+
const host = tabHost?.isConnected ? tabHost : createTabHost();
|
|
2975
|
+
const strip = host.querySelector("[data-codex-personal-tab-strip]");
|
|
2976
|
+
const panels = host.querySelector("[data-codex-personal-tab-panels]");
|
|
2977
|
+
const key = `${feature.id}:${detail.id}`;
|
|
2978
|
+
const stableId = `${feature.id}-${detail.id}`;
|
|
2979
|
+
|
|
2980
|
+
const tab = document.createElement("div");
|
|
2981
|
+
tab.setAttribute("data-app-shell-tab-controller", "right");
|
|
2982
|
+
tab.setAttribute("data-tab-id", stableId);
|
|
2983
|
+
tab.style.cssText = [
|
|
2984
|
+
"position:relative",
|
|
2985
|
+
"display:flex",
|
|
2986
|
+
"align-items:center",
|
|
2987
|
+
"min-width:90px",
|
|
2988
|
+
"max-width:160px",
|
|
2989
|
+
"height:28px",
|
|
2990
|
+
"padding:0 4px 0 8px",
|
|
2991
|
+
"border-radius:8px",
|
|
2992
|
+
"background:var(--app-shell-tab-background,transparent)",
|
|
2993
|
+
"flex:1 0 110px",
|
|
2994
|
+
].join(";");
|
|
2995
|
+
const tabButton = document.createElement("button");
|
|
2996
|
+
tabButton.type = "button";
|
|
2997
|
+
tabButton.setAttribute("role", "tab");
|
|
2998
|
+
tabButton.setAttribute("aria-selected", "false");
|
|
2999
|
+
tabButton.style.cssText = "display:flex;align-items:center;gap:7px;min-width:0;flex:1;height:100%;padding:0;background:transparent;border:0;color:var(--color-text-foreground);cursor:pointer";
|
|
3000
|
+
const icon = createMarkupIcon(detail.icon ?? feature.icon, "icon-xs", 16);
|
|
3001
|
+
if (icon) tabButton.append(icon);
|
|
3002
|
+
const label = document.createElement("span");
|
|
3003
|
+
label.textContent = detail.label;
|
|
3004
|
+
label.style.cssText = "min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
|
|
3005
|
+
tabButton.append(label);
|
|
3006
|
+
tabButton.addEventListener("click", () => activateTab(key));
|
|
3007
|
+
const close = document.createElement("button");
|
|
3008
|
+
close.type = "button";
|
|
3009
|
+
close.textContent = "×";
|
|
3010
|
+
close.setAttribute("data-app-shell-tab-close-button", "true");
|
|
3011
|
+
close.setAttribute(
|
|
3012
|
+
"aria-label",
|
|
3013
|
+
locale === "zh-CN" ? `关闭${detail.label}标签页` : `Close ${detail.label} tab`,
|
|
3014
|
+
);
|
|
3015
|
+
close.style.cssText = "width:20px;height:20px;border:0;border-radius:6px;padding:0;background:transparent;color:var(--color-text-foreground-tertiary);font-size:18px;line-height:18px;cursor:pointer";
|
|
3016
|
+
close.addEventListener("click", (event) => {
|
|
3017
|
+
event.stopPropagation();
|
|
3018
|
+
closeDetailTab(key);
|
|
3019
|
+
});
|
|
3020
|
+
tab.append(tabButton, close);
|
|
3021
|
+
|
|
3022
|
+
const panel = document.createElement("div");
|
|
3023
|
+
panel.setAttribute("role", "tabpanel");
|
|
3024
|
+
panel.setAttribute("aria-label", detail.label);
|
|
3025
|
+
panel.setAttribute("data-app-shell-tab-panel-controller", "right");
|
|
3026
|
+
panel.setAttribute("data-tab-id", stableId);
|
|
3027
|
+
panel.style.cssText = "position:absolute;inset:0;min-height:0;background:var(--color-token-main-surface-primary)";
|
|
3028
|
+
const frame = createFrame(detail.surfaceUrl, detail.label);
|
|
3029
|
+
panel.append(frame);
|
|
3030
|
+
strip.append(tab);
|
|
3031
|
+
panels.append(panel);
|
|
3032
|
+
const recordKey = surfaceKey(feature.id, "detail", detail.id);
|
|
3033
|
+
registerSurface(recordKey, feature, "detail", detail.surfaceUrl, panel, frame, detail.id);
|
|
3034
|
+
const record = { key, feature, detail, tab, tabButton, close, panel, frame, surfaceRecordKey: recordKey };
|
|
3035
|
+
openTabs.set(key, record);
|
|
3036
|
+
return record;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
function openFallbackDetailTab(feature, detail) {
|
|
3040
|
+
const key = `${feature.id}:${detail.id}`;
|
|
3041
|
+
const existing = openTabs.has(key);
|
|
3042
|
+
if (!existing) createDetailTab(feature, detail);
|
|
3043
|
+
else reloadSurfaceIfUnready(
|
|
3044
|
+
surfaceKey(feature.id, "detail", detail.id),
|
|
3045
|
+
detail.surfaceUrl,
|
|
3046
|
+
);
|
|
3047
|
+
activateTab(key);
|
|
3048
|
+
updateResponsiveSummaryPresentation();
|
|
3049
|
+
return {
|
|
3050
|
+
tabId: `${feature.id}-${detail.id}`,
|
|
3051
|
+
focusedExisting: existing,
|
|
3052
|
+
presentation: "fallback",
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
async function openDetailTab(featureId, detailId) {
|
|
3057
|
+
const feature = featureById.get(featureId);
|
|
3058
|
+
const detail = feature?.detailTabs?.find((candidate) => candidate.id === detailId);
|
|
3059
|
+
if (!feature || !detail) throw new Error("The requested detail Tab is not registered");
|
|
3060
|
+
return await openNativeDetailTab(feature, detail)
|
|
3061
|
+
?? openFallbackDetailTab(feature, detail);
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
function openThread(threadId) {
|
|
3065
|
+
const nativeThread = Array.from(
|
|
3066
|
+
document.querySelectorAll("[data-app-action-sidebar-thread-id]"),
|
|
3067
|
+
).find((element) => {
|
|
3068
|
+
const nativeId = element.getAttribute("data-app-action-sidebar-thread-id");
|
|
3069
|
+
return nativeId === threadId || nativeId?.endsWith(`:${threadId}`);
|
|
3070
|
+
});
|
|
3071
|
+
|
|
3072
|
+
hidePageSurfaces();
|
|
3073
|
+
hidePinnedSurfaces();
|
|
3074
|
+
if (nativeThread) {
|
|
3075
|
+
nativeThread.click();
|
|
3076
|
+
nativeThread.focus({ preventScroll: false });
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
const link = document.createElement("a");
|
|
3081
|
+
link.href = `codex://threads/${threadId}`;
|
|
3082
|
+
link.target = "_blank";
|
|
3083
|
+
link.rel = "noreferrer";
|
|
3084
|
+
link.hidden = true;
|
|
3085
|
+
document.body.append(link);
|
|
3086
|
+
link.click();
|
|
3087
|
+
link.remove();
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
function createSidebarEntry(feature, reference) {
|
|
3091
|
+
const entry = reference.cloneNode(true);
|
|
3092
|
+
entry.setAttribute(entryMarker, feature.id);
|
|
3093
|
+
entry.removeAttribute("href");
|
|
3094
|
+
entry.removeAttribute("aria-current");
|
|
3095
|
+
entry.querySelectorAll("[data-state]").forEach((node) => node.removeAttribute("data-state"));
|
|
3096
|
+
const originalIcon = entry.querySelector("svg");
|
|
3097
|
+
const featureIcon = createFeatureIcon(feature, originalIcon?.getAttribute("class"));
|
|
3098
|
+
if (originalIcon && featureIcon) originalIcon.replaceWith(featureIcon);
|
|
3099
|
+
|
|
3100
|
+
const referenceLabels = nativeLabels.plugins;
|
|
3101
|
+
const textLeaf = Array.from(entry.querySelectorAll("*")).find((node) => (
|
|
3102
|
+
node.children.length === 0
|
|
3103
|
+
&& referenceLabels.includes(node.textContent?.trim().toLowerCase())
|
|
3104
|
+
));
|
|
3105
|
+
if (textLeaf) textLeaf.textContent = feature.label;
|
|
3106
|
+
else entry.setAttribute("aria-label", feature.label);
|
|
3107
|
+
|
|
3108
|
+
entry.addEventListener("click", (event) => {
|
|
3109
|
+
event.preventDefault();
|
|
3110
|
+
event.stopPropagation();
|
|
3111
|
+
showPageSurface(feature.id);
|
|
3112
|
+
});
|
|
3113
|
+
return entry;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
function createToolbarEntry(feature, nativeButton) {
|
|
3117
|
+
const nativeRoot = toolbarControlRoot(nativeButton);
|
|
3118
|
+
const root = nativeRoot.cloneNode(true);
|
|
3119
|
+
root.setAttribute("data-codex-personal-toolbar-entry", feature.id);
|
|
3120
|
+
root.classList.remove("ms-auto");
|
|
3121
|
+
root.querySelectorAll("[id]").forEach((node) => node.removeAttribute("id"));
|
|
3122
|
+
root.querySelectorAll("[aria-describedby]").forEach((node) => node.removeAttribute("aria-describedby"));
|
|
3123
|
+
const button = root.matches("button") ? root : root.querySelector("button");
|
|
3124
|
+
if (!button) return null;
|
|
3125
|
+
const label = summaryButtonLabel(feature);
|
|
3126
|
+
button.setAttribute(entryMarker, feature.id);
|
|
3127
|
+
button.setAttribute("aria-label", label);
|
|
3128
|
+
button.setAttribute("title", label);
|
|
3129
|
+
button.setAttribute("aria-pressed", "false");
|
|
3130
|
+
button.setAttribute("aria-expanded", "false");
|
|
3131
|
+
button.setAttribute("data-state", "closed");
|
|
3132
|
+
button.removeAttribute("disabled");
|
|
3133
|
+
button.classList.remove(
|
|
3134
|
+
"text-token-foreground",
|
|
3135
|
+
"bg-token-foreground/5",
|
|
3136
|
+
"bg-token-foreground/10",
|
|
3137
|
+
"enabled:hover:bg-token-foreground/10",
|
|
3138
|
+
"data-[state=open]:bg-token-foreground/10",
|
|
3139
|
+
"bg-token-list-hover-background",
|
|
3140
|
+
);
|
|
3141
|
+
button.classList.add(
|
|
3142
|
+
"text-token-text-tertiary",
|
|
3143
|
+
"enabled:hover:bg-token-list-hover-background",
|
|
3144
|
+
"data-[state=open]:bg-token-list-hover-background",
|
|
3145
|
+
);
|
|
3146
|
+
const originalIcon = button.querySelector("svg");
|
|
3147
|
+
const featureIcon = createFeatureIcon(feature, originalIcon?.getAttribute("class"), 20);
|
|
3148
|
+
if (originalIcon && featureIcon) originalIcon.replaceWith(featureIcon);
|
|
3149
|
+
button.addEventListener("click", (event) => {
|
|
3150
|
+
event.preventDefault();
|
|
3151
|
+
event.stopPropagation();
|
|
3152
|
+
togglePinnedSurface(feature.id);
|
|
3153
|
+
});
|
|
3154
|
+
return button;
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
function ensureSidebarEntries() {
|
|
3158
|
+
if (!sidebarFeatures.length) return;
|
|
3159
|
+
const reference = findReferenceEntry();
|
|
3160
|
+
if (!reference) return;
|
|
3161
|
+
for (const feature of sidebarFeatures) {
|
|
3162
|
+
if (!findEntry(feature.id)) reference.after(createSidebarEntry(feature, reference));
|
|
3163
|
+
}
|
|
3164
|
+
const cursors = new Map();
|
|
3165
|
+
for (const feature of sidebarFeatures) {
|
|
3166
|
+
const anchorKey = feature.placement?.after ?? "sites";
|
|
3167
|
+
const anchor = cursors.get(anchorKey) ?? findNativeEntry(anchorKey) ?? reference;
|
|
3168
|
+
const entry = findEntry(feature.id);
|
|
3169
|
+
if (anchor && entry && anchor.nextElementSibling !== entry) anchor.after(entry);
|
|
3170
|
+
if (entry) cursors.set(anchorKey, entry);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
function ensureToolbarEntries() {
|
|
3175
|
+
if (!toolbarFeatures.length) return;
|
|
3176
|
+
const bottomPanelButton = nativeBottomPanelButton();
|
|
3177
|
+
if (!bottomPanelButton) return;
|
|
3178
|
+
const summaryButton = nativeSummaryButton();
|
|
3179
|
+
const summaryRoot = toolbarControlRoot(summaryButton);
|
|
3180
|
+
const summaryGroup = nativeSummaryToolbarGroup(summaryButton);
|
|
3181
|
+
const fallbackGroup = summaryGroup ? null : ensureFallbackSummaryToolbarGroup(bottomPanelButton);
|
|
3182
|
+
const targetGroup = summaryGroup ?? fallbackGroup;
|
|
3183
|
+
if (!targetGroup) return;
|
|
3184
|
+
let cursor = summaryRoot;
|
|
3185
|
+
for (const feature of toolbarFeatures) {
|
|
3186
|
+
const entries = Array.from(document.querySelectorAll(`[${entryMarker}="${feature.id}"]`));
|
|
3187
|
+
for (const duplicate of entries.slice(1)) {
|
|
3188
|
+
(duplicate.closest("[data-codex-personal-toolbar-entry]") ?? duplicate).remove();
|
|
3189
|
+
}
|
|
3190
|
+
const entry = entries[0]?.isConnected
|
|
3191
|
+
? entries[0]
|
|
3192
|
+
: createToolbarEntry(feature, summaryButton ?? bottomPanelButton);
|
|
3193
|
+
const entryRoot = entry?.closest("[data-codex-personal-toolbar-entry]") ?? entry;
|
|
3194
|
+
if (!entryRoot) continue;
|
|
3195
|
+
if (cursor) {
|
|
3196
|
+
if (cursor.nextElementSibling !== entryRoot) cursor.after(entryRoot);
|
|
3197
|
+
} else if (targetGroup.lastElementChild !== entryRoot) {
|
|
3198
|
+
targetGroup.append(entryRoot);
|
|
3199
|
+
}
|
|
3200
|
+
cursor = entryRoot;
|
|
3201
|
+
}
|
|
3202
|
+
if (summaryGroup) {
|
|
3203
|
+
document.querySelectorAll("[data-codex-personal-summary-toolbar-group]").forEach((group) => group.remove());
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
function ensureEntries() {
|
|
3208
|
+
ensureQueued = false;
|
|
3209
|
+
ensureFrame = null;
|
|
3210
|
+
ensureTimer = null;
|
|
3211
|
+
ensureSidebarEntries();
|
|
3212
|
+
ensureToolbarEntries();
|
|
3213
|
+
ensureModelSelector();
|
|
3214
|
+
checkThreadChange();
|
|
3215
|
+
updateSidebarSelectedState();
|
|
3216
|
+
updatePageSurfacePositions();
|
|
3217
|
+
updateTabHostGeometry();
|
|
3218
|
+
ensureMainContentObserver();
|
|
3219
|
+
updateResponsiveSummaryPresentation();
|
|
3220
|
+
const nativeToggle = nativeSummaryButton();
|
|
3221
|
+
if (
|
|
3222
|
+
activePinnedFeatureId
|
|
3223
|
+
&& (nativeToggle?.getAttribute("aria-expanded") === "true"
|
|
3224
|
+
|| nativeToggle?.getAttribute("aria-pressed") === "true")
|
|
3225
|
+
) {
|
|
3226
|
+
hidePinnedSurfaces();
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
function flushEnsure() {
|
|
3231
|
+
if (!ensureQueued) return;
|
|
3232
|
+
if (ensureFrame != null) cancelAnimationFrame(ensureFrame);
|
|
3233
|
+
if (ensureTimer != null) clearTimeout(ensureTimer);
|
|
3234
|
+
ensureFrame = null;
|
|
3235
|
+
ensureTimer = null;
|
|
3236
|
+
ensureEntries();
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
function queueEnsure() {
|
|
3240
|
+
if (ensureQueued) return;
|
|
3241
|
+
ensureQueued = true;
|
|
3242
|
+
ensureFrame = requestAnimationFrame(flushEnsure);
|
|
3243
|
+
ensureTimer = setTimeout(flushEnsure, 50);
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
function findMessageSurface(event) {
|
|
3247
|
+
for (const record of surfaceRecords.values()) {
|
|
3248
|
+
if (event.origin === record.origin && event.source === record.frame.contentWindow) return record;
|
|
3249
|
+
}
|
|
3250
|
+
return null;
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
function respondToSurface(record, requestId, { ok, result = null, error = null }) {
|
|
3254
|
+
record.frame.contentWindow?.postMessage({
|
|
3255
|
+
type: "codex-personal:host-result",
|
|
3256
|
+
feature: record.featureId,
|
|
3257
|
+
requestId,
|
|
3258
|
+
ok,
|
|
3259
|
+
result,
|
|
3260
|
+
error,
|
|
3261
|
+
}, record.origin);
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
async function handleSurfaceHostAction(record, message) {
|
|
3265
|
+
const feature = featureById.get(record.featureId);
|
|
3266
|
+
const { requestId, action, payload = {} } = message;
|
|
3267
|
+
if (typeof requestId !== "string" || !feature?.hostActions.has(action)) {
|
|
3268
|
+
respondToSurface(record, requestId, {
|
|
3269
|
+
ok: false,
|
|
3270
|
+
error: { code: "ACTION_NOT_ALLOWED", message: "This Host action is not registered" },
|
|
3271
|
+
});
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
try {
|
|
3275
|
+
if (action === "insert-text") {
|
|
3276
|
+
respondToSurface(record, requestId, { ok: true, result: insertComposerText(payload.text) });
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3279
|
+
if (action === "open-detail-tab") {
|
|
3280
|
+
respondToSurface(record, requestId, {
|
|
3281
|
+
ok: true,
|
|
3282
|
+
result: await openDetailTab(feature.id, payload.surface),
|
|
3283
|
+
});
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
if (action === "open-editor-modal") {
|
|
3287
|
+
respondToSurface(record, requestId, {
|
|
3288
|
+
ok: true,
|
|
3289
|
+
result: openEditorModal(record, payload),
|
|
3290
|
+
});
|
|
3291
|
+
return;
|
|
3292
|
+
}
|
|
3293
|
+
if (action === "resolve-file-paths") {
|
|
3294
|
+
const getPathForFile = window.electronBridge?.getPathForFile;
|
|
3295
|
+
if (typeof getPathForFile !== "function") {
|
|
3296
|
+
throw new Error("Codex did not expose its File path bridge");
|
|
3297
|
+
}
|
|
3298
|
+
const files = Array.isArray(payload.files) ? payload.files : [];
|
|
3299
|
+
if (!files.length || files.some((file) => !(file instanceof File))) {
|
|
3300
|
+
throw new Error("Real dropped File objects are required");
|
|
3301
|
+
}
|
|
3302
|
+
const paths = files.map((file) => getPathForFile(file)).filter((value) => (
|
|
3303
|
+
typeof value === "string" && value.startsWith("/")
|
|
3304
|
+
));
|
|
3305
|
+
if (!paths.length) throw new Error("Codex could not resolve the dropped file paths");
|
|
3306
|
+
respondToSurface(record, requestId, { ok: true, result: { paths } });
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
if (action === "attach-file") {
|
|
3310
|
+
if (typeof globalThis[config.bindingName] !== "function") {
|
|
3311
|
+
throw new Error("The native file bridge is unavailable");
|
|
3312
|
+
}
|
|
3313
|
+
pendingHostActions.set(requestId, { recordKey: record.key });
|
|
3314
|
+
globalThis[config.bindingName](JSON.stringify({
|
|
3315
|
+
token: config.bindingToken,
|
|
3316
|
+
featureId: feature.id,
|
|
3317
|
+
requestId,
|
|
3318
|
+
action,
|
|
3319
|
+
payload,
|
|
3320
|
+
}));
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3323
|
+
throw new Error("Unsupported Host action");
|
|
3324
|
+
} catch (error) {
|
|
3325
|
+
respondToSurface(record, requestId, {
|
|
3326
|
+
ok: false,
|
|
3327
|
+
error: { code: "HOST_ACTION_FAILED", message: error.message },
|
|
3328
|
+
});
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
function resolveHostAction(requestId, response) {
|
|
3333
|
+
const pending = pendingHostActions.get(requestId);
|
|
3334
|
+
if (!pending) return false;
|
|
3335
|
+
pendingHostActions.delete(requestId);
|
|
3336
|
+
const record = surfaceRecords.get(pending.recordKey);
|
|
3337
|
+
if (!record) return false;
|
|
3338
|
+
respondToSurface(record, requestId, response);
|
|
3339
|
+
return true;
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
document.addEventListener("selectionchange", rememberComposerSelection, { signal: lifetime.signal });
|
|
3343
|
+
document.addEventListener(
|
|
3344
|
+
"click",
|
|
3345
|
+
(event) => {
|
|
3346
|
+
if (isNativeSummaryButton(event.target) && activePinnedFeatureId) hidePinnedSurfaces();
|
|
3347
|
+
if (activePinnedFeatureId && summaryDisplayMode() === "overlay") {
|
|
3348
|
+
const state = summaryState(activePinnedFeatureId);
|
|
3349
|
+
const surface = pinnedSurfaces.get(activePinnedFeatureId);
|
|
3350
|
+
const ownEntry = event.target.closest?.(`[${entryMarker}="${activePinnedFeatureId}"]`);
|
|
3351
|
+
const rightWorkspaceInteraction = event.target.closest?.(
|
|
3352
|
+
'[data-app-shell-tab-strip-controller="right"],'
|
|
3353
|
+
+ '[data-app-shell-tab-panel-controller="right"],'
|
|
3354
|
+
+ '[data-codex-personal-tab-host="right"]',
|
|
3355
|
+
);
|
|
3356
|
+
const promptPreviewInteraction = event.target.closest?.(`[${promptPreviewMarker}]`);
|
|
3357
|
+
if (
|
|
3358
|
+
state.isPopoverOpen
|
|
3359
|
+
&& !surface?.contains(event.target)
|
|
3360
|
+
&& !ownEntry
|
|
3361
|
+
&& !rightWorkspaceInteraction
|
|
3362
|
+
&& !promptPreviewInteraction
|
|
3363
|
+
) {
|
|
3364
|
+
const featureId = activePinnedFeatureId;
|
|
3365
|
+
state.isPopoverOpen = false;
|
|
3366
|
+
if (!state.isPinned) activePinnedFeatureId = null;
|
|
3367
|
+
updatePinnedPosition(featureId);
|
|
3368
|
+
updateToolbarState();
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
if (!activePageFeatureId) return;
|
|
3372
|
+
if (event.target.closest?.(`[${entryMarker}]`)) return;
|
|
3373
|
+
if (event.target.closest?.("[data-app-action-sidebar-scroll]")) hidePageSurfaces();
|
|
3374
|
+
},
|
|
3375
|
+
{ capture: true, signal: lifetime.signal },
|
|
3376
|
+
);
|
|
3377
|
+
|
|
3378
|
+
document.addEventListener("keydown", (event) => {
|
|
3379
|
+
if (event.key !== "Escape") return;
|
|
3380
|
+
if (closePromptPreview({ immediate: true })) {
|
|
3381
|
+
event.preventDefault();
|
|
3382
|
+
event.stopPropagation();
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
if (closeModalSurface()) {
|
|
3386
|
+
event.preventDefault();
|
|
3387
|
+
event.stopPropagation();
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
if (!activePinnedFeatureId || summaryDisplayMode() !== "overlay") return;
|
|
3391
|
+
const state = summaryState(activePinnedFeatureId);
|
|
3392
|
+
if (!state.isPopoverOpen) return;
|
|
3393
|
+
const featureId = activePinnedFeatureId;
|
|
3394
|
+
state.isPopoverOpen = false;
|
|
3395
|
+
if (!state.isPinned) activePinnedFeatureId = null;
|
|
3396
|
+
updatePinnedPosition(featureId);
|
|
3397
|
+
updateToolbarState();
|
|
3398
|
+
}, { capture: true, signal: lifetime.signal });
|
|
3399
|
+
|
|
3400
|
+
window.addEventListener("message", (event) => {
|
|
3401
|
+
const record = findMessageSurface(event);
|
|
3402
|
+
if (!record || !event.data || typeof event.data !== "object") return;
|
|
3403
|
+
if (event.data.feature && event.data.feature !== record.featureId) return;
|
|
3404
|
+
if (event.data.type === "codex-personal:ready") {
|
|
3405
|
+
record.ready = true;
|
|
3406
|
+
queueTheme();
|
|
3407
|
+
if (record.kind === "modal") {
|
|
3408
|
+
record.frame.focus();
|
|
3409
|
+
record.frame.contentWindow?.postMessage({
|
|
3410
|
+
type: "codex-personal:focus",
|
|
3411
|
+
feature: record.featureId,
|
|
3412
|
+
}, record.origin);
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
if (event.data.type === "codex-personal:surface-metrics" && record.kind === "pinned") {
|
|
3416
|
+
const preferredHeight = Number(event.data.height);
|
|
3417
|
+
if (Number.isFinite(preferredHeight) && preferredHeight > 0) {
|
|
3418
|
+
record.preferredHeight = Math.min(2_000, Math.max(80, Math.ceil(preferredHeight)));
|
|
3419
|
+
updatePinnedPosition(record.featureId);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
if (event.data.type === "codex-personal:prompt-preview" && record.kind === "pinned") {
|
|
3423
|
+
if (event.data.phase === "show") showPromptPreview(record, event.data);
|
|
3424
|
+
if (event.data.phase === "hide") requestPromptPreviewClose(record);
|
|
3425
|
+
}
|
|
3426
|
+
if (event.data.type === "codex-personal:close") {
|
|
3427
|
+
if (record.kind === "pinned") hidePinnedSurfaces();
|
|
3428
|
+
if (record.kind === "page") hidePageSurfaces();
|
|
3429
|
+
if (record.kind === "modal") closeModalSurface();
|
|
3430
|
+
}
|
|
3431
|
+
if (event.data.type === "codex-personal:open-thread" && event.data.threadId) {
|
|
3432
|
+
openThread(event.data.threadId);
|
|
3433
|
+
}
|
|
3434
|
+
if (event.data.type === "codex-personal:host-action") {
|
|
3435
|
+
handleSurfaceHostAction(record, event.data);
|
|
3436
|
+
}
|
|
3437
|
+
}, { signal: lifetime.signal });
|
|
3438
|
+
|
|
3439
|
+
window.addEventListener("resize", () => {
|
|
3440
|
+
closePromptPreview({ immediate: true });
|
|
3441
|
+
updatePageSurfacePositions();
|
|
3442
|
+
updateTabHostGeometry();
|
|
3443
|
+
updateResponsiveSummaryPresentation();
|
|
3444
|
+
}, { signal: lifetime.signal });
|
|
3445
|
+
const ensureObserver = new MutationObserver(queueEnsure);
|
|
3446
|
+
ensureObserver.observe(document.documentElement, {
|
|
3447
|
+
childList: true,
|
|
3448
|
+
subtree: true,
|
|
3449
|
+
attributes: true,
|
|
3450
|
+
attributeFilter: [
|
|
3451
|
+
"aria-expanded",
|
|
3452
|
+
"aria-pressed",
|
|
3453
|
+
"data-state",
|
|
3454
|
+
"data-app-action-sidebar-thread-selected",
|
|
3455
|
+
"data-app-action-sidebar-thread-id",
|
|
3456
|
+
],
|
|
3457
|
+
});
|
|
3458
|
+
domObservers.push(ensureObserver);
|
|
3459
|
+
const themeObserver = new MutationObserver(queueTheme);
|
|
3460
|
+
themeObserver.observe(document.documentElement, {
|
|
3461
|
+
attributes: true,
|
|
3462
|
+
attributeFilter: ["class", "style"],
|
|
3463
|
+
});
|
|
3464
|
+
domObservers.push(themeObserver);
|
|
3465
|
+
|
|
3466
|
+
const runtimeApi = {
|
|
3467
|
+
version: config.version,
|
|
3468
|
+
sessionId: config.sessionId,
|
|
3469
|
+
ensure: queueEnsure,
|
|
3470
|
+
show: showPageSurface,
|
|
3471
|
+
hide: () => {
|
|
3472
|
+
hidePageSurfaces();
|
|
3473
|
+
hidePinnedSurfaces();
|
|
3474
|
+
closeModalSurface();
|
|
3475
|
+
},
|
|
3476
|
+
showPinned: showPinnedSurface,
|
|
3477
|
+
hidePinned: hidePinnedSurfaces,
|
|
3478
|
+
openDetailTab,
|
|
3479
|
+
detailTabCapability,
|
|
3480
|
+
insertText: insertComposerText,
|
|
3481
|
+
resolveHostAction,
|
|
3482
|
+
dispose: () => {
|
|
3483
|
+
lifetime.abort();
|
|
3484
|
+
if (ensureFrame != null) cancelAnimationFrame(ensureFrame);
|
|
3485
|
+
if (ensureTimer != null) clearTimeout(ensureTimer);
|
|
3486
|
+
for (const observer of domObservers) observer.disconnect();
|
|
3487
|
+
mainContentObserver?.observer.disconnect();
|
|
3488
|
+
restoreThreadContentShift();
|
|
3489
|
+
threadShiftStyle.remove();
|
|
3490
|
+
for (const root of document.querySelectorAll("[data-codex-personal-toolbar-entry]")) root.remove();
|
|
3491
|
+
for (const group of document.querySelectorAll("[data-codex-personal-summary-toolbar-group]")) group.remove();
|
|
3492
|
+
for (const surface of pageSurfaces.values()) surface.remove();
|
|
3493
|
+
for (const surface of pinnedSurfaces.values()) surface.remove();
|
|
3494
|
+
closePromptPreview({ immediate: true });
|
|
3495
|
+
closeModalSurface();
|
|
3496
|
+
closeNativeDetailTabs();
|
|
3497
|
+
restoreModelSelectorEnhancements();
|
|
3498
|
+
tabHost?.remove();
|
|
3499
|
+
surfaceRecords.clear();
|
|
3500
|
+
openTabs.clear();
|
|
3501
|
+
if (window.__codexPersonalRuntime === runtimeApi) delete window.__codexPersonalRuntime;
|
|
3502
|
+
},
|
|
3503
|
+
};
|
|
3504
|
+
window.__codexPersonalRuntime = runtimeApi;
|
|
3505
|
+
currentThread = threadIdentity();
|
|
3506
|
+
currentSummaryDisplayMode = summaryDisplayMode();
|
|
3507
|
+
queueEnsure();
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
const runtimeFeatures = features.map((feature) => ({
|
|
3511
|
+
id: feature.id,
|
|
3512
|
+
label: feature.label,
|
|
3513
|
+
labels: feature.labels ?? null,
|
|
3514
|
+
surfaceUrl: feature.surfaceUrl,
|
|
3515
|
+
icon: feature.icon,
|
|
3516
|
+
placement: feature.placement,
|
|
3517
|
+
entry: feature.entry ?? { kind: "sidebar" },
|
|
3518
|
+
pinnedSummary: feature.pinnedSummary ?? null,
|
|
3519
|
+
detailTabs: feature.detailTabs ?? [],
|
|
3520
|
+
hostActions: feature.hostActions ?? [],
|
|
3521
|
+
modelSelector: feature.modelSelector ?? null,
|
|
3522
|
+
}));
|
|
3523
|
+
return `(${install.toString()})(${JSON.stringify({
|
|
3524
|
+
version: RUNTIME_VERSION,
|
|
3525
|
+
sessionId: runtimeSessionId,
|
|
3526
|
+
bindingName,
|
|
3527
|
+
bindingToken,
|
|
3528
|
+
summaryLayout: RESPONSIVE_SUMMARY_LAYOUT,
|
|
3529
|
+
features: runtimeFeatures,
|
|
3530
|
+
})})`;
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
export function createRuntimeReadyContract({
|
|
3534
|
+
pid,
|
|
3535
|
+
features = [],
|
|
3536
|
+
plugins = [],
|
|
3537
|
+
failures = [],
|
|
3538
|
+
renderer = {},
|
|
3539
|
+
runtimeSessionId,
|
|
3540
|
+
}) {
|
|
3541
|
+
const discovered = Boolean(renderer.discovered);
|
|
3542
|
+
const injectedTargets = Math.max(0, Number(renderer.injectedTargets) || 0);
|
|
3543
|
+
const active = Boolean(renderer.active) && discovered && injectedTargets > 0;
|
|
3544
|
+
return {
|
|
3545
|
+
pid,
|
|
3546
|
+
features: features.map((feature) => typeof feature === "string" ? feature : feature.id),
|
|
3547
|
+
failures,
|
|
3548
|
+
runtimeSessionId,
|
|
3549
|
+
plugins: plugins.map((plugin) => ({
|
|
3550
|
+
featureId: plugin.featureId,
|
|
3551
|
+
state: active && (plugin.state === "configured" || plugin.state === "ready")
|
|
3552
|
+
? "injected"
|
|
3553
|
+
: plugin.state,
|
|
3554
|
+
owned: Boolean(plugin.owned),
|
|
3555
|
+
health: plugin.health ?? null,
|
|
3556
|
+
failure: plugin.failure ?? null,
|
|
3557
|
+
})),
|
|
3558
|
+
renderer: {
|
|
3559
|
+
discovered,
|
|
3560
|
+
injectedTargets,
|
|
3561
|
+
active,
|
|
3562
|
+
runtimeSessionId: renderer.runtimeSessionId ?? runtimeSessionId,
|
|
3563
|
+
},
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
async function connect(url) {
|
|
3568
|
+
const socket = new WebSocket(url);
|
|
3569
|
+
const pending = new Map();
|
|
3570
|
+
const eventWaiters = new Map();
|
|
3571
|
+
const eventListeners = new Map();
|
|
3572
|
+
let nextId = 1;
|
|
3573
|
+
|
|
3574
|
+
await new Promise((resolve, reject) => {
|
|
3575
|
+
socket.addEventListener("open", resolve, { once: true });
|
|
3576
|
+
socket.addEventListener("error", reject, { once: true });
|
|
3577
|
+
});
|
|
3578
|
+
socket.addEventListener("message", (event) => {
|
|
3579
|
+
const message = JSON.parse(String(event.data));
|
|
3580
|
+
if (!message.id) {
|
|
3581
|
+
for (const listener of eventListeners.get(message.method) ?? []) listener(message.params);
|
|
3582
|
+
const waiters = eventWaiters.get(message.method) ?? [];
|
|
3583
|
+
eventWaiters.delete(message.method);
|
|
3584
|
+
for (const waiter of waiters) waiter.resolve(message.params);
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3587
|
+
const request = pending.get(message.id);
|
|
3588
|
+
if (!request) return;
|
|
3589
|
+
pending.delete(message.id);
|
|
3590
|
+
if (message.error) request.reject(new Error(message.error.message));
|
|
3591
|
+
else request.resolve(message.result);
|
|
3592
|
+
});
|
|
3593
|
+
socket.addEventListener("close", () => {
|
|
3594
|
+
const error = new Error("CDP WebSocket closed");
|
|
3595
|
+
for (const request of pending.values()) request.reject(error);
|
|
3596
|
+
pending.clear();
|
|
3597
|
+
for (const waiters of eventWaiters.values()) {
|
|
3598
|
+
for (const waiter of waiters) waiter.reject(error);
|
|
3599
|
+
}
|
|
3600
|
+
eventWaiters.clear();
|
|
3601
|
+
eventListeners.clear();
|
|
3602
|
+
});
|
|
3603
|
+
|
|
3604
|
+
return {
|
|
3605
|
+
socket,
|
|
3606
|
+
send(method, params = {}) {
|
|
3607
|
+
const id = nextId++;
|
|
3608
|
+
return new Promise((resolve, reject) => {
|
|
3609
|
+
pending.set(id, { resolve, reject });
|
|
3610
|
+
socket.send(JSON.stringify({ id, method, params }));
|
|
3611
|
+
});
|
|
3612
|
+
},
|
|
3613
|
+
on(method, listener) {
|
|
3614
|
+
const listeners = eventListeners.get(method) ?? new Set();
|
|
3615
|
+
listeners.add(listener);
|
|
3616
|
+
eventListeners.set(method, listeners);
|
|
3617
|
+
return () => listeners.delete(listener);
|
|
3618
|
+
},
|
|
3619
|
+
waitFor(method, timeoutMs) {
|
|
3620
|
+
return new Promise((resolve, reject) => {
|
|
3621
|
+
const waiters = eventWaiters.get(method) ?? [];
|
|
3622
|
+
const waiter = {
|
|
3623
|
+
resolve(value) {
|
|
3624
|
+
clearTimeout(timeout);
|
|
3625
|
+
resolve(value);
|
|
3626
|
+
},
|
|
3627
|
+
reject,
|
|
3628
|
+
};
|
|
3629
|
+
const timeout = setTimeout(() => {
|
|
3630
|
+
const remaining = (eventWaiters.get(method) ?? []).filter((item) => item !== waiter);
|
|
3631
|
+
if (remaining.length) eventWaiters.set(method, remaining);
|
|
3632
|
+
else eventWaiters.delete(method);
|
|
3633
|
+
reject(new Error(`Timed out waiting for CDP event ${method}`));
|
|
3634
|
+
}, timeoutMs);
|
|
3635
|
+
waiters.push(waiter);
|
|
3636
|
+
eventWaiters.set(method, waiters);
|
|
3637
|
+
});
|
|
3638
|
+
},
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
async function waitForExpression(client, expression, timeoutMs = 20_000) {
|
|
3643
|
+
const deadline = Date.now() + timeoutMs;
|
|
3644
|
+
while (Date.now() < deadline) {
|
|
3645
|
+
try {
|
|
3646
|
+
const result = await client.send("Runtime.evaluate", { expression, returnByValue: true });
|
|
3647
|
+
if (result.result?.value) return;
|
|
3648
|
+
} catch {
|
|
3649
|
+
// Reloading briefly destroys the previous execution context.
|
|
3650
|
+
}
|
|
3651
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
3652
|
+
}
|
|
3653
|
+
throw new Error(`Timed out waiting for renderer state: ${expression}`);
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3656
|
+
function evaluationValue(result) {
|
|
3657
|
+
if (result.exceptionDetails) {
|
|
3658
|
+
throw new Error(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text);
|
|
3659
|
+
}
|
|
3660
|
+
return result.result?.value;
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
function requireCdpWebSocketUrl(version, expectedCdpPort) {
|
|
3664
|
+
const value = typeof version?.webSocketDebuggerUrl === "string"
|
|
3665
|
+
? version.webSocketDebuggerUrl.trim()
|
|
3666
|
+
: "";
|
|
3667
|
+
if (!value) {
|
|
3668
|
+
throw new Error("Codex CDP /json/version is missing webSocketDebuggerUrl");
|
|
3669
|
+
}
|
|
3670
|
+
let parsed;
|
|
3671
|
+
try {
|
|
3672
|
+
parsed = new URL(value);
|
|
3673
|
+
} catch {
|
|
3674
|
+
throw new Error("Codex CDP webSocketDebuggerUrl is invalid");
|
|
3675
|
+
}
|
|
3676
|
+
if (!new Set(["ws:", "wss:"]).has(parsed.protocol) || !parsed.host) {
|
|
3677
|
+
throw new Error("Codex CDP webSocketDebuggerUrl must use ws: or wss:");
|
|
3678
|
+
}
|
|
3679
|
+
const hostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
3680
|
+
if (!new Set(["127.0.0.1", "localhost", "::1"]).has(hostname)) {
|
|
3681
|
+
throw new Error("Codex CDP webSocketDebuggerUrl must use a loopback host");
|
|
3682
|
+
}
|
|
3683
|
+
const configuredPort = Number(expectedCdpPort);
|
|
3684
|
+
const effectivePort = parsed.port
|
|
3685
|
+
? Number(parsed.port)
|
|
3686
|
+
: parsed.protocol === "wss:" ? 443 : 80;
|
|
3687
|
+
if (!Number.isInteger(configuredPort) || effectivePort !== configuredPort) {
|
|
3688
|
+
throw new Error(`Codex CDP webSocketDebuggerUrl must use configured CDP port ${expectedCdpPort}`);
|
|
3689
|
+
}
|
|
3690
|
+
return value;
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
function processChildHasExited(child) {
|
|
3694
|
+
return child && (
|
|
3695
|
+
(child.exitCode !== null && child.exitCode !== undefined)
|
|
3696
|
+
|| (child.signalCode !== null && child.signalCode !== undefined)
|
|
3697
|
+
);
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
function waitForProcessChildExit(child, timeoutMs) {
|
|
3701
|
+
if (processChildHasExited(child)) return Promise.resolve(true);
|
|
3702
|
+
return new Promise((resolve) => {
|
|
3703
|
+
let settled = false;
|
|
3704
|
+
let timer;
|
|
3705
|
+
const finish = (exited) => {
|
|
3706
|
+
if (settled) return;
|
|
3707
|
+
settled = true;
|
|
3708
|
+
clearTimeout(timer);
|
|
3709
|
+
child.removeListener?.("exit", onExit);
|
|
3710
|
+
resolve(exited || processChildHasExited(child));
|
|
3711
|
+
};
|
|
3712
|
+
const onExit = () => finish(true);
|
|
3713
|
+
child.once?.("exit", onExit);
|
|
3714
|
+
timer = setTimeout(() => finish(false), timeoutMs);
|
|
3715
|
+
});
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
export class CodexRuntime {
|
|
3719
|
+
constructor({
|
|
3720
|
+
features,
|
|
3721
|
+
profileDir,
|
|
3722
|
+
appPath = "/Applications/ChatGPT.app/Contents/MacOS/ChatGPT",
|
|
3723
|
+
cdpPort = 9231,
|
|
3724
|
+
logger = console,
|
|
3725
|
+
fetchImpl = globalThis.fetch,
|
|
3726
|
+
spawnProcess = spawn,
|
|
3727
|
+
connectClient = connect,
|
|
3728
|
+
sleep = (timeoutMs) => new Promise((resolve) => setTimeout(resolve, timeoutMs)),
|
|
3729
|
+
availabilityTimeoutMs = 20_000,
|
|
3730
|
+
availabilityPollMs = 500,
|
|
3731
|
+
relaunchBaseDelayMs = 250,
|
|
3732
|
+
relaunchMaxDelayMs = 2_000,
|
|
3733
|
+
monitorIntervalMs = 2_000,
|
|
3734
|
+
runtimeSessionId = randomBytes(16).toString("hex"),
|
|
3735
|
+
onStatusChange = null,
|
|
3736
|
+
onManagedCodexPidChange = null,
|
|
3737
|
+
}) {
|
|
3738
|
+
this.features = features;
|
|
3739
|
+
this.profileDir = profileDir;
|
|
3740
|
+
this.appPath = appPath;
|
|
3741
|
+
this.cdpPort = cdpPort;
|
|
3742
|
+
this.logger = logger;
|
|
3743
|
+
this.fetchImpl = fetchImpl;
|
|
3744
|
+
this.spawnProcess = spawnProcess;
|
|
3745
|
+
this.connectClient = connectClient;
|
|
3746
|
+
this.sleep = sleep;
|
|
3747
|
+
this.availabilityTimeoutMs = availabilityTimeoutMs;
|
|
3748
|
+
this.availabilityPollMs = availabilityPollMs;
|
|
3749
|
+
this.relaunchBaseDelayMs = relaunchBaseDelayMs;
|
|
3750
|
+
this.relaunchMaxDelayMs = relaunchMaxDelayMs;
|
|
3751
|
+
this.monitorIntervalMs = monitorIntervalMs;
|
|
3752
|
+
this.runtimeSessionId = runtimeSessionId;
|
|
3753
|
+
this.onStatusChange = onStatusChange;
|
|
3754
|
+
this.onManagedCodexPidChange = onManagedCodexPidChange;
|
|
3755
|
+
this.injectedTargets = new Set();
|
|
3756
|
+
this.clients = new Map();
|
|
3757
|
+
this.scriptIds = new Map();
|
|
3758
|
+
this.bindingTokens = new Map();
|
|
3759
|
+
this.hostActionChains = new Map();
|
|
3760
|
+
this.refreshPromise = null;
|
|
3761
|
+
this.stopping = false;
|
|
3762
|
+
this.monitorPromise = null;
|
|
3763
|
+
this.browserClient = null;
|
|
3764
|
+
this.targetRefreshQueued = false;
|
|
3765
|
+
this.appPid = null;
|
|
3766
|
+
this.managedCodexChild = null;
|
|
3767
|
+
this.recoveryPromise = null;
|
|
3768
|
+
this.monitorTimer = null;
|
|
3769
|
+
this.monitorWake = null;
|
|
3770
|
+
this.stopPromise = null;
|
|
3771
|
+
this.pidChangePromise = Promise.resolve();
|
|
3772
|
+
this.pidChangeQueue = null;
|
|
3773
|
+
this.rendererDiscovered = false;
|
|
3774
|
+
this.lastRendererStatus = null;
|
|
3775
|
+
this.codexLifecycleManaged = false;
|
|
3776
|
+
}
|
|
3777
|
+
|
|
3778
|
+
async start() {
|
|
3779
|
+
this.stopping = false;
|
|
3780
|
+
await mkdir(this.profileDir, { recursive: true });
|
|
3781
|
+
this.codexLifecycleManaged = true;
|
|
3782
|
+
if (!(await this.isAvailable())) {
|
|
3783
|
+
this.launchManagedCodex();
|
|
3784
|
+
await this.waitUntilAvailable();
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
await this.startTargetDiscovery();
|
|
3788
|
+
await this.refresh();
|
|
3789
|
+
await this.flushManagedCodexPidChange();
|
|
3790
|
+
this.monitorPromise = this.monitor();
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
getRendererStatus() {
|
|
3794
|
+
const injectedTargets = this.injectedTargets.size;
|
|
3795
|
+
return {
|
|
3796
|
+
discovered: this.rendererDiscovered,
|
|
3797
|
+
injectedTargets,
|
|
3798
|
+
active: !this.stopping && this.rendererDiscovered && injectedTargets > 0,
|
|
3799
|
+
runtimeSessionId: this.runtimeSessionId,
|
|
3800
|
+
};
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3803
|
+
notifyStatusChange() {
|
|
3804
|
+
const status = this.getRendererStatus();
|
|
3805
|
+
const previous = this.lastRendererStatus;
|
|
3806
|
+
if (
|
|
3807
|
+
previous
|
|
3808
|
+
&& previous.discovered === status.discovered
|
|
3809
|
+
&& previous.injectedTargets === status.injectedTargets
|
|
3810
|
+
&& previous.active === status.active
|
|
3811
|
+
&& previous.runtimeSessionId === status.runtimeSessionId
|
|
3812
|
+
) return;
|
|
3813
|
+
this.lastRendererStatus = status;
|
|
3814
|
+
try {
|
|
3815
|
+
this.onStatusChange?.(status);
|
|
3816
|
+
} catch (error) {
|
|
3817
|
+
this.logger.warn?.("Runtime status update failed", error.message);
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
notifyManagedCodexPidChange(pid) {
|
|
3822
|
+
const invoke = () => {
|
|
3823
|
+
try {
|
|
3824
|
+
return this.onManagedCodexPidChange?.(pid);
|
|
3825
|
+
} catch (error) {
|
|
3826
|
+
this.logger.warn?.("Managed Codex PID update failed", error.message);
|
|
3827
|
+
return null;
|
|
3828
|
+
}
|
|
3829
|
+
};
|
|
3830
|
+
const next = this.pidChangeQueue
|
|
3831
|
+
? this.pidChangeQueue.catch(() => {}).then(invoke)
|
|
3832
|
+
: invoke();
|
|
3833
|
+
const tracked = Promise.resolve(next).catch((error) => {
|
|
3834
|
+
this.logger.warn?.("Managed Codex PID update failed", error.message);
|
|
3835
|
+
});
|
|
3836
|
+
const settled = tracked.finally(() => {
|
|
3837
|
+
if (this.pidChangeQueue === settled) this.pidChangeQueue = null;
|
|
3838
|
+
});
|
|
3839
|
+
this.pidChangeQueue = settled;
|
|
3840
|
+
this.pidChangePromise = settled;
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
async flushManagedCodexPidChange() {
|
|
3844
|
+
await this.pidChangePromise;
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
clearRendererState() {
|
|
3848
|
+
for (const client of this.clients.values()) client.socket.close();
|
|
3849
|
+
this.clients.clear();
|
|
3850
|
+
this.scriptIds.clear();
|
|
3851
|
+
this.bindingTokens.clear();
|
|
3852
|
+
this.hostActionChains.clear();
|
|
3853
|
+
this.injectedTargets.clear();
|
|
3854
|
+
this.rendererDiscovered = false;
|
|
3855
|
+
this.browserClient?.socket.close();
|
|
3856
|
+
this.browserClient = null;
|
|
3857
|
+
this.notifyStatusChange();
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
markCodexUnavailable(error) {
|
|
3861
|
+
this.clearRendererState();
|
|
3862
|
+
if (this.codexLifecycleManaged && !this.stopping) {
|
|
3863
|
+
this.scheduleCodexRecovery();
|
|
3864
|
+
}
|
|
3865
|
+
return error;
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
launchManagedCodex() {
|
|
3869
|
+
this.codexLifecycleManaged = true;
|
|
3870
|
+
const child = this.spawnProcess(
|
|
3871
|
+
this.appPath,
|
|
3872
|
+
[
|
|
3873
|
+
`--user-data-dir=${this.profileDir}`,
|
|
3874
|
+
"--remote-debugging-address=127.0.0.1",
|
|
3875
|
+
`--remote-debugging-port=${this.cdpPort}`,
|
|
3876
|
+
`--remote-allow-origins=http://127.0.0.1:${this.cdpPort}`,
|
|
3877
|
+
"--no-first-run",
|
|
3878
|
+
],
|
|
3879
|
+
{ detached: true, stdio: "ignore" },
|
|
3880
|
+
);
|
|
3881
|
+
this.managedCodexChild = child;
|
|
3882
|
+
this.appPid = child.pid ?? null;
|
|
3883
|
+
this.notifyManagedCodexPidChange(this.appPid);
|
|
3884
|
+
let handled = false;
|
|
3885
|
+
const onExit = () => {
|
|
3886
|
+
if (handled) return;
|
|
3887
|
+
handled = true;
|
|
3888
|
+
if (this.managedCodexChild !== child) return;
|
|
3889
|
+
this.managedCodexChild = null;
|
|
3890
|
+
this.appPid = null;
|
|
3891
|
+
this.notifyManagedCodexPidChange(null);
|
|
3892
|
+
if (this.stopping) return;
|
|
3893
|
+
this.clearRendererState();
|
|
3894
|
+
this.scheduleCodexRecovery();
|
|
3895
|
+
};
|
|
3896
|
+
child.once?.("exit", onExit);
|
|
3897
|
+
child.once?.("error", (error) => {
|
|
3898
|
+
this.logger.warn?.("Managed Codex exited before CDP became available", error.message);
|
|
3899
|
+
onExit();
|
|
3900
|
+
});
|
|
3901
|
+
child.unref?.();
|
|
3902
|
+
return child;
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
scheduleCodexRecovery() {
|
|
3906
|
+
if (this.stopping || this.recoveryPromise) return this.recoveryPromise;
|
|
3907
|
+
const recovery = this.recoverManagedCodex();
|
|
3908
|
+
let trackedRecovery;
|
|
3909
|
+
trackedRecovery = recovery.finally(() => {
|
|
3910
|
+
if (this.recoveryPromise === trackedRecovery) this.recoveryPromise = null;
|
|
3911
|
+
});
|
|
3912
|
+
this.recoveryPromise = trackedRecovery;
|
|
3913
|
+
return trackedRecovery;
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
async recoverManagedCodex() {
|
|
3917
|
+
let attempt = 0;
|
|
3918
|
+
while (!this.stopping) {
|
|
3919
|
+
if (this.managedCodexChild && !processChildHasExited(this.managedCodexChild)) {
|
|
3920
|
+
await this.sleep(Math.max(1, Math.min(this.relaunchMaxDelayMs, this.relaunchBaseDelayMs || 1)));
|
|
3921
|
+
continue;
|
|
3922
|
+
}
|
|
3923
|
+
if (await this.isAvailable()) {
|
|
3924
|
+
try {
|
|
3925
|
+
await this.startTargetDiscovery();
|
|
3926
|
+
await this.refresh();
|
|
3927
|
+
await this.flushManagedCodexPidChange();
|
|
3928
|
+
return;
|
|
3929
|
+
} catch (error) {
|
|
3930
|
+
if (this.stopping) return;
|
|
3931
|
+
if (await this.isAvailable()) {
|
|
3932
|
+
this.logger.warn?.("Managed Codex CDP is still available; retrying discovery", error.message);
|
|
3933
|
+
await this.sleep(Math.max(1, Math.min(this.relaunchMaxDelayMs, this.relaunchBaseDelayMs || 1)));
|
|
3934
|
+
continue;
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
const delay = Math.min(
|
|
3939
|
+
this.relaunchBaseDelayMs * (2 ** attempt),
|
|
3940
|
+
this.relaunchMaxDelayMs,
|
|
3941
|
+
);
|
|
3942
|
+
if (delay > 0) await this.sleep(delay);
|
|
3943
|
+
if (this.stopping) return;
|
|
3944
|
+
let child;
|
|
3945
|
+
try {
|
|
3946
|
+
child = this.launchManagedCodex();
|
|
3947
|
+
await this.waitUntilAvailable();
|
|
3948
|
+
await this.startTargetDiscovery();
|
|
3949
|
+
await this.refresh();
|
|
3950
|
+
await this.flushManagedCodexPidChange();
|
|
3951
|
+
return;
|
|
3952
|
+
} catch (error) {
|
|
3953
|
+
this.logger.warn?.("Managed Codex recovery retry failed", error.message);
|
|
3954
|
+
if (
|
|
3955
|
+
child
|
|
3956
|
+
&& this.managedCodexChild === child
|
|
3957
|
+
&& child.exitCode === null
|
|
3958
|
+
&& child.signalCode === null
|
|
3959
|
+
) {
|
|
3960
|
+
child.kill?.("SIGTERM");
|
|
3961
|
+
const exited = await waitForProcessChildExit(child, 500);
|
|
3962
|
+
if (!exited && !processChildHasExited(child)) {
|
|
3963
|
+
child.kill?.("SIGKILL");
|
|
3964
|
+
await waitForProcessChildExit(child, 500);
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
attempt += 1;
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3972
|
+
queueTargetRefresh() {
|
|
3973
|
+
if (this.targetRefreshQueued || this.stopping) return;
|
|
3974
|
+
this.targetRefreshQueued = true;
|
|
3975
|
+
queueMicrotask(() => {
|
|
3976
|
+
this.targetRefreshQueued = false;
|
|
3977
|
+
if (this.stopping) return;
|
|
3978
|
+
this.refresh().catch((error) => {
|
|
3979
|
+
this.logger.warn?.("Waiting for Codex renderer", error.message);
|
|
3980
|
+
});
|
|
3981
|
+
});
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3984
|
+
async startTargetDiscovery() {
|
|
3985
|
+
const response = await this.fetchImpl(`http://127.0.0.1:${this.cdpPort}/json/version`);
|
|
3986
|
+
if (!response?.ok) {
|
|
3987
|
+
throw new Error(`Codex CDP version returned HTTP ${response?.status ?? "error"}`);
|
|
3988
|
+
}
|
|
3989
|
+
const version = await response.json();
|
|
3990
|
+
const webSocketDebuggerUrl = requireCdpWebSocketUrl(version, this.cdpPort);
|
|
3991
|
+
let client;
|
|
3992
|
+
try {
|
|
3993
|
+
client = await this.connectClient(webSocketDebuggerUrl);
|
|
3994
|
+
for (const method of [
|
|
3995
|
+
"Target.targetCreated",
|
|
3996
|
+
"Target.targetInfoChanged",
|
|
3997
|
+
"Target.targetDestroyed",
|
|
3998
|
+
]) {
|
|
3999
|
+
client.on(method, () => this.queueTargetRefresh());
|
|
4000
|
+
}
|
|
4001
|
+
await client.send("Target.setDiscoverTargets", { discover: true });
|
|
4002
|
+
this.browserClient = client;
|
|
4003
|
+
} catch (error) {
|
|
4004
|
+
client?.socket.close();
|
|
4005
|
+
if (this.browserClient === client) this.browserClient = null;
|
|
4006
|
+
this.logger.warn?.("CDP target discovery unavailable; using /json/list polling", error.message);
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
|
|
4010
|
+
refresh() {
|
|
4011
|
+
if (this.refreshPromise) return this.refreshPromise;
|
|
4012
|
+
const refresh = this.refreshTargets()
|
|
4013
|
+
.catch((error) => {
|
|
4014
|
+
if (!this.rendererDiscovered) this.notifyStatusChange();
|
|
4015
|
+
throw error;
|
|
4016
|
+
})
|
|
4017
|
+
.finally(() => {
|
|
4018
|
+
if (this.refreshPromise === refresh) this.refreshPromise = null;
|
|
4019
|
+
});
|
|
4020
|
+
this.refreshPromise = refresh;
|
|
4021
|
+
return refresh;
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
async monitor() {
|
|
4025
|
+
while (!this.stopping) {
|
|
4026
|
+
await new Promise((resolve) => {
|
|
4027
|
+
this.monitorWake = () => {
|
|
4028
|
+
this.monitorWake = null;
|
|
4029
|
+
if (this.monitorTimer) clearTimeout(this.monitorTimer);
|
|
4030
|
+
this.monitorTimer = null;
|
|
4031
|
+
resolve();
|
|
4032
|
+
};
|
|
4033
|
+
this.monitorTimer = setTimeout(() => {
|
|
4034
|
+
this.monitorTimer = null;
|
|
4035
|
+
this.monitorWake = null;
|
|
4036
|
+
resolve();
|
|
4037
|
+
}, this.monitorIntervalMs);
|
|
4038
|
+
});
|
|
4039
|
+
if (this.stopping) break;
|
|
4040
|
+
try {
|
|
4041
|
+
await this.refresh();
|
|
4042
|
+
} catch (error) {
|
|
4043
|
+
this.logger.warn?.("Waiting for Codex renderer", error.message);
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
async isAvailable() {
|
|
4049
|
+
try {
|
|
4050
|
+
const response = await this.fetchImpl(`http://127.0.0.1:${this.cdpPort}/json/version`);
|
|
4051
|
+
if (!response?.ok) return false;
|
|
4052
|
+
requireCdpWebSocketUrl(await response.json(), this.cdpPort);
|
|
4053
|
+
return true;
|
|
4054
|
+
} catch {
|
|
4055
|
+
return false;
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
|
|
4059
|
+
async waitUntilAvailable() {
|
|
4060
|
+
const deadline = Date.now() + this.availabilityTimeoutMs;
|
|
4061
|
+
while (Date.now() < deadline) {
|
|
4062
|
+
if (this.stopping) throw new Error("RuntimeHost is stopping");
|
|
4063
|
+
if (await this.isAvailable()) return;
|
|
4064
|
+
await this.sleep(this.availabilityPollMs);
|
|
4065
|
+
}
|
|
4066
|
+
throw new Error(`Codex CDP did not become available on port ${this.cdpPort}`);
|
|
4067
|
+
}
|
|
4068
|
+
|
|
4069
|
+
enqueueHostAction(targetId, operation) {
|
|
4070
|
+
const previous = this.hostActionChains.get(targetId) ?? Promise.resolve();
|
|
4071
|
+
const next = previous.then(operation, operation).finally(() => {
|
|
4072
|
+
if (this.hostActionChains.get(targetId) === next) this.hostActionChains.delete(targetId);
|
|
4073
|
+
});
|
|
4074
|
+
this.hostActionChains.set(targetId, next);
|
|
4075
|
+
return next;
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
async resolveHostAction(client, requestId, response) {
|
|
4079
|
+
const expression = `window.__codexPersonalRuntime?.resolveHostAction(${JSON.stringify(
|
|
4080
|
+
requestId,
|
|
4081
|
+
)}, ${JSON.stringify(response)})`;
|
|
4082
|
+
await client.send("Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true });
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
async attachFileToComposer(client, filePath, requestId) {
|
|
4086
|
+
if (typeof filePath !== "string" || !path.isAbsolute(filePath)) {
|
|
4087
|
+
throw new Error("An absolute file path is required");
|
|
4088
|
+
}
|
|
4089
|
+
const info = await stat(filePath);
|
|
4090
|
+
if (!info.isFile()) throw new Error("Only regular files can be attached");
|
|
4091
|
+
const fileName = path.basename(filePath);
|
|
4092
|
+
const marker = `codex-personal-${requestId}`;
|
|
4093
|
+
|
|
4094
|
+
try {
|
|
4095
|
+
evaluationValue(await client.send("Runtime.evaluate", {
|
|
4096
|
+
expression: `(() => {
|
|
4097
|
+
document.querySelectorAll('[data-codex-personal-file-input]').forEach((input) => input.remove());
|
|
4098
|
+
const input = document.createElement("input");
|
|
4099
|
+
input.type = "file";
|
|
4100
|
+
input.multiple = true;
|
|
4101
|
+
input.dataset.codexPersonalFileInput = ${JSON.stringify(marker)};
|
|
4102
|
+
input.style.display = "none";
|
|
4103
|
+
document.body.append(input);
|
|
4104
|
+
return true;
|
|
4105
|
+
})()`,
|
|
4106
|
+
returnByValue: true,
|
|
4107
|
+
}));
|
|
4108
|
+
const { root } = await client.send("DOM.getDocument", { depth: 0 });
|
|
4109
|
+
const { nodeId } = await client.send("DOM.querySelector", {
|
|
4110
|
+
nodeId: root.nodeId,
|
|
4111
|
+
selector: `[data-codex-personal-file-input="${marker}"]`,
|
|
4112
|
+
});
|
|
4113
|
+
if (!nodeId) throw new Error("The temporary file bridge could not be resolved");
|
|
4114
|
+
await client.send("DOM.setFileInputFiles", { files: [filePath], nodeId });
|
|
4115
|
+
|
|
4116
|
+
const attachment = evaluationValue(await client.send("Runtime.evaluate", {
|
|
4117
|
+
expression: `(async () => {
|
|
4118
|
+
const marker = ${JSON.stringify(marker)};
|
|
4119
|
+
const fileName = ${JSON.stringify(fileName)};
|
|
4120
|
+
const input = Array.from(document.querySelectorAll('[data-codex-personal-file-input]'))
|
|
4121
|
+
.find((candidate) => candidate.dataset.codexPersonalFileInput === marker);
|
|
4122
|
+
const composer = document.querySelector('[data-codex-composer="true"][contenteditable="true"]');
|
|
4123
|
+
if (!input?.files?.length || !composer) return { attached: false, reason: "Composer unavailable" };
|
|
4124
|
+
const transfer = new DataTransfer();
|
|
4125
|
+
for (const file of input.files) transfer.items.add(file);
|
|
4126
|
+
for (const type of ["dragenter", "dragover", "drop"]) {
|
|
4127
|
+
composer.dispatchEvent(new DragEvent(type, {
|
|
4128
|
+
bubbles: true,
|
|
4129
|
+
cancelable: true,
|
|
4130
|
+
composed: true,
|
|
4131
|
+
dataTransfer: transfer,
|
|
4132
|
+
}));
|
|
4133
|
+
}
|
|
4134
|
+
await new Promise((resolve) => setTimeout(resolve, 850));
|
|
4135
|
+
const attached = Boolean(document.querySelector(
|
|
4136
|
+
'button[aria-label=' + JSON.stringify('Remove ' + fileName) + ']'
|
|
4137
|
+
));
|
|
4138
|
+
return { attached, reason: attached ? null : "Native attachment chip was not observed" };
|
|
4139
|
+
})()`,
|
|
4140
|
+
awaitPromise: true,
|
|
4141
|
+
returnByValue: true,
|
|
4142
|
+
}));
|
|
4143
|
+
|
|
4144
|
+
if (attachment?.attached) return { mode: "attach", name: fileName };
|
|
4145
|
+
const fallback = evaluationValue(await client.send("Runtime.evaluate", {
|
|
4146
|
+
expression: `window.__codexPersonalRuntime?.insertText(${JSON.stringify(filePath)})`,
|
|
4147
|
+
returnByValue: true,
|
|
4148
|
+
}));
|
|
4149
|
+
if (!fallback) throw new Error(attachment?.reason ?? "File attachment failed");
|
|
4150
|
+
return { mode: "insert-path", path: filePath, fallback: true };
|
|
4151
|
+
} finally {
|
|
4152
|
+
await client.send("Runtime.evaluate", {
|
|
4153
|
+
expression: `document.querySelectorAll('[data-codex-personal-file-input]').forEach((input) => input.remove())`,
|
|
4154
|
+
}).catch(() => {});
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
async handleBindingCalled(targetId, client, params) {
|
|
4159
|
+
if (params.name !== HOST_BINDING_NAME) return;
|
|
4160
|
+
let request;
|
|
4161
|
+
try {
|
|
4162
|
+
request = JSON.parse(params.payload);
|
|
4163
|
+
} catch {
|
|
4164
|
+
return;
|
|
4165
|
+
}
|
|
4166
|
+
if (request.token !== this.bindingTokens.get(targetId) || typeof request.requestId !== "string") return;
|
|
4167
|
+
const feature = this.features.find((candidate) => candidate.id === request.featureId);
|
|
4168
|
+
if (!feature?.hostActions?.includes(request.action)) {
|
|
4169
|
+
await this.resolveHostAction(client, request.requestId, {
|
|
4170
|
+
ok: false,
|
|
4171
|
+
error: { code: "ACTION_NOT_ALLOWED", message: "Host action is not registered" },
|
|
4172
|
+
});
|
|
4173
|
+
return;
|
|
4174
|
+
}
|
|
4175
|
+
try {
|
|
4176
|
+
if (request.action !== "attach-file") throw new Error("Unsupported native Host action");
|
|
4177
|
+
const result = await this.attachFileToComposer(client, request.payload?.path, request.requestId);
|
|
4178
|
+
await this.resolveHostAction(client, request.requestId, { ok: true, result });
|
|
4179
|
+
} catch (error) {
|
|
4180
|
+
await this.resolveHostAction(client, request.requestId, {
|
|
4181
|
+
ok: false,
|
|
4182
|
+
error: { code: error.code ?? "ATTACH_FAILED", message: error.message },
|
|
4183
|
+
});
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
|
|
4187
|
+
async refreshTargets() {
|
|
4188
|
+
let response;
|
|
4189
|
+
try {
|
|
4190
|
+
response = await this.fetchImpl(`http://127.0.0.1:${this.cdpPort}/json/list`);
|
|
4191
|
+
} catch (error) {
|
|
4192
|
+
throw this.markCodexUnavailable(error);
|
|
4193
|
+
}
|
|
4194
|
+
if (!response?.ok) {
|
|
4195
|
+
throw this.markCodexUnavailable(
|
|
4196
|
+
new Error(`Codex target list returned HTTP ${response?.status ?? "error"}`),
|
|
4197
|
+
);
|
|
4198
|
+
}
|
|
4199
|
+
let targets;
|
|
4200
|
+
try {
|
|
4201
|
+
targets = await response.json();
|
|
4202
|
+
} catch (error) {
|
|
4203
|
+
throw this.markCodexUnavailable(error);
|
|
4204
|
+
}
|
|
4205
|
+
if (!Array.isArray(targets)) {
|
|
4206
|
+
throw this.markCodexUnavailable(new Error("Codex target list was not an array"));
|
|
4207
|
+
}
|
|
4208
|
+
const liveRendererIds = new Set(
|
|
4209
|
+
targets
|
|
4210
|
+
.filter((target) => target.type === "page" && target.url === "app://-/index.html")
|
|
4211
|
+
.map((target) => target.id),
|
|
4212
|
+
);
|
|
4213
|
+
let removedTarget = false;
|
|
4214
|
+
for (const id of this.injectedTargets) {
|
|
4215
|
+
if (liveRendererIds.has(id)) continue;
|
|
4216
|
+
removedTarget = true;
|
|
4217
|
+
this.clients.get(id)?.socket.close();
|
|
4218
|
+
this.clients.delete(id);
|
|
4219
|
+
this.scriptIds.delete(id);
|
|
4220
|
+
this.bindingTokens.delete(id);
|
|
4221
|
+
this.hostActionChains.delete(id);
|
|
4222
|
+
this.injectedTargets.delete(id);
|
|
4223
|
+
}
|
|
4224
|
+
const rendererDiscovered = liveRendererIds.size > 0;
|
|
4225
|
+
const rendererChanged = rendererDiscovered !== this.rendererDiscovered;
|
|
4226
|
+
this.rendererDiscovered = rendererDiscovered;
|
|
4227
|
+
if (removedTarget || rendererChanged) this.notifyStatusChange();
|
|
4228
|
+
|
|
4229
|
+
for (const target of targets) {
|
|
4230
|
+
if (target.type !== "page" || target.url !== "app://-/index.html" || this.injectedTargets.has(target.id)) {
|
|
4231
|
+
continue;
|
|
4232
|
+
}
|
|
4233
|
+
const client = await this.connectClient(target.webSocketDebuggerUrl);
|
|
4234
|
+
try {
|
|
4235
|
+
await Promise.all([
|
|
4236
|
+
client.send("Page.enable"),
|
|
4237
|
+
client.send("Runtime.enable"),
|
|
4238
|
+
client.send("DOM.enable"),
|
|
4239
|
+
]);
|
|
4240
|
+
await client.send("Page.setBypassCSP", { enabled: true });
|
|
4241
|
+
await client.send("Runtime.removeBinding", { name: HOST_BINDING_NAME }).catch(() => {});
|
|
4242
|
+
await client.send("Runtime.addBinding", { name: HOST_BINDING_NAME });
|
|
4243
|
+
const bindingToken = randomBytes(24).toString("hex");
|
|
4244
|
+
this.bindingTokens.set(target.id, bindingToken);
|
|
4245
|
+
client.on?.("Runtime.bindingCalled", (params) => {
|
|
4246
|
+
this.enqueueHostAction(target.id, () => this.handleBindingCalled(target.id, client, params))
|
|
4247
|
+
.catch((error) => this.logger.warn?.("Codex Host action failed", error.message));
|
|
4248
|
+
});
|
|
4249
|
+
const source = createInjectionSource(this.features, {
|
|
4250
|
+
bindingToken,
|
|
4251
|
+
runtimeSessionId: this.runtimeSessionId,
|
|
4252
|
+
});
|
|
4253
|
+
const script = await client.send("Page.addScriptToEvaluateOnNewDocument", { source });
|
|
4254
|
+
await waitForExpression(
|
|
4255
|
+
client,
|
|
4256
|
+
`document.readyState === "interactive" || document.readyState === "complete"`,
|
|
4257
|
+
);
|
|
4258
|
+
await client.send("Page.setBypassCSP", { enabled: true });
|
|
4259
|
+
await client.send("Runtime.evaluate", { expression: source, awaitPromise: true });
|
|
4260
|
+
await waitForExpression(
|
|
4261
|
+
client,
|
|
4262
|
+
`window.__codexPersonalRuntime?.version === ${RUNTIME_VERSION}`
|
|
4263
|
+
+ ` && window.__codexPersonalRuntime?.sessionId === ${JSON.stringify(this.runtimeSessionId)}`,
|
|
4264
|
+
);
|
|
4265
|
+
this.clients.set(target.id, client);
|
|
4266
|
+
this.scriptIds.set(target.id, script.identifier);
|
|
4267
|
+
this.injectedTargets.add(target.id);
|
|
4268
|
+
this.notifyStatusChange();
|
|
4269
|
+
} catch (error) {
|
|
4270
|
+
this.bindingTokens.delete(target.id);
|
|
4271
|
+
client.socket.close();
|
|
4272
|
+
throw error;
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
|
|
4277
|
+
async stop() {
|
|
4278
|
+
if (this.stopPromise) return this.stopPromise;
|
|
4279
|
+
this.stopPromise = (async () => {
|
|
4280
|
+
this.stopping = true;
|
|
4281
|
+
this.targetRefreshQueued = false;
|
|
4282
|
+
this.monitorWake?.();
|
|
4283
|
+
await this.recoveryPromise?.catch(() => {});
|
|
4284
|
+
await this.monitorPromise?.catch(() => {});
|
|
4285
|
+
await Promise.all(Array.from(this.clients, async ([targetId, client]) => {
|
|
4286
|
+
const identifier = this.scriptIds.get(targetId);
|
|
4287
|
+
await client.send("Runtime.evaluate", {
|
|
4288
|
+
expression: "window.__codexPersonalRuntime?.dispose?.()",
|
|
4289
|
+
awaitPromise: true,
|
|
4290
|
+
returnByValue: true,
|
|
4291
|
+
}).catch(() => {});
|
|
4292
|
+
if (identifier) {
|
|
4293
|
+
await client.send("Page.removeScriptToEvaluateOnNewDocument", { identifier }).catch(() => {});
|
|
4294
|
+
}
|
|
4295
|
+
await client.send("Runtime.removeBinding", { name: HOST_BINDING_NAME }).catch(() => {});
|
|
4296
|
+
client.socket.close();
|
|
4297
|
+
}));
|
|
4298
|
+
this.browserClient?.socket.close();
|
|
4299
|
+
this.browserClient = null;
|
|
4300
|
+
this.clients.clear();
|
|
4301
|
+
this.scriptIds.clear();
|
|
4302
|
+
this.bindingTokens.clear();
|
|
4303
|
+
this.hostActionChains.clear();
|
|
4304
|
+
this.injectedTargets.clear();
|
|
4305
|
+
this.rendererDiscovered = false;
|
|
4306
|
+
this.notifyStatusChange();
|
|
4307
|
+
this.monitorPromise = null;
|
|
4308
|
+
})();
|
|
4309
|
+
return this.stopPromise;
|
|
4310
|
+
}
|
|
4311
|
+
}
|