codex-overleaf-link 1.8.5 → 1.9.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.
Files changed (74) hide show
  1. package/README.md +45 -21
  2. package/bin/codex-overleaf-link.mjs +58 -0
  3. package/extension/assets/icons/codex-overleaf-dialog-icon.png +0 -0
  4. package/extension/assets/icons/codex-overleaf-icon.png +0 -0
  5. package/extension/assets/icons/icon128.png +0 -0
  6. package/extension/assets/icons/icon16.png +0 -0
  7. package/extension/assets/icons/icon32.png +0 -0
  8. package/extension/assets/icons/icon48.png +0 -0
  9. package/extension/bootstrap/background.js +404 -0
  10. package/extension/bootstrap/manifest.template.json +51 -0
  11. package/extension/bootstrap/popup.css +26 -0
  12. package/extension/bootstrap/popup.html +38 -0
  13. package/extension/bootstrap/popup.js +44 -0
  14. package/extension/bootstrap/runtimeContext.js +2 -0
  15. package/extension/runtime-manifest.json +60 -0
  16. package/extension/src/background.js +532 -0
  17. package/extension/src/content/applyResultFormatters.js +387 -0
  18. package/extension/src/content/composerAttachments.js +522 -0
  19. package/extension/src/content/composerPanel.js +254 -0
  20. package/extension/src/content/contentRuntime.js +8895 -0
  21. package/extension/src/content/contextTray.js +686 -0
  22. package/extension/src/content/diagnosticsController.js +659 -0
  23. package/extension/src/content/diagnosticsPanel.js +342 -0
  24. package/extension/src/content/diffReviewPanel.js +797 -0
  25. package/extension/src/content/localSkillsPanel.js +280 -0
  26. package/extension/src/content/markdownText.js +606 -0
  27. package/extension/src/content/mirrorHealth.js +92 -0
  28. package/extension/src/content/modelPicker.js +489 -0
  29. package/extension/src/content/nativeChannel.js +62 -0
  30. package/extension/src/content/otWarmMirror.js +964 -0
  31. package/extension/src/content/otWarmMirrorController.js +174 -0
  32. package/extension/src/content/panelMaintenance.js +367 -0
  33. package/extension/src/content/panelRenderer.js +253 -0
  34. package/extension/src/content/recentProjects.js +1063 -0
  35. package/extension/src/content/reviewHunks.js +247 -0
  36. package/extension/src/content/runController.js +242 -0
  37. package/extension/src/content/runTimelineView.js +1126 -0
  38. package/extension/src/content/sessionManager.js +487 -0
  39. package/extension/src/content/sessionPanel.js +263 -0
  40. package/extension/src/content/settingsPanel.js +527 -0
  41. package/extension/src/content/themeController.js +91 -0
  42. package/extension/src/content/updateIdle.js +67 -0
  43. package/extension/src/content/writebackController.js +300 -0
  44. package/extension/src/content/writebackOrchestrator.js +850 -0
  45. package/extension/src/contentScript.js +8 -0
  46. package/extension/src/page/compileBridge.js +460 -0
  47. package/extension/src/page/overleafCapabilities.js +100 -0
  48. package/extension/src/page/overleafEditor.js +282 -0
  49. package/extension/src/page/overleafProjectSnapshot.js +234 -0
  50. package/extension/src/page/overleafRealtimeObserver.js +491 -0
  51. package/extension/src/page/pageBridgeCapability.js +71 -0
  52. package/extension/src/page/snapshotRouter.js +948 -0
  53. package/extension/src/page/trackedChangesLifecycle.js +1506 -0
  54. package/extension/src/page/treeOperations.js +1584 -0
  55. package/extension/src/page/writeGuard.js +142 -0
  56. package/extension/src/page/writebackRouter.js +1934 -0
  57. package/extension/src/pageBridge.js +2224 -0
  58. package/extension/src/popup.js +247 -0
  59. package/extension/src/shared/compatibility.js +1 -1
  60. package/extension/styles/panel.css +4048 -0
  61. package/native-host/src/index.js +9 -2
  62. package/native-host/src/launcher.js +2 -0
  63. package/native-host/src/managedInstall.js +393 -0
  64. package/native-host/src/managedLauncherRuntime.js +71 -0
  65. package/native-host/src/nativeEnvironment.js +4 -0
  66. package/native-host/src/nativeHostPlatform.js +16 -0
  67. package/native-host/src/runtimeInstaller.js +6 -1
  68. package/native-host/src/taskRunnerRuntime.js +8 -0
  69. package/native-host/src/updateArchive.js +253 -0
  70. package/native-host/src/updateManager.js +548 -0
  71. package/native-host/src/updateTrust.js +156 -0
  72. package/package.json +11 -2
  73. package/scripts/install-managed.mjs +64 -0
  74. package/scripts/uninstall-managed.mjs +48 -0
@@ -0,0 +1,489 @@
1
+ (function initCodexOverleafModelPicker() {
2
+ 'use strict';
3
+
4
+ // Model picker — the model/reasoning/speed catalog, discovery, selects, and
5
+ // config popover carved out of contentRuntime.js (v1.4.8 structural-debt
6
+ // phase 4). Code moved verbatim; runtime collaborators are factory-injected
7
+ // and mutable runtime state is read through lazy getters. The model
8
+ // discovery status lives here with the code that maintains it; the runtime
9
+ // reads it for the diagnostics bundle via getModelDiscovery().
10
+ function create(deps = {}) {
11
+ const {
12
+ tr,
13
+ tx,
14
+ getLocale,
15
+ sendBackgroundNative,
16
+ readSelectedSpeedInput,
17
+ getRenderedModelEntries,
18
+ persistPanelInputs,
19
+ closeDiagnosticsMenu,
20
+ closeCustomInstructionsSettings,
21
+ closeContextTray,
22
+ closeSlashMenu,
23
+ getPanel,
24
+ getState
25
+ } = deps;
26
+
27
+ let modelDiscovery = { status: 'fallback', source: 'fallback', fetchedAt: '' };
28
+
29
+ function getModelDiscovery() {
30
+ return modelDiscovery;
31
+ }
32
+
33
+ function toggleModelConfigPopover() {
34
+ const popover = getPanel()?.querySelector('[data-model-config-popover]');
35
+ const button = getPanel()?.querySelector('[data-model-config-toggle]');
36
+ if (!popover || !button) {
37
+ return;
38
+ }
39
+
40
+ const open = popover.hidden;
41
+ if (open) {
42
+ closeDiagnosticsMenu();
43
+ closeContextTray();
44
+ closeCustomInstructionsSettings();
45
+ if (typeof closeSlashMenu === 'function') {
46
+ closeSlashMenu();
47
+ }
48
+ renderModelConfigChoices();
49
+ }
50
+ popover.hidden = !open;
51
+ button.dataset.active = open ? 'true' : 'false';
52
+ button.setAttribute('aria-expanded', open ? 'true' : 'false');
53
+ }
54
+
55
+ function closeModelConfigPopover() {
56
+ const popover = getPanel()?.querySelector('[data-model-config-popover]');
57
+ const button = getPanel()?.querySelector('[data-model-config-toggle]');
58
+ if (!popover || !button) {
59
+ return;
60
+ }
61
+ popover.hidden = true;
62
+ button.dataset.active = 'false';
63
+ button.setAttribute('aria-expanded', 'false');
64
+ }
65
+
66
+ async function handleModelConfigChoiceClick(event) {
67
+ const choice = event.target?.closest?.('[data-reasoning-choice], [data-model-choice], [data-speed-choice]');
68
+ if (!choice || choice.disabled) {
69
+ return;
70
+ }
71
+ event.preventDefault();
72
+
73
+ const modelSelect = getPanel()?.querySelector('[data-model]');
74
+ const reasoningSelect = getPanel()?.querySelector('[data-reasoning]');
75
+ const speedSelect = getPanel()?.querySelector('[data-speed]');
76
+
77
+ if (choice.dataset.reasoningChoice && reasoningSelect) {
78
+ reasoningSelect.value = choice.dataset.reasoningChoice;
79
+ } else if (choice.dataset.modelChoice && modelSelect) {
80
+ modelSelect.value = choice.dataset.modelChoice;
81
+ renderSpeedOptions(getRenderedModelEntries());
82
+ } else if (choice.dataset.speedChoice && speedSelect) {
83
+ speedSelect.value = choice.dataset.speedChoice;
84
+ }
85
+
86
+ renderModelConfigChoices();
87
+ updateModelDisplay();
88
+ await persistPanelInputs();
89
+ }
90
+ async function loadModelOptions() {
91
+ const selectedModel = resolveSelectedModel();
92
+ const modelCatalog = getModelCatalog();
93
+ const fallbackModels = modelCatalog.FALLBACK_MODELS;
94
+
95
+ try {
96
+ const response = await sendBackgroundNative({
97
+ method: 'codex.models',
98
+ params: {}
99
+ });
100
+ const currentSelectedModel = resolveSelectedModel() || selectedModel;
101
+ const hasDiscoveredModels = response?.ok
102
+ && Array.isArray(response.result?.models)
103
+ && response.result.models.length > 0;
104
+ const sourceModels = hasDiscoveredModels ? response.result.models : fallbackModels;
105
+ const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel: currentSelectedModel });
106
+ renderModelOptions(normalized.models, currentSelectedModel);
107
+ modelDiscovery = {
108
+ status: hasDiscoveredModels && !normalized.usedFallback ? 'discovered' : 'fallback',
109
+ source: hasDiscoveredModels ? response.result?.source || 'unknown' : 'fallback',
110
+ fetchedAt: hasDiscoveredModels ? response.result?.fetchedAt || '' : '',
111
+ errorCode: hasDiscoveredModels ? '' : response?.error?.code || '',
112
+ errorMessage: hasDiscoveredModels ? '' : response?.error?.message || ''
113
+ };
114
+ updateModelDisplay();
115
+ } catch (error) {
116
+ applyFallbackModelOptions(resolveSelectedModel() || selectedModel, error);
117
+ }
118
+ }
119
+
120
+ function applyFallbackModelOptions(selectedModel, error) {
121
+ const modelCatalog = getModelCatalog();
122
+ const fallbackModels = modelCatalog.FALLBACK_MODELS;
123
+ const sourceModels = fallbackModels;
124
+ const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel });
125
+ renderModelOptions(normalized.models, selectedModel);
126
+ modelDiscovery = {
127
+ status: 'fallback',
128
+ source: 'fallback',
129
+ fetchedAt: '',
130
+ errorCode: error?.code || '',
131
+ errorMessage: error?.message || (error ? String(error) : '')
132
+ };
133
+ updateModelDisplay();
134
+ }
135
+
136
+ function getModelCatalog() {
137
+ const shared = window.CodexOverleafModels;
138
+ if (Array.isArray(shared?.FALLBACK_MODELS) && typeof shared?.normalizeDiscoveredModels === 'function') {
139
+ return shared;
140
+ }
141
+
142
+ return {
143
+ FALLBACK_MODELS: buildDomModelCatalogFallback(),
144
+ normalizeDiscoveredModels: normalizeDiscoveredModelsFallback
145
+ };
146
+ }
147
+
148
+ function buildDomModelCatalogFallback() {
149
+ const modelSelect = getPanel()?.querySelector('[data-model]');
150
+ const domModels = Array.from(modelSelect?.options || [])
151
+ .map(option => ({
152
+ id: normalizeModelOptionId(option.value),
153
+ label: option.textContent || option.value
154
+ }))
155
+ .filter(model => model.id);
156
+
157
+ return domModels.length ? domModels : [
158
+ { id: 'gpt-5.5', label: 'GPT-5.5' },
159
+ { id: 'gpt-5.4', label: 'GPT-5.4' },
160
+ { id: 'gpt-5.4-mini', label: 'GPT-5.4 Mini' },
161
+ { id: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' },
162
+ { id: 'gpt-5.3-codex-spark', label: 'GPT-5.3 Codex Spark' },
163
+ { id: 'gpt-5.2', label: 'GPT-5.2' }
164
+ ];
165
+ }
166
+
167
+ function normalizeDiscoveredModelsFallback({ models, selectedModel } = {}) {
168
+ const normalized = normalizeModelCatalogEntries(models);
169
+ const usedFallback = normalized.length === 0;
170
+ const resultModels = usedFallback ? buildDomModelCatalogFallback().map(model => ({ ...model })) : normalized;
171
+ const selectedId = normalizeModelOptionId(selectedModel);
172
+
173
+ if (selectedId && !resultModels.some(model => model.id === selectedId)) {
174
+ resultModels.push({
175
+ id: selectedId,
176
+ label: `${selectedId} (custom)`,
177
+ unverified: true
178
+ });
179
+ }
180
+
181
+ return {
182
+ models: resultModels,
183
+ usedFallback
184
+ };
185
+ }
186
+
187
+ function normalizeModelCatalogEntries(models) {
188
+ if (!Array.isArray(models)) {
189
+ return [];
190
+ }
191
+
192
+ const seen = new Set();
193
+ const result = [];
194
+
195
+ for (const model of models) {
196
+ const id = normalizeModelOptionId(typeof model === 'string' ? model : model?.id);
197
+ if (!id || seen.has(id)) {
198
+ continue;
199
+ }
200
+ seen.add(id);
201
+ const normalized = {
202
+ id,
203
+ label: typeof model?.label === 'string' && model.label.length > 0 ? model.label : id,
204
+ reasoningEfforts: Array.isArray(model?.reasoningEfforts) ? model.reasoningEfforts.slice() : [],
205
+ speedTiers: normalizeSpeedTiersForSelect(model?.speedTiers)
206
+ };
207
+ if (Object.prototype.hasOwnProperty.call(Object(model), 'defaultReasoningEffort')) {
208
+ normalized.defaultReasoningEffort = model.defaultReasoningEffort;
209
+ }
210
+ if (Object.prototype.hasOwnProperty.call(Object(model), 'defaultSpeedTier')) {
211
+ normalized.defaultSpeedTier = model.defaultSpeedTier;
212
+ }
213
+ result.push(normalized);
214
+ }
215
+
216
+ return result;
217
+ }
218
+
219
+ function renderModelOptions(models, selectedModel) {
220
+ const modelSelect = getPanel()?.querySelector('[data-model]');
221
+ if (!modelSelect) {
222
+ return;
223
+ }
224
+
225
+ const selectedId = normalizeModelOptionId(selectedModel);
226
+ modelSelect.textContent = '';
227
+ let renderedSelected = false;
228
+ let firstModelId = '';
229
+
230
+ for (const model of Array.isArray(models) ? models : []) {
231
+ const id = normalizeModelOptionId(model?.id);
232
+ if (!id) {
233
+ continue;
234
+ }
235
+ if (!firstModelId) {
236
+ firstModelId = id;
237
+ }
238
+ const option = document.createElement('option');
239
+ option.value = id;
240
+ option.textContent = model.label;
241
+ option.dataset.speedTiers = normalizeSpeedTiersForSelect(model.speedTiers).join(',');
242
+ option.dataset.defaultSpeedTier = model.defaultSpeedTier || 'standard';
243
+ if (model.unverified) {
244
+ option.dataset.unverified = 'true';
245
+ }
246
+ modelSelect.append(option);
247
+ if (id === selectedId) {
248
+ renderedSelected = true;
249
+ }
250
+ }
251
+
252
+ if (selectedId && !renderedSelected) {
253
+ const option = document.createElement('option');
254
+ option.value = selectedId;
255
+ option.textContent = `${selectedId} (custom)`;
256
+ option.dataset.speedTiers = 'standard';
257
+ option.dataset.defaultSpeedTier = 'standard';
258
+ option.dataset.unverified = 'true';
259
+ modelSelect.append(option);
260
+ renderedSelected = true;
261
+ }
262
+
263
+ if (selectedId && renderedSelected) {
264
+ modelSelect.value = selectedId;
265
+ } else if (firstModelId) {
266
+ modelSelect.value = firstModelId;
267
+ }
268
+ renderSpeedOptions(models);
269
+ renderModelConfigChoices();
270
+ updateModelDisplay();
271
+ }
272
+
273
+ function renderSpeedOptions(models) {
274
+ const speedSelect = getPanel()?.querySelector('[data-speed]');
275
+ const modelSelect = getPanel()?.querySelector('[data-model]');
276
+ if (!speedSelect || !modelSelect) {
277
+ return;
278
+ }
279
+
280
+ const selectedModel = normalizeModelOptionId(modelSelect.value);
281
+ const model = (Array.isArray(models) ? models : []).find(item => normalizeModelOptionId(item?.id) === selectedModel);
282
+ const speedTiers = normalizeSpeedTiersForSelect(model?.speedTiers);
283
+ const selectedSpeed = speedTiers.includes(getState()?.speedTier) ? getState().speedTier : (model?.defaultSpeedTier || 'standard');
284
+ speedSelect.textContent = '';
285
+ for (const tier of speedTiers) {
286
+ const option = document.createElement('option');
287
+ option.value = tier;
288
+ option.textContent = formatSpeedTierLabel(tier);
289
+ speedSelect.append(option);
290
+ }
291
+ speedSelect.value = speedTiers.includes(selectedSpeed) ? selectedSpeed : 'standard';
292
+ speedSelect.disabled = speedTiers.length <= 1;
293
+ speedSelect.title = speedSelect.disabled
294
+ ? tx('Fast mode is not available for this model.', '当前模型不支持 Fast 模式。')
295
+ : tx('Codex speed tier. Fast mode uses extra credits.', 'Codex 速度档;Fast 会消耗额外 credits。');
296
+ renderModelConfigChoices();
297
+ }
298
+
299
+ function renderModelConfigChoices() {
300
+ renderReasoningChoices();
301
+ renderModelChoices();
302
+ renderSpeedChoices();
303
+ syncModelConfigChoices();
304
+ }
305
+
306
+ function renderReasoningChoices() {
307
+ const list = getPanel()?.querySelector('[data-reasoning-choice-list]');
308
+ const reasoningSelect = getPanel()?.querySelector('[data-reasoning]');
309
+ if (!list || !reasoningSelect) {
310
+ return;
311
+ }
312
+ list.textContent = '';
313
+ for (const option of Array.from(reasoningSelect.options || [])) {
314
+ list.append(createModelConfigChoice({
315
+ value: option.value,
316
+ label: formatReasoningEffortLabel(option.value),
317
+ datasetName: 'reasoningChoice'
318
+ }));
319
+ }
320
+ }
321
+
322
+ function renderModelChoices() {
323
+ const list = getPanel()?.querySelector('[data-model-choice-list]');
324
+ const modelSelect = getPanel()?.querySelector('[data-model]');
325
+ if (!list || !modelSelect) {
326
+ return;
327
+ }
328
+ list.textContent = '';
329
+ for (const option of Array.from(modelSelect.options || [])) {
330
+ const choice = createModelConfigChoice({
331
+ value: option.value,
332
+ label: option.textContent || option.value,
333
+ datasetName: 'modelChoice'
334
+ });
335
+ choice.title = option.textContent || option.value;
336
+ list.append(choice);
337
+ }
338
+ }
339
+
340
+ function renderSpeedChoices() {
341
+ const list = getPanel()?.querySelector('[data-speed-choice-list]');
342
+ const speedSelect = getPanel()?.querySelector('[data-speed]');
343
+ if (!list || !speedSelect) {
344
+ return;
345
+ }
346
+ list.textContent = '';
347
+ for (const option of Array.from(speedSelect.options || [])) {
348
+ const choice = createModelConfigChoice({
349
+ value: option.value,
350
+ label: formatSpeedTierLabel(option.value),
351
+ datasetName: 'speedChoice'
352
+ });
353
+ choice.disabled = speedSelect.disabled && speedSelect.options.length <= 1;
354
+ list.append(choice);
355
+ }
356
+ }
357
+
358
+ function createModelConfigChoice({ value, label, datasetName }) {
359
+ const button = document.createElement('button');
360
+ button.type = 'button';
361
+ button.className = 'codex-model-config-choice';
362
+ button.dataset[datasetName] = value;
363
+ button.setAttribute('role', 'menuitemradio');
364
+ button.setAttribute('aria-checked', 'false');
365
+
366
+ const text = document.createElement('span');
367
+ text.className = 'codex-model-config-choice-label';
368
+ text.textContent = label;
369
+ button.append(text);
370
+
371
+ const check = document.createElement('span');
372
+ check.className = 'codex-model-config-check';
373
+ check.textContent = '✓';
374
+ check.setAttribute('aria-hidden', 'true');
375
+ button.append(check);
376
+ return button;
377
+ }
378
+
379
+ function syncModelConfigChoices() {
380
+ const selectedModel = getPanel()?.querySelector('[data-model]')?.value || '';
381
+ const selectedReasoning = getPanel()?.querySelector('[data-reasoning]')?.value || '';
382
+ const selectedSpeed = getPanel()?.querySelector('[data-speed]')?.value || 'standard';
383
+ syncChoiceGroup('[data-model-choice]', selectedModel);
384
+ syncChoiceGroup('[data-reasoning-choice]', selectedReasoning);
385
+ syncChoiceGroup('[data-speed-choice]', selectedSpeed);
386
+ }
387
+
388
+ function syncChoiceGroup(selector, selectedValue) {
389
+ for (const button of getPanel()?.querySelectorAll(selector) || []) {
390
+ const value = button.dataset.modelChoice || button.dataset.reasoningChoice || button.dataset.speedChoice || '';
391
+ const active = value === selectedValue;
392
+ button.dataset.active = active ? 'true' : 'false';
393
+ button.setAttribute('aria-checked', active ? 'true' : 'false');
394
+ }
395
+ }
396
+
397
+ function normalizeSpeedTiersForSelect(speedTiers) {
398
+ const tiers = Array.isArray(speedTiers)
399
+ ? speedTiers.map(tier => normalizeModelOptionId(tier)).filter(Boolean)
400
+ : ['standard'];
401
+ return tiers.includes('standard') ? tiers : ['standard', ...tiers];
402
+ }
403
+
404
+ function formatSpeedTierLabel(tier) {
405
+ return tier === 'fast' ? tx('Fast', '快速') : tx('Standard', '标准');
406
+ }
407
+
408
+ function formatReasoningEffortLabel(effort) {
409
+ const labels = getLocale() === 'zh'
410
+ ? { low: '低', medium: '中', high: '高', xhigh: '超高' }
411
+ : { low: 'Low', medium: 'Medium', high: 'High', xhigh: 'XHigh' };
412
+ return labels[effort] || effort || '';
413
+ }
414
+
415
+ function formatCompactModelLabel(label) {
416
+ return String(label || '').replace(/^gpt[-\s]*/i, '');
417
+ }
418
+
419
+ function resolveSelectedModel() {
420
+ return getPanel()?.querySelector('[data-model]')?.value || getState()?.model || '';
421
+ }
422
+
423
+ function normalizeModelOptionId(id) {
424
+ return typeof id === 'string' ? id.trim() : '';
425
+ }
426
+
427
+ function getModelDiscoverySourceLabel() {
428
+ if (modelDiscovery.errorCode || modelDiscovery.errorMessage) {
429
+ return `${tr('modelSourceFailed')} (${tr('modelSourceFallback')})`;
430
+ }
431
+ if (modelDiscovery.source === 'fallback') {
432
+ return tr('modelSourceFallback');
433
+ }
434
+ if (modelDiscovery.source) {
435
+ return modelDiscovery.source;
436
+ }
437
+ return modelDiscovery.status === 'discovered' ? tr('modelSourceDiscovered') : tr('modelSourceFallback');
438
+ }
439
+
440
+ function updateModelDisplay() {
441
+ const modelSelect = getPanel()?.querySelector('[data-model]');
442
+ const modelDisplay = getPanel()?.querySelector('[data-model-display]');
443
+ const reasoningDisplay = getPanel()?.querySelector('[data-reasoning-display]');
444
+ const speedIndicator = getPanel()?.querySelector('[data-speed-indicator]');
445
+ const configButton = getPanel()?.querySelector('[data-model-config-toggle]');
446
+ if (!modelSelect || !modelDisplay) {
447
+ return;
448
+ }
449
+ const fullLabel = modelSelect.options[modelSelect.selectedIndex]?.textContent || modelSelect.value;
450
+ modelDisplay.textContent = formatCompactModelLabel(fullLabel);
451
+ const sourceTitle = tr('modelDisplayTitle', {
452
+ label: fullLabel,
453
+ source: getModelDiscoverySourceLabel()
454
+ });
455
+ modelDisplay.title = sourceTitle;
456
+ if (reasoningDisplay) {
457
+ reasoningDisplay.textContent = formatReasoningEffortLabel(getPanel()?.querySelector('[data-reasoning]')?.value || getState()?.reasoningEffort || '');
458
+ }
459
+ if (speedIndicator) {
460
+ speedIndicator.hidden = readSelectedSpeedInput() !== 'fast';
461
+ }
462
+ if (configButton) {
463
+ configButton.title = [
464
+ sourceTitle,
465
+ reasoningDisplay?.textContent ? `${tx('Reasoning', '推理')}: ${reasoningDisplay.textContent}` : '',
466
+ readSelectedSpeedInput() === 'fast' ? tx('Fast mode', '快速模式') : tx('Standard speed', '标准速度')
467
+ ].filter(Boolean).join(' · ');
468
+ }
469
+ }
470
+
471
+ return {
472
+ getModelDiscovery,
473
+ toggleModelConfigPopover,
474
+ closeModelConfigPopover,
475
+ handleModelConfigChoiceClick,
476
+ loadModelOptions,
477
+ applyFallbackModelOptions,
478
+ getModelCatalog,
479
+ renderModelOptions,
480
+ renderSpeedOptions,
481
+ renderModelConfigChoices,
482
+ resolveSelectedModel,
483
+ normalizeModelOptionId,
484
+ updateModelDisplay
485
+ };
486
+ }
487
+
488
+ window.CodexOverleafModelPicker = { create };
489
+ })();
@@ -0,0 +1,62 @@
1
+ (function initCodexOverleafNativeChannel(root, factory) {
2
+ if (typeof module === 'object' && module.exports) {
3
+ module.exports = factory();
4
+ } else {
5
+ root.CodexOverleafNativeChannel = factory();
6
+ }
7
+ })(typeof window !== 'undefined' ? window : globalThis, function nativeChannelFactory() {
8
+ 'use strict';
9
+
10
+ function create(deps = {}) {
11
+ const runtime = deps.chrome?.runtime || chrome.runtime;
12
+ const cryptoImpl = deps.crypto || crypto;
13
+ let activeRequestId = null;
14
+
15
+ function sendNative(payload) {
16
+ const id = cryptoImpl.randomUUID();
17
+ activeRequestId = id;
18
+ return runtime.sendMessage({
19
+ type: 'codex-overleaf/native-request',
20
+ payload: {
21
+ id,
22
+ ...payload
23
+ }
24
+ });
25
+ }
26
+
27
+ function sendBackgroundNative(payload) {
28
+ const id = cryptoImpl.randomUUID();
29
+ return runtime.sendMessage({
30
+ type: 'codex-overleaf/native-request',
31
+ payload: {
32
+ id,
33
+ ...payload
34
+ }
35
+ });
36
+ }
37
+
38
+ function getActiveRequestId() {
39
+ return activeRequestId;
40
+ }
41
+
42
+ function clearActiveRequest() {
43
+ activeRequestId = null;
44
+ }
45
+
46
+ function shouldHandleNativeEvent(message = {}) {
47
+ return message?.type === 'codex-overleaf/native-event' && message.id === activeRequestId;
48
+ }
49
+
50
+ return {
51
+ clearActiveRequest,
52
+ getActiveRequestId,
53
+ sendBackgroundNative,
54
+ sendNative,
55
+ shouldHandleNativeEvent
56
+ };
57
+ }
58
+
59
+ return {
60
+ create
61
+ };
62
+ });