codex-overleaf-link 2.1.5 → 2.2.1
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/README.md +27 -23
- package/extension/bootstrap/background.js +14 -1
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/runtime-manifest.json +10 -0
- package/extension/src/background.js +507 -8
- package/extension/src/backgroundUpdateCoordinator.js +284 -19
- package/extension/src/content/activeTurnControl.js +179 -0
- package/extension/src/content/composerPanel.js +14 -5
- package/extension/src/content/contentRuntime.js +330 -309
- package/extension/src/content/diagnosticsPanel.js +132 -1
- package/extension/src/content/markdownDomRenderer.js +451 -0
- package/extension/src/content/markdownText.js +86 -7
- package/extension/src/content/mathText.js +49 -4
- package/extension/src/content/modelPicker.js +14 -5
- package/extension/src/content/nativeChannel.js +7 -1
- package/extension/src/content/panelRenderer.js +112 -12
- package/extension/src/content/pendingInputView.js +106 -0
- package/extension/src/content/projectProviderSelection.js +68 -0
- package/extension/src/content/projectSessionCleanup.js +4 -0
- package/extension/src/content/providerSettingsCoordinator.js +170 -22
- package/extension/src/content/providerSettingsDialog.js +2 -2
- package/extension/src/content/recentProjects.js +7 -2
- package/extension/src/content/runGuidanceController.js +70 -0
- package/extension/src/content/runGuidanceView.js +48 -0
- package/extension/src/content/runQueueScheduler.js +260 -0
- package/extension/src/content/runTimelineView.js +33 -80
- package/extension/src/content/sessionManager.js +2 -2
- package/extension/src/content/sessionPersistence.js +62 -0
- package/extension/src/content/settingsPanel.js +1 -1
- package/extension/src/content/updateNotice.js +80 -1
- package/extension/src/page/writebackRouter.js +3 -9
- package/extension/src/shared/compatibility.js +2 -1
- package/extension/src/shared/i18n.js +38 -0
- package/extension/src/shared/providerProfiles.js +3 -2
- package/extension/src/shared/runInputQueue.js +253 -0
- package/extension/src/shared/sessionState.js +45 -22
- package/extension/src/shared/storageDb.js +39 -44
- package/extension/src/shared/storageMigration.js +123 -3
- package/extension/src/shared/storageRunActions.js +34 -1
- package/extension/src/shared/updateConsent.js +15 -4
- package/extension/styles/panel.css +319 -20
- package/extension/vendor/katex/katex.min.css +1 -1
- package/extension/vendor/markdown-it/LICENSE +22 -0
- package/extension/vendor/markdown-it/SOURCE.json +7 -0
- package/extension/vendor/markdown-it/VERSION +1 -0
- package/extension/vendor/markdown-it/markdown-it.min.js +3 -0
- package/native-host/src/anthropicBridgeRequest.js +3 -3
- package/native-host/src/anthropicBridgeResponse.js +58 -17
- package/native-host/src/anthropicMessagesBridge.js +156 -23
- package/native-host/src/chatBridgeRequest.js +1 -1
- package/native-host/src/chatBridgeResponse.js +12 -8
- package/native-host/src/chatCompletionsBridge.js +22 -40
- package/native-host/src/codexPromptAssembly.js +3 -1
- package/native-host/src/codexProviderLaunch.js +6 -3
- package/native-host/src/codexSessionRunner.js +65 -65
- package/native-host/src/codexSessionTiming.js +45 -0
- package/native-host/src/index.js +58 -3
- package/native-host/src/managedInstall.js +45 -7
- package/native-host/src/mirrorWorkspace.js +15 -8
- package/native-host/src/providerProfile.js +2 -2
- package/native-host/src/providerStreamLifecycle.js +100 -0
- package/native-host/src/responsesPassthroughBridge.js +636 -0
- package/native-host/src/taskRunnerRuntime.js +81 -2
- package/native-host/src/updateArchive.js +4 -3
- package/native-host/src/updateManager.js +306 -19
- package/native-host/src/updateTrust.js +1 -1
- package/package.json +3 -1
|
@@ -463,13 +463,56 @@
|
|
|
463
463
|
return '#';
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
466
|
+
const renderingDiagnosticKeys = new Set();
|
|
467
|
+
|
|
468
|
+
function renderMarkdownBlockText(target, value, renderOptions = {}) {
|
|
469
|
+
const rawSource = String(value || '').trim();
|
|
470
|
+
if (!rawSource) {
|
|
471
|
+
target.replaceChildren();
|
|
470
472
|
return;
|
|
471
473
|
}
|
|
472
474
|
|
|
475
|
+
if (!renderOptions.streaming) {
|
|
476
|
+
const runtimeRoot = typeof window !== 'undefined' ? window : globalThis;
|
|
477
|
+
const domRenderer = runtimeRoot.CodexOverleafMarkdownDomRenderer;
|
|
478
|
+
try {
|
|
479
|
+
if (!domRenderer?.renderMarkdown || typeof runtimeRoot.markdownit !== 'function') {
|
|
480
|
+
const dependencyError = new Error('The vendored Markdown DOM renderer is unavailable.');
|
|
481
|
+
dependencyError.code = 'markdown_dom_dependency_missing';
|
|
482
|
+
throw dependencyError;
|
|
483
|
+
}
|
|
484
|
+
domRenderer.renderMarkdown(target, rawSource, {
|
|
485
|
+
document,
|
|
486
|
+
markdownIt: runtimeRoot.markdownit,
|
|
487
|
+
mathText: runtimeRoot.CodexOverleafMathText,
|
|
488
|
+
renderTextNodes: text => buildMarkdownInlineNodes(text),
|
|
489
|
+
renderLinkNodes: (label, href) => {
|
|
490
|
+
const safeLabel = String(label || '');
|
|
491
|
+
const target = String(href || '').trim();
|
|
492
|
+
if (!/^https?:\/\//i.test(target) && !resolveMarkdownLineReference(safeLabel, target)) return buildMarkdownInlineNodes(safeLabel);
|
|
493
|
+
return buildMarkdownInlineNodes(
|
|
494
|
+
`[${safeLabel.replace(/]/g, '\\]')}](${target.replace(/\)/g, '\\)')})`
|
|
495
|
+
);
|
|
496
|
+
},
|
|
497
|
+
renderCodeNodes: text => buildMarkdownInlineNodes(`\`${String(text || '').replace(/`/g, '\\`')}\``),
|
|
498
|
+
sanitizeText: sanitizeAssistantVisibleText,
|
|
499
|
+
createMathNode: segment => runtimeRoot.CodexOverleafMathText.createMathNode(segment, {
|
|
500
|
+
document, katex: runtimeRoot.katex,
|
|
501
|
+
renderText: text => buildMarkdownInlineNodes(text)
|
|
502
|
+
}),
|
|
503
|
+
recordDiagnostic: recordRenderingDiagnostic
|
|
504
|
+
});
|
|
505
|
+
return;
|
|
506
|
+
} catch (error) {
|
|
507
|
+
recordRenderingDiagnostic({
|
|
508
|
+
code: error?.code || 'markdown_dom_fallback',
|
|
509
|
+
detail: String(error?.message || 'Markdown DOM rendering failed.')
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const source = normalizeInlineOrderedLists(rawSource);
|
|
515
|
+
target.replaceChildren();
|
|
473
516
|
const lines = source.split(/\r?\n/);
|
|
474
517
|
let index = 0;
|
|
475
518
|
|
|
@@ -533,10 +576,46 @@
|
|
|
533
576
|
index++;
|
|
534
577
|
}
|
|
535
578
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
579
|
+
appendMarkdownParagraphFlow(target, paragraphLines.join(' '));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function recordRenderingDiagnostic(diagnostic = {}) {
|
|
584
|
+
const code = String(diagnostic.code || 'markdown_rendering_diagnostic');
|
|
585
|
+
const detail = String(diagnostic.detail || '').slice(0, 240);
|
|
586
|
+
const key = `${code}:${detail}`;
|
|
587
|
+
if (renderingDiagnosticKeys.has(key)) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
renderingDiagnosticKeys.add(key);
|
|
591
|
+
if (typeof options.recordRenderingDiagnostic === 'function') options.recordRenderingDiagnostic({ code, detail });
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function appendMarkdownParagraphFlow(target, value) {
|
|
595
|
+
const nodes = buildMarkdownInlineNodes(value);
|
|
596
|
+
let paragraph = null;
|
|
597
|
+
let paragraphHasContent = false;
|
|
598
|
+
const flushParagraph = () => {
|
|
599
|
+
if (paragraph && paragraphHasContent) {
|
|
600
|
+
target.append(paragraph);
|
|
601
|
+
}
|
|
602
|
+
paragraph = null;
|
|
603
|
+
paragraphHasContent = false;
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
for (const node of nodes) {
|
|
607
|
+
if (node?.dataset?.mathDisplay === 'block') {
|
|
608
|
+
flushParagraph();
|
|
609
|
+
target.append(node);
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (!paragraph) {
|
|
613
|
+
paragraph = document.createElement('p');
|
|
614
|
+
}
|
|
615
|
+
paragraph.append(node);
|
|
616
|
+
paragraphHasContent = true;
|
|
539
617
|
}
|
|
618
|
+
flushParagraph();
|
|
540
619
|
}
|
|
541
620
|
|
|
542
621
|
function isMarkdownFenceLine(line) {
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
Object.freeze({ open: '\\(', close: '\\)', display: false, singleDollar: false }),
|
|
8
8
|
Object.freeze({ open: '$', close: '$', display: false, singleDollar: true })
|
|
9
9
|
]);
|
|
10
|
+
const LONG_INLINE_MATH_CHARS = 120;
|
|
11
|
+
const COMPLEX_INLINE_MATH_CHARS = 80;
|
|
10
12
|
|
|
11
13
|
function parseMathSegments(value) {
|
|
12
14
|
const source = String(value || '');
|
|
@@ -186,14 +188,19 @@
|
|
|
186
188
|
function createMathNode(segment, options = {}) {
|
|
187
189
|
const documentRef = options.document || root.document;
|
|
188
190
|
const node = documentRef.createElement('span');
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
const source = normalizeMathForRendering(segment.value);
|
|
192
|
+
const display = segment.display || shouldPromoteInlineMath(source);
|
|
193
|
+
node.className = `run-math ${display ? 'run-math--display' : 'run-math--inline'}`;
|
|
194
|
+
node.dataset.mathDisplay = display ? 'block' : 'inline';
|
|
195
|
+
if (display && !segment.display) {
|
|
196
|
+
node.classList.add('run-math--promoted');
|
|
197
|
+
}
|
|
191
198
|
try {
|
|
192
199
|
if (typeof options.katex?.render !== 'function') {
|
|
193
200
|
throw new Error('KaTeX is unavailable');
|
|
194
201
|
}
|
|
195
|
-
options.katex.render(
|
|
196
|
-
displayMode:
|
|
202
|
+
options.katex.render(source, node, {
|
|
203
|
+
displayMode: display,
|
|
197
204
|
throwOnError: true,
|
|
198
205
|
trust: false,
|
|
199
206
|
strict: 'warn',
|
|
@@ -211,6 +218,23 @@
|
|
|
211
218
|
}
|
|
212
219
|
}
|
|
213
220
|
|
|
221
|
+
function normalizeMathForRendering(value) {
|
|
222
|
+
return String(value || '')
|
|
223
|
+
.replace(/\r\n?/g, '\n')
|
|
224
|
+
.replace(/(\S)\s+\|\s+(\S)/g, '$1 \\mid $2');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function shouldPromoteInlineMath(value) {
|
|
228
|
+
const source = String(value || '').trim();
|
|
229
|
+
if (source.length >= LONG_INLINE_MATH_CHARS) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (source.length < COMPLEX_INLINE_MATH_CHARS) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
return /(?:=|\\(?:le|ge|approx|sim|to|Rightarrow|Longrightarrow)\b|\\begin\{(?:aligned|gathered|split|array)\}|\\\\)/.test(source);
|
|
236
|
+
}
|
|
237
|
+
|
|
214
238
|
function toNodeArray(value) {
|
|
215
239
|
if (Array.isArray(value)) {
|
|
216
240
|
return value;
|
|
@@ -221,9 +245,30 @@
|
|
|
221
245
|
return [value];
|
|
222
246
|
}
|
|
223
247
|
|
|
248
|
+
function matchMathAt(value, index = 0) {
|
|
249
|
+
const source = String(value || '');
|
|
250
|
+
const start = Number.isSafeInteger(index) ? index : 0;
|
|
251
|
+
if (start < 0 || start >= source.length || (source[start] !== '$' && source[start] !== '\\')) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const segment = parseMathSegments(source.slice(start))[0];
|
|
255
|
+
if (!segment || segment.type !== 'math' || !segment.raw) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
start,
|
|
260
|
+
end: start + segment.raw.length,
|
|
261
|
+
raw: segment.raw,
|
|
262
|
+
value: segment.value,
|
|
263
|
+
display: Boolean(segment.display)
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
224
267
|
const api = {
|
|
225
268
|
buildMathNodes,
|
|
226
269
|
createMathNode,
|
|
270
|
+
matchMathAt,
|
|
271
|
+
normalizeMathForRendering,
|
|
227
272
|
parseMathSegments
|
|
228
273
|
};
|
|
229
274
|
root.CodexOverleafMathText = api;
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
updateModelDisplay();
|
|
103
103
|
await persistPanelInputs(changedInput ? { type: 'change', target: changedInput } : undefined);
|
|
104
104
|
}
|
|
105
|
-
async function loadModelOptions(providerSelection) {
|
|
105
|
+
async function loadModelOptions(providerSelection, options = {}) {
|
|
106
106
|
const generation = ++modelLoadGeneration;
|
|
107
107
|
const selection = providerSelection || getProviderSelection?.() || null;
|
|
108
108
|
const selectedModel = resolveSelectedModel();
|
|
@@ -131,14 +131,17 @@
|
|
|
131
131
|
&& response.result.models.length > 0;
|
|
132
132
|
if (!hasDiscoveredModels && !Support.shouldUseBuiltInFallback(response)) {
|
|
133
133
|
applyUnavailableModelOptions(response?.error);
|
|
134
|
-
return;
|
|
134
|
+
return { stale: false, error: response?.error || { code: 'provider_models_unavailable' } };
|
|
135
135
|
}
|
|
136
136
|
const sourceModels = hasDiscoveredModels ? response.result.models : fallbackModels;
|
|
137
137
|
const retainedSelectedModel = retainSelectedModel(sourceModels, currentSelectedModel);
|
|
138
138
|
const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel: retainedSelectedModel });
|
|
139
139
|
if (normalized.usedFallback && response.result?.providerId && response.result.providerId !== 'builtin') {
|
|
140
140
|
applyUnavailableModelOptions({ code: 'provider_model_catalog_invalid', message: 'The active provider returned no usable models.' });
|
|
141
|
-
return
|
|
141
|
+
return {
|
|
142
|
+
stale: false,
|
|
143
|
+
error: { code: 'provider_model_catalog_invalid', message: 'The active provider returned no usable models.' }
|
|
144
|
+
};
|
|
142
145
|
}
|
|
143
146
|
renderModelOptions(normalized.models, retainedSelectedModel, { allowUnknownSelected: false });
|
|
144
147
|
modelDiscovery = {
|
|
@@ -151,8 +154,14 @@
|
|
|
151
154
|
providerRevision: Number(response?.result?.providerRevision || selection?.providerRevision || 0)
|
|
152
155
|
};
|
|
153
156
|
updateModelDisplay();
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
if (options.persist !== false) {
|
|
158
|
+
await persistPanelInputs();
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
stale: false,
|
|
162
|
+
providerId: modelDiscovery.providerId,
|
|
163
|
+
selectedModel: resolveSelectedModel()
|
|
164
|
+
};
|
|
156
165
|
} catch (error) {
|
|
157
166
|
if (generation !== modelLoadGeneration) {
|
|
158
167
|
return { stale: true };
|
|
@@ -14,15 +14,20 @@
|
|
|
14
14
|
const backgroundEventHandlers = new Map();
|
|
15
15
|
|
|
16
16
|
function sendNative(payload) {
|
|
17
|
+
return sendNativeTracked(payload).promise;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sendNativeTracked(payload) {
|
|
17
21
|
const id = cryptoImpl.randomUUID();
|
|
18
22
|
activeRequestId = id;
|
|
19
|
-
|
|
23
|
+
const promise = runtime.sendMessage({
|
|
20
24
|
type: 'codex-overleaf/native-request',
|
|
21
25
|
payload: {
|
|
22
26
|
id,
|
|
23
27
|
...payload
|
|
24
28
|
}
|
|
25
29
|
});
|
|
30
|
+
return { id, promise };
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
function sendBackgroundNative(payload, onEvent) {
|
|
@@ -71,6 +76,7 @@
|
|
|
71
76
|
handleBackgroundNativeEvent,
|
|
72
77
|
sendBackgroundNative,
|
|
73
78
|
sendNative,
|
|
79
|
+
sendNativeTracked,
|
|
74
80
|
shouldHandleNativeEvent
|
|
75
81
|
};
|
|
76
82
|
}
|
|
@@ -51,18 +51,6 @@
|
|
|
51
51
|
<div data-session-slot></div>
|
|
52
52
|
<div class="codex-toast-region" data-toast-region aria-live="polite" aria-atomic="false"></div>
|
|
53
53
|
<section class="codex-thread-section">
|
|
54
|
-
<div class="codex-thread-title" data-session-label>
|
|
55
|
-
<button type="button" class="codex-thread-title-trigger" data-session-menu-trigger aria-haspopup="menu" aria-expanded="false">
|
|
56
|
-
<span class="codex-thread-title-text" data-session-label-text></span>
|
|
57
|
-
<span class="codex-thread-title-chevron" aria-hidden="true">▾</span>
|
|
58
|
-
</button>
|
|
59
|
-
<input type="text" class="codex-thread-title-input" data-session-rename-input maxlength="80" hidden>
|
|
60
|
-
<div class="codex-thread-title-actions">
|
|
61
|
-
<button type="button" class="codex-thread-title-action" data-session-rename title="Rename session" aria-label="Rename session">✎</button>
|
|
62
|
-
<button type="button" class="codex-thread-title-action codex-thread-title-action--delete" data-session-delete title="Delete session" aria-label="Delete session">×</button>
|
|
63
|
-
</div>
|
|
64
|
-
<div class="codex-session-menu" data-session-menu hidden></div>
|
|
65
|
-
</div>
|
|
66
54
|
<div class="col-log" data-log></div>
|
|
67
55
|
</section>
|
|
68
56
|
</div>
|
|
@@ -236,6 +224,117 @@
|
|
|
236
224
|
actions.prepend(button);
|
|
237
225
|
}
|
|
238
226
|
|
|
227
|
+
function createConfirmController(options = {}) {
|
|
228
|
+
let activeResolve = null;
|
|
229
|
+
|
|
230
|
+
function isOpen() {
|
|
231
|
+
return Boolean(activeResolve);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function dismiss(value = false) {
|
|
235
|
+
activeResolve?.(value);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function show(config = {}) {
|
|
239
|
+
if (!options.getPanel?.()) {
|
|
240
|
+
options.ensurePanelOpen?.();
|
|
241
|
+
}
|
|
242
|
+
const panel = options.getPanel?.();
|
|
243
|
+
if (!panel) {
|
|
244
|
+
return Promise.resolve(false);
|
|
245
|
+
}
|
|
246
|
+
dismiss(false);
|
|
247
|
+
const tr = options.tr || (key => key);
|
|
248
|
+
const title = config.title || tr('confirmDefaultTitle');
|
|
249
|
+
const message = config.message || '';
|
|
250
|
+
const confirmLabel = config.confirmLabel || tr('confirmDefaultConfirm');
|
|
251
|
+
const cancelLabel = config.cancelLabel || tr('confirmDefaultCancel');
|
|
252
|
+
const destructive = config.destructive === true;
|
|
253
|
+
const doc = panel.ownerDocument || document;
|
|
254
|
+
|
|
255
|
+
return new Promise(resolve => {
|
|
256
|
+
let settled = false;
|
|
257
|
+
const overlay = doc.createElement('div');
|
|
258
|
+
overlay.className = 'codex-plugin-confirm';
|
|
259
|
+
overlay.setAttribute('data-plugin-confirm', 'true');
|
|
260
|
+
overlay.setAttribute('role', 'dialog');
|
|
261
|
+
overlay.setAttribute('aria-modal', 'true');
|
|
262
|
+
overlay.setAttribute('aria-label', title);
|
|
263
|
+
|
|
264
|
+
const card = doc.createElement('section');
|
|
265
|
+
card.className = 'codex-plugin-confirm-card';
|
|
266
|
+
const head = doc.createElement('div');
|
|
267
|
+
head.className = 'codex-plugin-confirm-head';
|
|
268
|
+
const icon = doc.createElement('img');
|
|
269
|
+
icon.className = 'codex-plugin-confirm-icon';
|
|
270
|
+
icon.alt = '';
|
|
271
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
272
|
+
icon.src = options.getIconUrl?.() || '';
|
|
273
|
+
const titleWrap = doc.createElement('div');
|
|
274
|
+
const brand = doc.createElement('div');
|
|
275
|
+
brand.className = 'codex-plugin-confirm-brand';
|
|
276
|
+
brand.textContent = tr('confirmBrand');
|
|
277
|
+
const titleEl = doc.createElement('div');
|
|
278
|
+
titleEl.className = 'codex-plugin-confirm-title';
|
|
279
|
+
titleEl.textContent = title;
|
|
280
|
+
titleWrap.append(brand, titleEl);
|
|
281
|
+
head.append(icon, titleWrap);
|
|
282
|
+
|
|
283
|
+
const body = doc.createElement('div');
|
|
284
|
+
body.className = 'codex-plugin-confirm-body';
|
|
285
|
+
body.textContent = String(message);
|
|
286
|
+
const actions = doc.createElement('div');
|
|
287
|
+
actions.className = 'codex-plugin-confirm-actions';
|
|
288
|
+
const cancel = doc.createElement('button');
|
|
289
|
+
cancel.type = 'button';
|
|
290
|
+
cancel.className = 'codex-plugin-confirm-cancel';
|
|
291
|
+
cancel.textContent = cancelLabel;
|
|
292
|
+
const confirm = doc.createElement('button');
|
|
293
|
+
confirm.type = 'button';
|
|
294
|
+
confirm.className = 'codex-plugin-confirm-confirm';
|
|
295
|
+
if (destructive) confirm.dataset.destructive = 'true';
|
|
296
|
+
confirm.textContent = confirmLabel;
|
|
297
|
+
actions.append(cancel, confirm);
|
|
298
|
+
card.append(head, body, actions);
|
|
299
|
+
overlay.append(card);
|
|
300
|
+
panel.append(overlay);
|
|
301
|
+
|
|
302
|
+
const cleanup = value => {
|
|
303
|
+
if (settled) return;
|
|
304
|
+
settled = true;
|
|
305
|
+
activeResolve = null;
|
|
306
|
+
doc.removeEventListener('keydown', onKeydown, true);
|
|
307
|
+
overlay.remove();
|
|
308
|
+
resolve(value);
|
|
309
|
+
};
|
|
310
|
+
activeResolve = cleanup;
|
|
311
|
+
const onKeydown = event => {
|
|
312
|
+
if (event.key === 'Escape') {
|
|
313
|
+
event.preventDefault();
|
|
314
|
+
cleanup(false);
|
|
315
|
+
} else if (event.key === 'Enter') {
|
|
316
|
+
event.preventDefault();
|
|
317
|
+
if (!destructive && event.target !== cancel) cleanup(true);
|
|
318
|
+
else if (event.target === cancel) cleanup(false);
|
|
319
|
+
else if (event.target === confirm) cleanup(true);
|
|
320
|
+
} else if (event.key === 'Tab') {
|
|
321
|
+
event.preventDefault();
|
|
322
|
+
(event.target === cancel ? confirm : cancel).focus();
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
overlay.addEventListener('click', event => {
|
|
326
|
+
if (event.target === overlay) cleanup(false);
|
|
327
|
+
});
|
|
328
|
+
cancel.addEventListener('click', () => cleanup(false));
|
|
329
|
+
confirm.addEventListener('click', () => cleanup(true));
|
|
330
|
+
doc.addEventListener('keydown', onKeydown, true);
|
|
331
|
+
(destructive ? cancel : confirm).focus();
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return { dismiss, isOpen, show };
|
|
336
|
+
}
|
|
337
|
+
|
|
239
338
|
function destroy(instance) {
|
|
240
339
|
for (const { target, type, listener, options } of instance.listeners.splice(0)) {
|
|
241
340
|
target.removeEventListener?.(type, listener, options);
|
|
@@ -246,6 +345,7 @@
|
|
|
246
345
|
|
|
247
346
|
window.CodexOverleafPanelRenderer = {
|
|
248
347
|
create,
|
|
348
|
+
createConfirmController,
|
|
249
349
|
setVisible,
|
|
250
350
|
setBadge,
|
|
251
351
|
setWidth
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
(function initCodexOverleafPendingInputView(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafPendingInputView = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function pendingInputViewFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
function create(options = {}) {
|
|
11
|
+
const container = options.container;
|
|
12
|
+
|
|
13
|
+
function render(items = [], state = {}) {
|
|
14
|
+
if (!container) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
container.replaceChildren();
|
|
18
|
+
container.hidden = !items.length;
|
|
19
|
+
for (const item of items) {
|
|
20
|
+
const row = document.createElement('article');
|
|
21
|
+
row.className = 'codex-pending-input';
|
|
22
|
+
row.dataset.status = item.status || 'queued';
|
|
23
|
+
const copy = document.createElement('div');
|
|
24
|
+
copy.className = 'codex-pending-input-copy';
|
|
25
|
+
const status = document.createElement('span');
|
|
26
|
+
status.className = 'codex-pending-input-status';
|
|
27
|
+
status.textContent = item.status === 'paused'
|
|
28
|
+
? options.tr?.('queuedInputPaused')
|
|
29
|
+
: item.status === 'steering'
|
|
30
|
+
? options.tr?.('queuedInputGuiding')
|
|
31
|
+
: item.status === 'executing'
|
|
32
|
+
? options.tr?.('queuedInputExecuting')
|
|
33
|
+
: options.tr?.('queuedInputQueued');
|
|
34
|
+
const text = document.createElement('span');
|
|
35
|
+
text.className = 'codex-pending-input-text';
|
|
36
|
+
text.textContent = item.text;
|
|
37
|
+
copy.append(status, text);
|
|
38
|
+
const actions = document.createElement('div');
|
|
39
|
+
actions.className = 'codex-pending-input-actions';
|
|
40
|
+
if (item.status === 'paused') {
|
|
41
|
+
actions.append(createButton(options.tr?.('queuedInputResume'), () => options.onResume?.()));
|
|
42
|
+
} else if (state.running && item.status === 'queued') {
|
|
43
|
+
const guide = createIconButton(
|
|
44
|
+
options.tr?.('queuedInputGuide'),
|
|
45
|
+
'guide',
|
|
46
|
+
() => options.onGuide?.(item.id)
|
|
47
|
+
);
|
|
48
|
+
guide.disabled = !state.canGuide;
|
|
49
|
+
guide.title = state.canGuide
|
|
50
|
+
? options.tr?.('queuedInputGuideTitle')
|
|
51
|
+
: options.tr?.('queuedInputGuideUnavailable');
|
|
52
|
+
actions.append(guide);
|
|
53
|
+
}
|
|
54
|
+
const remove = createIconButton(
|
|
55
|
+
options.tr?.('queuedInputRemove'),
|
|
56
|
+
'remove',
|
|
57
|
+
() => options.onRemove?.(item.id)
|
|
58
|
+
);
|
|
59
|
+
remove.classList.add('codex-pending-input-remove');
|
|
60
|
+
remove.disabled = item.status === 'steering' || item.status === 'executing';
|
|
61
|
+
text.title = item.text;
|
|
62
|
+
actions.append(remove);
|
|
63
|
+
row.append(copy, actions);
|
|
64
|
+
container.append(row);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { render };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createButton(label, onClick) {
|
|
72
|
+
const button = document.createElement('button');
|
|
73
|
+
button.type = 'button';
|
|
74
|
+
button.textContent = label || '';
|
|
75
|
+
button.addEventListener('click', onClick);
|
|
76
|
+
return button;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function createIconButton(label, icon, onClick) {
|
|
80
|
+
const button = createButton('', onClick);
|
|
81
|
+
button.className = 'codex-pending-input-icon';
|
|
82
|
+
button.dataset.action = icon;
|
|
83
|
+
button.title = label || '';
|
|
84
|
+
button.setAttribute('aria-label', label || '');
|
|
85
|
+
button.append(createIcon(icon));
|
|
86
|
+
return button;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createIcon(icon) {
|
|
90
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
91
|
+
svg.setAttribute('viewBox', '0 0 20 20');
|
|
92
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
93
|
+
svg.setAttribute('focusable', 'false');
|
|
94
|
+
const paths = icon === 'guide'
|
|
95
|
+
? ['M4 4.5v4a4 4 0 0 0 4 4h7', 'm12 9 3.5 3.5L12 16']
|
|
96
|
+
: ['M6 6h8', 'M8 6V4.5h4V6', 'm7.2 8 0.6 7.5h4.4l.6-7.5'];
|
|
97
|
+
for (const value of paths) {
|
|
98
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
99
|
+
path.setAttribute('d', value);
|
|
100
|
+
svg.append(path);
|
|
101
|
+
}
|
|
102
|
+
return svg;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { create };
|
|
106
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
(function initCodexOverleafProjectProviderSelection() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
function create(deps = {}) {
|
|
5
|
+
async function confirmSwitch({ providerName } = {}) {
|
|
6
|
+
if ((deps.getRunningSessionIds?.() || []).length) {
|
|
7
|
+
deps.showToast?.(deps.tx(
|
|
8
|
+
'Wait for the running task to finish before switching providers.',
|
|
9
|
+
'请等待正在运行的任务结束后再切换模型服务。'
|
|
10
|
+
), { status: 'warning' });
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return deps.showConfirm?.({
|
|
14
|
+
title: deps.tx('Switch provider for this project?', '切换当前项目的模型服务?'),
|
|
15
|
+
message: deps.tx(
|
|
16
|
+
`All sessions in this project will use ${providerName || 'the selected provider'}. The model, reasoning, and speed choices may change; existing run history stays unchanged, while future turns start fresh provider threads and may behave differently.`,
|
|
17
|
+
`当前项目的所有会话都将使用 ${providerName || '所选模型服务'}。模型、推理强度和速度选项可能变化;已有运行历史保持不变,后续轮次会创建新的服务线程,输出效果也可能变化。`
|
|
18
|
+
),
|
|
19
|
+
confirmLabel: deps.tx('Switch provider', '切换模型服务'),
|
|
20
|
+
cancelLabel: deps.tr('confirmDefaultCancel')
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function commit(providerId, model) {
|
|
25
|
+
const state = deps.getState?.() || {};
|
|
26
|
+
const nextProviderId = providerId || 'builtin';
|
|
27
|
+
const providerChanged = nextProviderId !== (state.providerId || 'builtin');
|
|
28
|
+
const nextModel = model || deps.readSelectedModel?.() || state.model || '';
|
|
29
|
+
const nextReasoningEffort = deps.getPanel?.()?.querySelector('[data-reasoning]')?.value || state.reasoningEffort;
|
|
30
|
+
const nextSpeedTier = deps.readSelectedSpeed?.() || state.speedTier;
|
|
31
|
+
const runSelection = deps.getRunSelection?.(nextProviderId);
|
|
32
|
+
const nextProviderRevision = Number(runSelection?.providerRevision || 0);
|
|
33
|
+
const sessions = (state.sessions || []).map(session => ({
|
|
34
|
+
...session,
|
|
35
|
+
providerId: nextProviderId,
|
|
36
|
+
model: nextModel,
|
|
37
|
+
reasoningEffort: nextReasoningEffort,
|
|
38
|
+
speedTier: nextSpeedTier,
|
|
39
|
+
codexThreadId: providerChanged ? '' : session.codexThreadId,
|
|
40
|
+
pendingInputs: (session.pendingInputs || []).map(item => ({
|
|
41
|
+
...item,
|
|
42
|
+
payload: {
|
|
43
|
+
...(item.payload || {}),
|
|
44
|
+
providerId: nextProviderId,
|
|
45
|
+
providerRevision: nextProviderRevision,
|
|
46
|
+
model: nextModel,
|
|
47
|
+
reasoningEffort: nextReasoningEffort,
|
|
48
|
+
speedTier: nextSpeedTier
|
|
49
|
+
}
|
|
50
|
+
}))
|
|
51
|
+
}));
|
|
52
|
+
deps.setState?.(deps.normalizeState({
|
|
53
|
+
...state,
|
|
54
|
+
providerId: nextProviderId,
|
|
55
|
+
providerRevision: nextProviderRevision,
|
|
56
|
+
model: nextModel,
|
|
57
|
+
reasoningEffort: nextReasoningEffort,
|
|
58
|
+
speedTier: nextSpeedTier,
|
|
59
|
+
sessions
|
|
60
|
+
}));
|
|
61
|
+
deps.applyStateToPanel?.();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { confirmSwitch, commit };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
window.CodexOverleafProjectProviderSelection = { create };
|
|
68
|
+
})();
|
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
if (!approved) {
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
|
+
const Migration = window.CodexOverleafStorageMigration;
|
|
53
|
+
if (Migration?.addSessionTombstones) {
|
|
54
|
+
await Migration.addSessionTombstones(projectId, records.map(record => record.id));
|
|
55
|
+
}
|
|
52
56
|
try {
|
|
53
57
|
await mutateProjectPanelState(projectId, state => records.reduce(
|
|
54
58
|
(nextState, record) => SessionState.deleteSession(nextState, record.id),
|