codex-overleaf-link 2.6.0 → 2.6.5
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 +9 -9
- package/README.zh-CN.md +9 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +70 -0
- package/extension/src/content/composerAttachments.js +35 -8
- package/extension/src/content/contentRuntime.js +200 -51
- package/extension/src/content/diagnosticsController.js +77 -34
- package/extension/src/content/diagnosticsExport.js +1 -1
- package/extension/src/content/diagnosticsPanel.js +129 -42
- package/extension/src/content/generated/content.bundle.js +1283 -331
- package/extension/src/content/generated/content.bundle.meta.json +3 -3
- package/extension/src/content/localSkillsPanel.js +4 -2
- package/extension/src/content/markdownText.js +11 -9
- package/extension/src/content/modelPicker.js +28 -19
- package/extension/src/content/nativeCompatibilityController.js +23 -9
- package/extension/src/content/pageBridgeClient.js +5 -5
- package/extension/src/content/panelMaintenance.js +4 -0
- package/extension/src/content/panelRenderer.js +4 -1
- package/extension/src/content/providerSettingsDialog.js +26 -18
- package/extension/src/content/recentProjects.js +26 -16
- package/extension/src/content/runQueueScheduler.js +19 -9
- package/extension/src/content/runResultActions.js +51 -4
- package/extension/src/content/runReviewController.js +9 -3
- package/extension/src/content/scopedPersistencePanelState.js +34 -25
- package/extension/src/content/selectionContextView.js +30 -3
- package/extension/src/content/sharedSessionViewSync.js +9 -1
- package/extension/src/content/writebackOrchestrator.js +266 -50
- package/extension/src/content/writingStyleSettings.js +70 -17
- package/extension/src/page/overleafEditor.js +12 -3
- package/extension/src/page/overleafProjectSnapshot.js +56 -3
- package/extension/src/page/snapshotRouter.js +75 -66
- package/extension/src/page/treeOperations.js +200 -19
- package/extension/src/page/writebackRouter.js +152 -40
- package/extension/src/pageBridge.js +17 -7
- package/extension/src/shared/agentTranscript.js +23 -2
- package/extension/src/shared/auditRecords.js +1 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/composerAttachmentDraft.js +22 -7
- package/extension/src/shared/failureReasons.js +15 -0
- package/extension/src/shared/i18n.js +16 -0
- package/extension/src/shared/pageRpcContract.js +1 -1
- package/extension/src/shared/projectFiles.js +2 -1
- package/extension/src/shared/scopedPersistenceBrowserAdapter.js +34 -0
- package/extension/src/shared/staleGuard.js +58 -0
- package/extension/src/shared/storageMigration.js +104 -1
- package/extension/styles/panel.css +191 -63
- package/native-host/src/codexSessionRunner.js +1 -0
- package/native-host/src/debugLog.js +27 -3
- package/native-host/src/index.js +42 -11
- package/native-host/src/localSkills.js +1 -1
- package/native-host/src/mirrorWorkspace.js +47 -3
- package/native-host/src/nativeEnvironment.js +27 -5
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/skills/reviewer-response/SKILL.md +65 -0
- package/native-host/src/taskRunnerRuntime.js +11 -3
- package/native-host/src/updateManager.js +24 -6
- package/native-host/src/writingStyleRuntime.js +1 -1
- package/package.json +1 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"entry": "extension/entries/content-entry.mjs",
|
|
4
4
|
"output": "extension/src/content/generated/content.bundle.js",
|
|
5
|
-
"sourceDigest": "
|
|
6
|
-
"outputDigest": "
|
|
7
|
-
"byteLength":
|
|
5
|
+
"sourceDigest": "91739cf894fed56c2880986120922d3b54bb423ab2d116705385da1d0fe518be",
|
|
6
|
+
"outputDigest": "a52bf46e64085f1a64339769d64ab59246323c3a51c65851f3385750727b0624",
|
|
7
|
+
"byteLength": 3443627,
|
|
8
8
|
"inputs": [
|
|
9
9
|
"extension/entries/content-entry.mjs",
|
|
10
10
|
"extension/src/content/activeTurnControl.js",
|
|
@@ -204,7 +204,8 @@
|
|
|
204
204
|
copy.append(text);
|
|
205
205
|
const descriptions = {
|
|
206
206
|
'annotated-rewrite': tx('Guided editing for document revisions.', '用于文稿修订的编辑流程。'),
|
|
207
|
-
'parallel-subagents': tx('Split independent subtasks across multiple agents.', '将相互独立的子任务分配给多个 agent。')
|
|
207
|
+
'parallel-subagents': tx('Split independent subtasks across multiple agents.', '将相互独立的子任务分配给多个 agent。'),
|
|
208
|
+
'reviewer-response': tx('Map reviewer comments to evidence, edits and reply drafts.', '把审稿意见对应到论文证据、修改和回复草稿。')
|
|
208
209
|
};
|
|
209
210
|
const description = (!local && descriptions[id])
|
|
210
211
|
|| (typeof skill.description === 'string' ? skill.description.replace(/\s+/g, ' ').trim() : '');
|
|
@@ -249,7 +250,8 @@
|
|
|
249
250
|
if (sourceLabel) sourceLabel.textContent = sourceText();
|
|
250
251
|
toggle.setAttribute('aria-label', accessibleSkillName + ': ' + tr('codexOverleafSkillEnableToggle'));
|
|
251
252
|
const label = !local && id === 'annotated-rewrite' ? tx('Guided editing for document revisions.', '用于文稿修订的编辑流程。')
|
|
252
|
-
: !local && id === 'parallel-subagents' ? tx('Split independent subtasks across multiple agents.', '将相互独立的子任务分配给多个 agent。')
|
|
253
|
+
: !local && id === 'parallel-subagents' ? tx('Split independent subtasks across multiple agents.', '将相互独立的子任务分配给多个 agent。')
|
|
254
|
+
: !local && id === 'reviewer-response' ? tx('Map reviewer comments to evidence, edits and reply drafts.', '把审稿意见对应到论文证据、修改和回复草稿。') : '';
|
|
253
255
|
const help = copy.querySelector('.codex-local-skill-description');
|
|
254
256
|
if (label && help) { help.textContent = label; help.title = label; }
|
|
255
257
|
};
|
|
@@ -70,6 +70,15 @@
|
|
|
70
70
|
macros: getMathMacros(), renderText: buildMarkdownInlineNodesWithoutMath });
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
function buildParsedTextNodes(value) {
|
|
74
|
+
const source = String(value || '');
|
|
75
|
+
if (!MathText?.buildMathNodes) {
|
|
76
|
+
return buildLineReferenceInlineNodes(source);
|
|
77
|
+
}
|
|
78
|
+
return MathText.buildMathNodes(source, { document, katex: window.katex,
|
|
79
|
+
macros: getMathMacros(), renderText: buildLineReferenceInlineNodes });
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
function buildMarkdownInlineNodesWithoutMath(value) {
|
|
74
83
|
const source = String(value || '');
|
|
75
84
|
const nodes = [];
|
|
@@ -514,15 +523,8 @@
|
|
|
514
523
|
document,
|
|
515
524
|
markdownIt: runtimeRoot.markdownit,
|
|
516
525
|
mathText: runtimeRoot.CodexOverleafMathText,
|
|
517
|
-
renderTextNodes:
|
|
518
|
-
renderLinkNodes:
|
|
519
|
-
const safeLabel = String(label || '');
|
|
520
|
-
const target = String(href || '').trim();
|
|
521
|
-
if (!/^https?:\/\//i.test(target) && !resolveMarkdownLineReference(safeLabel, target)) return buildMarkdownInlineNodes(safeLabel);
|
|
522
|
-
return buildMarkdownInlineNodes(
|
|
523
|
-
`[${safeLabel.replace(/]/g, '\\]')}](${target.replace(/\)/g, '\\)')})`
|
|
524
|
-
);
|
|
525
|
-
},
|
|
526
|
+
renderTextNodes: buildParsedTextNodes,
|
|
527
|
+
renderLinkNodes: buildMarkdownLinkNodes,
|
|
526
528
|
renderCodeNodes: text => buildInlineCodeNodes(text),
|
|
527
529
|
sanitizeText: sanitizeAssistantVisibleText,
|
|
528
530
|
createMathNode: segment => runtimeRoot.CodexOverleafMathText.createMathNode(segment,
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
retainSelectedModel
|
|
34
34
|
} = Support;
|
|
35
35
|
|
|
36
|
-
let modelDiscovery = { status: 'fallback', source: 'fallback', fetchedAt: '' };
|
|
36
|
+
let modelDiscovery = { status: 'fallback', source: 'fallback', fetchedAt: '', provisional: true };
|
|
37
37
|
let modelLoadGeneration = 0;
|
|
38
38
|
|
|
39
39
|
function getModelDiscovery() {
|
|
@@ -83,24 +83,21 @@
|
|
|
83
83
|
const modelSelect = getPanel()?.querySelector('[data-model]');
|
|
84
84
|
const reasoningSelect = getPanel()?.querySelector('[data-reasoning]');
|
|
85
85
|
const speedSelect = getPanel()?.querySelector('[data-speed]');
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
const changedInput = choice.dataset.reasoningChoice ? reasoningSelect
|
|
87
|
+
: choice.dataset.modelChoice ? modelSelect : choice.dataset.speedChoice ? speedSelect : null;
|
|
88
|
+
const value = choice.dataset.reasoningChoice || choice.dataset.modelChoice || choice.dataset.speedChoice;
|
|
89
|
+
const option = Array.from(changedInput?.options || []).find(item => item.value === value);
|
|
90
|
+
if (!changedInput || changedInput.disabled || !option || option.disabled) return;
|
|
91
|
+
|
|
92
|
+
changedInput.value = value;
|
|
93
|
+
if (changedInput === modelSelect) {
|
|
94
94
|
renderReasoningOptions(getRenderedModelEntries());
|
|
95
95
|
renderSpeedOptions(getRenderedModelEntries());
|
|
96
|
-
} else if (choice.dataset.speedChoice && speedSelect) {
|
|
97
|
-
speedSelect.value = choice.dataset.speedChoice;
|
|
98
|
-
changedInput = speedSelect;
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
renderModelConfigChoices();
|
|
102
99
|
updateModelDisplay();
|
|
103
|
-
await persistPanelInputs(
|
|
100
|
+
await persistPanelInputs({ type: 'change', target: changedInput });
|
|
104
101
|
}
|
|
105
102
|
async function loadModelOptions(providerSelection, options = {}) {
|
|
106
103
|
const generation = ++modelLoadGeneration;
|
|
@@ -136,7 +133,9 @@
|
|
|
136
133
|
}
|
|
137
134
|
const sourceModels = hasDiscoveredModels ? response.result.models : fallbackModels;
|
|
138
135
|
const preserveBuiltin = (selection?.providerId || 'builtin') === 'builtin' && (getState()?.providerId || 'builtin') === 'builtin';
|
|
139
|
-
const retainedSelectedModel = retainSelectedModel(sourceModels, currentSelectedModel, { preserveUnavailable: preserveBuiltin })
|
|
136
|
+
const retainedSelectedModel = retainSelectedModel(sourceModels, currentSelectedModel, { preserveUnavailable: preserveBuiltin })
|
|
137
|
+
|| (responseProviderId && responseProviderId !== 'builtin'
|
|
138
|
+
? retainSelectedModel(sourceModels, response.result?.defaultModelId) : '');
|
|
140
139
|
const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel: retainedSelectedModel });
|
|
141
140
|
if (normalized.usedFallback && response.result?.providerId && response.result.providerId !== 'builtin') {
|
|
142
141
|
applyUnavailableModelOptions({ code: 'provider_model_catalog_invalid', message: 'The active provider returned no usable models.' });
|
|
@@ -238,6 +237,13 @@
|
|
|
238
237
|
}
|
|
239
238
|
|
|
240
239
|
const selectedId = normalizeModelOptionId(selectedModel);
|
|
240
|
+
if (options.provisionalSelected === true) {
|
|
241
|
+
models = [...(Array.isArray(models) ? models : [])];
|
|
242
|
+
if (selectedId && !models.some(model => normalizeModelOptionId(model?.id) === selectedId)) {
|
|
243
|
+
models.push({ id: selectedId, label: selectedId, unverified: true });
|
|
244
|
+
}
|
|
245
|
+
models = Support.preserveSelectedCapabilities(models, selectedId, getState(), true);
|
|
246
|
+
}
|
|
241
247
|
modelSelect.textContent = '';
|
|
242
248
|
modelSelect.disabled = false;
|
|
243
249
|
let renderedSelected = false;
|
|
@@ -373,7 +379,8 @@
|
|
|
373
379
|
list.append(createModelConfigChoice({
|
|
374
380
|
value: option.value,
|
|
375
381
|
label: formatReasoningEffortLabel(option.value, reasoningSelect.dataset.presentation),
|
|
376
|
-
datasetName: 'reasoningChoice'
|
|
382
|
+
datasetName: 'reasoningChoice',
|
|
383
|
+
disabled: reasoningSelect.disabled || option.disabled
|
|
377
384
|
}));
|
|
378
385
|
}
|
|
379
386
|
}
|
|
@@ -390,7 +397,8 @@
|
|
|
390
397
|
const choice = createModelConfigChoice({
|
|
391
398
|
value: option.value,
|
|
392
399
|
label: option.textContent || option.value,
|
|
393
|
-
datasetName: 'modelChoice'
|
|
400
|
+
datasetName: 'modelChoice',
|
|
401
|
+
disabled: modelSelect.disabled || option.disabled
|
|
394
402
|
});
|
|
395
403
|
choice.title = option.textContent || option.value;
|
|
396
404
|
list.append(choice);
|
|
@@ -408,16 +416,17 @@
|
|
|
408
416
|
const choice = createModelConfigChoice({
|
|
409
417
|
value: option.value,
|
|
410
418
|
label: formatSpeedTierLabel(option.value),
|
|
411
|
-
datasetName: 'speedChoice'
|
|
419
|
+
datasetName: 'speedChoice',
|
|
420
|
+
disabled: speedSelect.disabled || option.disabled
|
|
412
421
|
});
|
|
413
|
-
choice.disabled = speedSelect.disabled && speedSelect.options.length <= 1;
|
|
414
422
|
list.append(choice);
|
|
415
423
|
}
|
|
416
424
|
}
|
|
417
425
|
|
|
418
|
-
function createModelConfigChoice({ value, label, datasetName }) {
|
|
426
|
+
function createModelConfigChoice({ value, label, datasetName, disabled = false }) {
|
|
419
427
|
const button = document.createElement('button');
|
|
420
428
|
button.type = 'button';
|
|
429
|
+
button.disabled = disabled;
|
|
421
430
|
button.className = 'codex-model-config-choice';
|
|
422
431
|
button.dataset[datasetName] = value;
|
|
423
432
|
button.setAttribute('role', 'menuitemradio');
|
|
@@ -37,8 +37,10 @@
|
|
|
37
37
|
try {
|
|
38
38
|
compatibilityGate = await ensureForMethod(payload?.method, context);
|
|
39
39
|
if (!compatibilityGate.ok) return compatibilityGate.response;
|
|
40
|
+
// Catalog reads belong to the selected session, not a retained cancelled run.
|
|
41
|
+
const independentCatalog = payload?.method === 'codex.models' || payload?.method === 'codex.providers.list';
|
|
40
42
|
// Post-Undo cache bookkeeping must survive the ended run's cancellation.
|
|
41
|
-
if (gatedMethods.has(payload?.method) && payload.method !== 'mirror.invalidate') {
|
|
43
|
+
if (gatedMethods.has(payload?.method) && payload.method !== 'mirror.invalidate' && !independentCatalog) {
|
|
42
44
|
options.throwIfCancellationRequested?.();
|
|
43
45
|
}
|
|
44
46
|
context.assertCurrent?.();
|
|
@@ -147,7 +149,9 @@
|
|
|
147
149
|
options.setDiagnosticsHealth?.(classification === 'update-available' ? 'warn' : 'fail');
|
|
148
150
|
options.setBadge?.(renderer.headerEl, {
|
|
149
151
|
type: 'update',
|
|
150
|
-
tooltip:
|
|
152
|
+
tooltip: compatibility.status === 'native_unhealthy'
|
|
153
|
+
? tx('Codex Unavailable', 'Codex 不可用')
|
|
154
|
+
: tx('Native host update available', 'Native host 可更新'),
|
|
151
155
|
onClick: () => showUpdateGuidance(compatibility)
|
|
152
156
|
});
|
|
153
157
|
if (!compatibility?.native?.version) maybePromptFirstRunSetup();
|
|
@@ -191,7 +195,9 @@
|
|
|
191
195
|
|| compatibility.nativeVersion
|
|
192
196
|
|| native.version
|
|
193
197
|
|| tx('missing', '未安装');
|
|
194
|
-
const
|
|
198
|
+
const codexUnavailable = compatibility.status === 'native_unhealthy';
|
|
199
|
+
const resolvedCommand = resolveInstallCommand(compatibility);
|
|
200
|
+
const command = codexUnavailable ? '' : resolvedCommand;
|
|
195
201
|
const releaseUrl = compatibility.releaseUrl || '';
|
|
196
202
|
const overlay = options.document.createElement('div');
|
|
197
203
|
overlay.className = 'codex-plugin-confirm';
|
|
@@ -201,7 +207,9 @@
|
|
|
201
207
|
const nativeMissing = !native.version;
|
|
202
208
|
overlay.setAttribute(
|
|
203
209
|
'aria-label',
|
|
204
|
-
|
|
210
|
+
codexUnavailable
|
|
211
|
+
? tx('Codex Unavailable', 'Codex 不可用')
|
|
212
|
+
: nativeMissing
|
|
205
213
|
? tx('Set up the native host', '安装 Native host')
|
|
206
214
|
: tx('Native host update available', 'Native host 可更新')
|
|
207
215
|
);
|
|
@@ -220,14 +228,19 @@
|
|
|
220
228
|
brand.textContent = 'Codex Overleaf Link';
|
|
221
229
|
const title = options.document.createElement('div');
|
|
222
230
|
title.className = 'codex-plugin-confirm-title';
|
|
223
|
-
title.textContent =
|
|
231
|
+
title.textContent = codexUnavailable
|
|
232
|
+
? tx('Codex Unavailable', 'Codex 不可用')
|
|
233
|
+
: nativeMissing
|
|
224
234
|
? tx('One step left: install the native host', '还差一步:安装 Native host')
|
|
225
235
|
: tx('Native host update available', 'Native host 可更新');
|
|
226
236
|
titleWrap.append(brand, title);
|
|
227
237
|
head.append(icon, titleWrap);
|
|
228
238
|
const body = options.document.createElement('div');
|
|
229
239
|
body.className = 'codex-plugin-confirm-body';
|
|
230
|
-
body.textContent = [
|
|
240
|
+
body.textContent = codexUnavailable ? [
|
|
241
|
+
tx('The extension reached the native host, but local Codex was not found, so tasks cannot start.', '插件已经连上 native host,但没有找到本机 Codex,所以不能启动任务。'),
|
|
242
|
+
tx('Make sure codex works in Terminal, then reload the extension.', '请确认终端里可以运行 codex,然后重新加载扩展。')
|
|
243
|
+
].join('\n\n') : [
|
|
231
244
|
nativeMissing
|
|
232
245
|
? tx(
|
|
233
246
|
`Extension v${extensionVersion} is ready; Codex still needs its local bridge to read and edit this project.`,
|
|
@@ -247,8 +260,8 @@
|
|
|
247
260
|
const commandCode = options.document.createElement('code');
|
|
248
261
|
commandCode.className = 'codex-native-guidance-command';
|
|
249
262
|
commandCode.textContent = command;
|
|
250
|
-
body.append(commandCode);
|
|
251
|
-
if (releaseUrl) {
|
|
263
|
+
if (!codexUnavailable) body.append(commandCode);
|
|
264
|
+
if (!codexUnavailable && releaseUrl) {
|
|
252
265
|
const link = options.document.createElement('a');
|
|
253
266
|
link.className = 'codex-native-guidance-link';
|
|
254
267
|
link.href = releaseUrl;
|
|
@@ -274,7 +287,8 @@
|
|
|
274
287
|
close.className = 'codex-plugin-confirm-cancel';
|
|
275
288
|
close.textContent = tx('Close', '关闭');
|
|
276
289
|
close.addEventListener('click', () => overlay.remove());
|
|
277
|
-
actions.append(close
|
|
290
|
+
actions.append(close);
|
|
291
|
+
if (!codexUnavailable) actions.append(copy);
|
|
278
292
|
card.append(head, body, actions);
|
|
279
293
|
overlay.append(card);
|
|
280
294
|
overlay.addEventListener('click', event => {
|
|
@@ -10,21 +10,22 @@
|
|
|
10
10
|
['src/shared/projectFiles.js', 'codex-overleaf-project-files-script'],
|
|
11
11
|
['src/shared/compatibility.js', 'codex-overleaf-compatibility-page-script'],
|
|
12
12
|
['src/shared/pageRpcContract.js', 'codex-overleaf-page-rpc-contract-script', true],
|
|
13
|
-
['src/shared/staleGuard.js', 'codex-overleaf-stale-guard-script'],
|
|
13
|
+
['src/shared/staleGuard.js', 'codex-overleaf-stale-guard-script', true],
|
|
14
|
+
['src/shared/otText.js', 'codex-overleaf-ot-text-script'],
|
|
14
15
|
['src/shared/compileAdapter.js', 'codex-overleaf-compile-adapter-script'],
|
|
15
16
|
['src/shared/sensitiveScan.js', 'codex-overleaf-sensitive-scan-script'],
|
|
16
17
|
['src/shared/auditRecords.js', 'codex-overleaf-audit-records-script'],
|
|
17
18
|
['src/page/saveState.js', 'codex-overleaf-save-state-script', true],
|
|
18
19
|
['src/page/overleafCapabilities.js', 'codex-overleaf-capabilities-script', true],
|
|
19
20
|
['src/page/compileBridge.js', 'codex-overleaf-compile-bridge-script'],
|
|
20
|
-
['src/page/overleafEditor.js', 'codex-overleaf-editor-script'],
|
|
21
|
-
['src/page/overleafProjectSnapshot.js', 'codex-overleaf-project-snapshot-script'],
|
|
21
|
+
['src/page/overleafEditor.js', 'codex-overleaf-editor-script', true],
|
|
22
|
+
['src/page/overleafProjectSnapshot.js', 'codex-overleaf-project-snapshot-script', true],
|
|
22
23
|
['src/page/pageBridgeCapability.js', 'codex-overleaf-page-bridge-capability-script'],
|
|
23
24
|
['src/page/treeOperations.js', 'codex-overleaf-tree-operations-script', true],
|
|
24
25
|
['src/page/ownedDirectoryCleanup.js', 'codex-overleaf-owned-directory-cleanup-script', true],
|
|
25
26
|
['src/page/textFileCreator.js', 'codex-overleaf-text-file-creator-script', true],
|
|
26
27
|
['src/page/binaryAssetUploader.js', 'codex-overleaf-binary-asset-uploader-script', true],
|
|
27
|
-
['src/page/snapshotRouter.js', 'codex-overleaf-snapshot-router-script'],
|
|
28
|
+
['src/page/snapshotRouter.js', 'codex-overleaf-snapshot-router-script', true],
|
|
28
29
|
['src/page/referenceProjects.js', 'codex-overleaf-reference-projects-script', true],
|
|
29
30
|
['src/page/fileMover.js', 'codex-overleaf-file-mover-script', true],
|
|
30
31
|
['src/page/writeGuard.js', 'codex-overleaf-write-guard-script', true],
|
|
@@ -39,7 +40,6 @@
|
|
|
39
40
|
['src/pageBridge.js', 'codex-overleaf-page-bridge-script', true]
|
|
40
41
|
]);
|
|
41
42
|
const OPTIONAL_OT_SCRIPTS = Object.freeze([
|
|
42
|
-
['src/shared/otText.js', 'codex-overleaf-ot-text-script'],
|
|
43
43
|
['src/page/overleafRealtimeObserver.js', 'codex-overleaf-realtime-observer-script']
|
|
44
44
|
]);
|
|
45
45
|
|
|
@@ -189,6 +189,10 @@
|
|
|
189
189
|
showPluginToast(tx('Nothing to refill: the original task text is unavailable.', '无法回填:原任务文本不可用。'));
|
|
190
190
|
return;
|
|
191
191
|
}
|
|
192
|
+
if (input.value.trim() && input.value.trim() !== task.trim()) {
|
|
193
|
+
showPluginToast(tx('Kept your draft. Clear the composer, then try again to refill this task.', '已保留你的草稿。清空输入框后再试一次,即可回填这条任务。'));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
192
196
|
prepareRetryReplacement?.(run || null, task);
|
|
193
197
|
input.value = task;
|
|
194
198
|
getState().task = task;
|
|
@@ -228,7 +228,10 @@
|
|
|
228
228
|
return compact;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
function setVisible(
|
|
231
|
+
function setVisible(target, visible) {
|
|
232
|
+
const instance = target?._instance;
|
|
233
|
+
const panelEl = instance?.panelEl || target;
|
|
234
|
+
if (visible && instance) updateCompactMode(instance);
|
|
232
235
|
panelEl?.classList?.toggle('is-open', Boolean(visible));
|
|
233
236
|
document.documentElement.classList.toggle(ROOT_CLASS, Boolean(visible));
|
|
234
237
|
const launcherEl = panelEl?.ownerDocument?.getElementById?.('codex-overleaf-launcher');
|
|
@@ -613,18 +613,7 @@
|
|
|
613
613
|
actions.innerHTML = instance.embedded ? '' : `<button type="button" class="codex-provider-secondary-button" data-provider-action="close">${escapeHtml(tx('Close', '关闭'))}</button>`;
|
|
614
614
|
return;
|
|
615
615
|
}
|
|
616
|
-
const
|
|
617
|
-
const isNew = !provider.id;
|
|
618
|
-
const destinationChanged = active && getCurrentBaseUrl(instance) !== provider.endpointDisclosureBaseUrl;
|
|
619
|
-
const actionState = getFooterActionState({
|
|
620
|
-
isNew,
|
|
621
|
-
active,
|
|
622
|
-
dirty: instance.dirty,
|
|
623
|
-
destinationChanged,
|
|
624
|
-
canSave: canSaveCurrentDraft(instance),
|
|
625
|
-
hasProject: instance.callbacks.hasCurrentProject?.() !== false,
|
|
626
|
-
canActivate: canActivateCurrentProvider(instance)
|
|
627
|
-
});
|
|
616
|
+
const actionState = getCurrentFooterActionState(instance);
|
|
628
617
|
const saveDisabled = actionState.saveEnabled ? '' : ' disabled aria-disabled="true"';
|
|
629
618
|
const useDisabled = actionState.useEnabled ? '' : ' disabled aria-disabled="true"';
|
|
630
619
|
actions.innerHTML = `
|
|
@@ -638,6 +627,21 @@
|
|
|
638
627
|
`;
|
|
639
628
|
}
|
|
640
629
|
|
|
630
|
+
function getCurrentFooterActionState(instance) {
|
|
631
|
+
const provider = getSelectedProvider(instance);
|
|
632
|
+
const active = Boolean(provider?.id && provider.id === getCurrentProjectProviderId(instance));
|
|
633
|
+
return getFooterActionState({
|
|
634
|
+
isNew: !provider?.id,
|
|
635
|
+
active,
|
|
636
|
+
dirty: instance.dirty,
|
|
637
|
+
destinationChanged: active && getCurrentBaseUrl(instance) !== provider.endpointDisclosureBaseUrl,
|
|
638
|
+
disclosureAccepted: instance.root.querySelector('[data-provider-disclosure]')?.checked === true,
|
|
639
|
+
canSave: canSaveCurrentDraft(instance),
|
|
640
|
+
hasProject: instance.callbacks.hasCurrentProject?.() !== false,
|
|
641
|
+
canActivate: canActivateCurrentProvider(instance)
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
641
645
|
function getFooterActionState(options = {}) {
|
|
642
646
|
const isNew = options.isNew === true;
|
|
643
647
|
const active = options.active === true;
|
|
@@ -645,9 +649,9 @@
|
|
|
645
649
|
return {
|
|
646
650
|
showSave: isNew || dirty,
|
|
647
651
|
showSaveAndUse: options.hasProject !== false && (isNew || dirty) && (!active || options.destinationChanged === true),
|
|
648
|
-
showUse: options.hasProject !== false && !isNew && !active && !dirty,
|
|
652
|
+
showUse: options.hasProject !== false && !isNew && (!active || options.destinationChanged === true) && !dirty,
|
|
649
653
|
saveEnabled: options.canSave === true,
|
|
650
|
-
useEnabled: options.canActivate === true
|
|
654
|
+
useEnabled: options.canActivate === true && (!options.destinationChanged || options.disclosureAccepted === true)
|
|
651
655
|
};
|
|
652
656
|
}
|
|
653
657
|
|
|
@@ -837,6 +841,11 @@
|
|
|
837
841
|
refreshTestState(instance);
|
|
838
842
|
return;
|
|
839
843
|
}
|
|
844
|
+
if (event.target.matches('[data-provider-disclosure]')) {
|
|
845
|
+
renderFooter(instance);
|
|
846
|
+
applyBusyState(instance);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
840
849
|
if (event.target.matches('[data-provider-field]')) {
|
|
841
850
|
const field = event.target.dataset.providerField;
|
|
842
851
|
if (event.target.dataset.providerField === 'apiKey') {
|
|
@@ -1023,8 +1032,7 @@
|
|
|
1023
1032
|
|
|
1024
1033
|
function applyBusyState(instance) {
|
|
1025
1034
|
const busy = Boolean(instance.busy && instance.busy !== 'failed');
|
|
1026
|
-
const
|
|
1027
|
-
const canActivate = canActivateCurrentProvider(instance);
|
|
1035
|
+
const actionState = getCurrentFooterActionState(instance);
|
|
1028
1036
|
instance.root.dataset.busy = instance.busy || '';
|
|
1029
1037
|
for (const element of instance.root.querySelectorAll('input, select, textarea, button')) {
|
|
1030
1038
|
if (element.matches('[data-provider-confirm-answer]')) {
|
|
@@ -1034,9 +1042,9 @@
|
|
|
1034
1042
|
} else if (element.matches('[data-provider-action="cancel-test"]')) {
|
|
1035
1043
|
element.disabled = false;
|
|
1036
1044
|
} else if (element.matches('[data-provider-action="save"], [data-provider-action="save-use"]')) {
|
|
1037
|
-
element.disabled = busy || !
|
|
1045
|
+
element.disabled = busy || !actionState.saveEnabled;
|
|
1038
1046
|
} else if (element.matches('[data-provider-action="use"]')) {
|
|
1039
|
-
element.disabled = busy || !
|
|
1047
|
+
element.disabled = busy || !actionState.useEnabled;
|
|
1040
1048
|
} else {
|
|
1041
1049
|
element.disabled = busy;
|
|
1042
1050
|
}
|
|
@@ -455,10 +455,10 @@
|
|
|
455
455
|
};
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
-
async function refreshDashboardView() {
|
|
458
|
+
async function refreshDashboardView(options = {}) {
|
|
459
459
|
var panelEl = getPanel();
|
|
460
460
|
if (panelEl && panelEl.dataset.view === 'recent-projects') {
|
|
461
|
-
await renderRecentProjectsVariant(dashboardViewOptions());
|
|
461
|
+
await renderRecentProjectsVariant(Object.assign(dashboardViewOptions(), options));
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
464
|
|
|
@@ -634,7 +634,7 @@
|
|
|
634
634
|
: {};
|
|
635
635
|
map[projectId] = record.id;
|
|
636
636
|
prefs.activeSessionByProject = map;
|
|
637
|
-
await Migration.savePrefs(
|
|
637
|
+
await Migration.savePrefs({ activeSessionByProject: map }, accountScopeId, projectId);
|
|
638
638
|
}
|
|
639
639
|
} catch (_error) { /* fall through: opening the project still works */ }
|
|
640
640
|
openProjectFromRow(projectId);
|
|
@@ -786,19 +786,31 @@
|
|
|
786
786
|
input.focus();
|
|
787
787
|
input.select();
|
|
788
788
|
var settled = false;
|
|
789
|
-
var finish = function (commit) {
|
|
789
|
+
var finish = async function (commit) {
|
|
790
790
|
if (settled) {
|
|
791
791
|
return;
|
|
792
792
|
}
|
|
793
793
|
settled = true;
|
|
794
794
|
var nextRaw = input.value;
|
|
795
|
-
input.remove();
|
|
796
|
-
titleEl.hidden = false;
|
|
797
795
|
// Unchanged input is a cancel: never rewrites the title source.
|
|
798
796
|
if (!commit || nextRaw.trim() === seed.trim()) {
|
|
797
|
+
input.remove();
|
|
798
|
+
titleEl.hidden = false;
|
|
799
799
|
return;
|
|
800
800
|
}
|
|
801
|
-
|
|
801
|
+
input.readOnly = true;
|
|
802
|
+
try {
|
|
803
|
+
await commitDashboardSessionRename(sessionsEl, projectId, record, nextRaw);
|
|
804
|
+
} catch (_error) {
|
|
805
|
+
settled = false;
|
|
806
|
+
input.readOnly = false;
|
|
807
|
+
showPluginToast(tr('recentProjects_renameFailed'), { status: 'failed', sticky: true });
|
|
808
|
+
input.focus();
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
input.remove();
|
|
812
|
+
titleEl.hidden = false;
|
|
813
|
+
await refreshDashboardView().catch(function () { /* keep last render */ });
|
|
802
814
|
};
|
|
803
815
|
input.addEventListener('keydown', function (event) {
|
|
804
816
|
if (event.key === 'Enter') {
|
|
@@ -833,15 +845,12 @@
|
|
|
833
845
|
return SessionState.renameSession(state, record.id, rawTitle, { placeholderTitle: tr('newSessionFallback') });
|
|
834
846
|
});
|
|
835
847
|
} catch (_error) { /* blob may be absent; record update still proceeds */ }
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
843
|
-
} catch (_error) { /* swallow */ }
|
|
844
|
-
await refreshDashboardView();
|
|
848
|
+
if (StorageDb) {
|
|
849
|
+
await StorageDb.putRecord('sessions', StorageDb.buildSessionRecord(Object.assign({}, record, {
|
|
850
|
+
title: next.title,
|
|
851
|
+
titleSource: next.titleSource
|
|
852
|
+
}), { preserveRunActionPayload: true }));
|
|
853
|
+
}
|
|
845
854
|
}
|
|
846
855
|
|
|
847
856
|
function ensureRecentProjectsRoot() {
|
|
@@ -1071,6 +1080,7 @@
|
|
|
1071
1080
|
openProjectFromRow,
|
|
1072
1081
|
renderRecentProjectRow,
|
|
1073
1082
|
renderRecentProjectsVariant,
|
|
1083
|
+
refreshDashboardView,
|
|
1074
1084
|
mountDashboardShell,
|
|
1075
1085
|
resetProjectNameCache,
|
|
1076
1086
|
renderPerProjectVariant
|
|
@@ -42,10 +42,15 @@
|
|
|
42
42
|
reason: status === 'completed' ? 'completed' : 'continue_queue'
|
|
43
43
|
})
|
|
44
44
|
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
}
|
|
46
|
+
const canStart = shouldContinue ? options.canStart?.(sessionId) : true;
|
|
47
|
+
const latestQueue = options.getQueue?.(sessionId) || originalQueue;
|
|
48
|
+
const inactive = shouldContinue && canStart !== 'session_transition'
|
|
49
|
+
&& (canStart === false || options.isRunning?.())
|
|
50
|
+
&& latestQueue.some(item => item?.status === 'queued');
|
|
51
|
+
if (!shouldContinue || inactive) {
|
|
52
|
+
const reason = inactive ? 'session_inactive' : status || 'run_did_not_complete';
|
|
53
|
+
const paused = latestQueue.length ? Queue.pauseAll(latestQueue, reason) : latestQueue;
|
|
49
54
|
options.setQueue?.(sessionId, paused);
|
|
50
55
|
try {
|
|
51
56
|
await options.persist?.({
|
|
@@ -55,19 +60,23 @@
|
|
|
55
60
|
const latestQueue = options.getQueue?.(sessionId) || paused;
|
|
56
61
|
options.setQueue?.(
|
|
57
62
|
sessionId,
|
|
58
|
-
Queue.pauseAll(latestQueue,
|
|
63
|
+
Queue.pauseAll(latestQueue, reason)
|
|
59
64
|
);
|
|
60
65
|
throw error;
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
options.onChange?.(sessionId);
|
|
64
|
-
if (shouldContinue) {
|
|
69
|
+
if (shouldContinue && !inactive) {
|
|
65
70
|
scheduleOne(sessionId);
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
function scheduleOne(sessionId) {
|
|
70
|
-
if (scheduling || options.isRunning?.()
|
|
75
|
+
if (scheduling || options.isRunning?.()) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const canStart = options.canStart?.(sessionId);
|
|
79
|
+
if (canStart === false || canStart === 'session_transition') {
|
|
71
80
|
return false;
|
|
72
81
|
}
|
|
73
82
|
const initialQueue = options.getQueue?.(sessionId) || [];
|
|
@@ -195,7 +204,7 @@
|
|
|
195
204
|
setQueue: (sessionId, pendingInputs) => options.setQueue?.(sessionId, pendingInputs),
|
|
196
205
|
persist: persistenceOptions => options.save?.(persistenceOptions),
|
|
197
206
|
isRunning: () => Boolean(options.getCurrentRun?.()),
|
|
198
|
-
canStart: sessionId => options.canStart?.(sessionId)
|
|
207
|
+
canStart: sessionId => options.canStart?.(sessionId),
|
|
199
208
|
start: (item, sessionId) => startQueuedInput(item, sessionId),
|
|
200
209
|
onChange: () => render(),
|
|
201
210
|
onDequeued: (item, context) => options.removeGuidance?.(item.id, {
|
|
@@ -445,7 +454,8 @@
|
|
|
445
454
|
|
|
446
455
|
async function startQueuedInput(item, sessionId) {
|
|
447
456
|
const assertCanStart = () => {
|
|
448
|
-
|
|
457
|
+
const canStart = options.canStart?.(sessionId);
|
|
458
|
+
if (canStart === false || canStart === 'session_transition' || options.getCurrentRun?.()) {
|
|
449
459
|
const error = new Error('Queued input cannot start in the current session.');
|
|
450
460
|
error.code = 'queue_session_not_active';
|
|
451
461
|
throw error;
|
|
@@ -74,17 +74,18 @@
|
|
|
74
74
|
const existing = root.querySelector('[data-run-copy]');
|
|
75
75
|
const button = existing.cloneNode(true);
|
|
76
76
|
existing.replaceWith(button);
|
|
77
|
-
const
|
|
78
|
-
const copyText = String(finalAnswer?.innerText || finalAnswer?.textContent || '').trim();
|
|
77
|
+
const available = Boolean(readConclusionText(root).trim());
|
|
79
78
|
button.hidden = false;
|
|
80
|
-
button.disabled = !
|
|
79
|
+
button.disabled = !available;
|
|
81
80
|
button.title = tr('copyConclusionTitle');
|
|
82
81
|
button.setAttribute('aria-label', button.title);
|
|
83
82
|
button.innerHTML = '<svg viewBox="0 0 20 20" aria-hidden="true"><g transform="translate(.75 .75)"><rect x="6.5" y="6.5" width="9" height="9" rx="2"/><path d="M13.5 6.5V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v6.5a2 2 0 0 0 2 2h1.5"/></g></svg>';
|
|
84
|
-
if (!
|
|
83
|
+
if (!available) return;
|
|
85
84
|
button.addEventListener('click', async event => {
|
|
86
85
|
event.stopPropagation();
|
|
87
86
|
try {
|
|
87
|
+
const copyText = readConclusionText(root);
|
|
88
|
+
if (!copyText.trim()) return;
|
|
88
89
|
await writeClipboardText(copyText);
|
|
89
90
|
button.title = tr('copyConclusionDone');
|
|
90
91
|
} catch (_error) {
|
|
@@ -94,6 +95,52 @@
|
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
function readConclusionText(root) {
|
|
99
|
+
const answer = root.querySelector('.run-completion-report .run-final-answer');
|
|
100
|
+
if (!answer) return '';
|
|
101
|
+
return extractConclusionText(answer).replace(/\u200b/g, '').replace(/[ \t]+$/gm, '').trim();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractConclusionText(node) {
|
|
105
|
+
if (node.nodeType === 3) return String(node.textContent || '').replace(/\s+/g, ' ');
|
|
106
|
+
const tag = node.tagName?.toLowerCase();
|
|
107
|
+
if (tag === 'br') return '\n';
|
|
108
|
+
if (tag === 'pre' || tag === 'code') return node.textContent || '';
|
|
109
|
+
if (node.classList?.contains('run-math')) {
|
|
110
|
+
const source = node.dataset.mathRendered === 'false'
|
|
111
|
+
? node.textContent || ''
|
|
112
|
+
: node.querySelector('annotation[encoding="application/x-tex"]')?.textContent || '';
|
|
113
|
+
const delimiter = node.dataset.mathDisplay === 'block' ? '$$' : '$';
|
|
114
|
+
return source ? `${delimiter}${source}${delimiter}` : '';
|
|
115
|
+
}
|
|
116
|
+
const children = Array.from(node.childNodes || []);
|
|
117
|
+
if (tag === 'ul' || tag === 'ol') {
|
|
118
|
+
const start = Number(node.getAttribute('start') || 1);
|
|
119
|
+
return children.filter(child => child.tagName?.toLowerCase() === 'li')
|
|
120
|
+
.map((child, index) => `${tag === 'ol' ? `${start + index}. ` : '- '}${extractConclusionText(child)}`)
|
|
121
|
+
.join('\n');
|
|
122
|
+
}
|
|
123
|
+
if (tag === 'table') {
|
|
124
|
+
return Array.from(node.querySelectorAll('tr'), row => Array.from(row.children,
|
|
125
|
+
cell => extractConclusionText(cell)).join('\t')).join('\n');
|
|
126
|
+
}
|
|
127
|
+
let text = '';
|
|
128
|
+
let previousBlock = false;
|
|
129
|
+
for (const child of children) {
|
|
130
|
+
const value = extractConclusionText(child);
|
|
131
|
+
if (!value) continue;
|
|
132
|
+
const block = /^(p|h[1-6]|blockquote|ul|ol|pre|table|div)$/.test(child.tagName?.toLowerCase())
|
|
133
|
+
|| child.dataset?.mathDisplay === 'block';
|
|
134
|
+
if (text && (previousBlock || block)) {
|
|
135
|
+
const newlines = (text.match(/\n*$/)?.[0].length || 0) + (value.match(/^\n*/)?.[0].length || 0);
|
|
136
|
+
text += '\n'.repeat(Math.max(0, 2 - newlines));
|
|
137
|
+
}
|
|
138
|
+
text += value;
|
|
139
|
+
previousBlock = block;
|
|
140
|
+
}
|
|
141
|
+
return text;
|
|
142
|
+
}
|
|
143
|
+
|
|
97
144
|
async function writeClipboardText(text) {
|
|
98
145
|
if (navigatorRef?.clipboard?.writeText) {
|
|
99
146
|
await navigatorRef.clipboard.writeText(text);
|