codex-overleaf-link 2.5.1 → 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 +149 -398
- package/README.zh-CN.md +153 -404
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +148 -3
- package/extension/src/backgroundSharedSessions.js +55 -2
- package/extension/src/backgroundUpdateCoordinator.js +128 -12
- package/extension/src/content/activeTurnControl.js +164 -7
- package/extension/src/content/composerAttachments.js +46 -10
- package/extension/src/content/composerPanel.js +92 -12
- package/extension/src/content/contentRuntime.js +1114 -321
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +77 -34
- package/extension/src/content/diagnosticsExport.js +1 -1
- package/extension/src/content/diagnosticsPanel.js +147 -42
- package/extension/src/content/generated/content.bundle.js +5761 -1237
- package/extension/src/content/generated/content.bundle.meta.json +6 -3
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/localSkillsPanel.js +6 -4
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +53 -14
- package/extension/src/content/modelPicker.js +29 -19
- package/extension/src/content/nativeCompatibilityController.js +52 -19
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/pageBridgeClient.js +7 -7
- package/extension/src/content/panelMaintenance.js +10 -2
- package/extension/src/content/panelRenderer.js +55 -28
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectSettingsCoordinator.js +30 -6
- package/extension/src/content/providerSettingsDialog.js +28 -19
- package/extension/src/content/recentProjects.js +30 -19
- package/extension/src/content/runActivitySummary.js +122 -14
- package/extension/src/content/runCompletionMeta.js +194 -6
- package/extension/src/content/runController.js +2 -1
- package/extension/src/content/runFailureNotice.js +8 -1
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +27 -13
- package/extension/src/content/runRecoveryActions.js +2 -1
- package/extension/src/content/runResultActions.js +73 -24
- package/extension/src/content/runReviewController.js +102 -37
- package/extension/src/content/runTimelineView.js +224 -25
- package/extension/src/content/scopedPersistencePanelState.js +124 -46
- package/extension/src/content/selectionContextView.js +30 -3
- package/extension/src/content/sensitivePreflight.js +14 -1
- package/extension/src/content/sessionManager.js +49 -3
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +15 -2
- package/extension/src/content/settingsWorkbench.js +127 -89
- package/extension/src/content/sharedSessionViewSync.js +13 -4
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateNotice.js +60 -6
- package/extension/src/content/writebackOrchestrator.js +301 -72
- package/extension/src/content/writingStyleSettings.js +70 -17
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/overleafCapabilities.js +4 -1
- 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/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +9 -1
- package/extension/src/page/treeOperations.js +200 -19
- package/extension/src/page/writebackRouter.js +172 -113
- package/extension/src/pageBridge.js +124 -73
- package/extension/src/popup.js +1 -1
- package/extension/src/shared/agentTranscript.js +372 -18
- package/extension/src/shared/auditRecords.js +3 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/compileAdapter.js +58 -17
- package/extension/src/shared/composerAttachmentDraft.js +27 -7
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/failureReasons.js +57 -3
- package/extension/src/shared/i18n.js +315 -29
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/pageRpcContract.js +1 -1
- package/extension/src/shared/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +2 -1
- package/extension/src/shared/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -0
- package/extension/src/shared/scopedPersistenceBrowserAdapter.js +34 -0
- package/extension/src/shared/sessionState.js +63 -4
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/sharedSessionBridge.js +81 -13
- package/extension/src/shared/staleGuard.js +130 -63
- package/extension/src/shared/storageDb.js +35 -3
- package/extension/src/shared/storageMigration.js +111 -2
- package/extension/src/shared/storageRunActions.js +62 -8
- package/extension/src/shared/undoOperations.js +126 -28
- package/extension/src/shared/updateRuntimeIdentity.js +16 -2
- package/extension/src/shared/writebackSettlement.js +21 -3
- package/extension/styles/panel.css +1293 -549
- package/native-host/src/bootstrapUpdate.js +52 -16
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +10 -1
- package/native-host/src/chatCompletionsBridge.js +29 -2
- package/native-host/src/codexPromptAssembly.js +86 -118
- package/native-host/src/codexSessionRunner.js +127 -17
- 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/providerContextError.js +20 -0
- 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 +28 -8
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/updateManager.js +144 -21
- package/native-host/src/writingStyleRuntime.js +1 -1
- package/package.json +5 -8
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
closeDiagnosticsMenu = () => {},
|
|
33
33
|
closeCustomInstructionsSettings = () => {},
|
|
34
34
|
closeSlashMenu = () => {},
|
|
35
|
+
icons = null,
|
|
35
36
|
projectFiles: projectFilesDep,
|
|
36
37
|
exactFileListZipTimeoutMs: exactFileListZipTimeoutMsDep
|
|
37
38
|
} = {}) {
|
|
@@ -39,6 +40,11 @@
|
|
|
39
40
|
|| root.document
|
|
40
41
|
|| (typeof globalThis !== 'undefined' ? globalThis.document : null);
|
|
41
42
|
const getLocationHref = getLocationHrefDep || (() => root.location?.href || '');
|
|
43
|
+
const setCloseGlyph = button => {
|
|
44
|
+
const glyph = icons?.svg?.('close', 'codex-close-glyph');
|
|
45
|
+
if (glyph) button.innerHTML = glyph;
|
|
46
|
+
else button.textContent = '×';
|
|
47
|
+
};
|
|
42
48
|
const projectFiles = projectFilesDep || ProjectFiles;
|
|
43
49
|
const exactFileListZipTimeoutMs = Number(exactFileListZipTimeoutMsDep) ||
|
|
44
50
|
DEFAULT_CONTEXT_EXACT_FILE_LIST_ZIP_TIMEOUT_MS;
|
|
@@ -556,7 +562,7 @@
|
|
|
556
562
|
clear.dataset.contextClear = '';
|
|
557
563
|
clear.title = tr('clearFiles');
|
|
558
564
|
clear.setAttribute('aria-label', tr('clearFiles'));
|
|
559
|
-
clear
|
|
565
|
+
setCloseGlyph(clear);
|
|
560
566
|
clear.addEventListener('click', clearFocusFile);
|
|
561
567
|
chip.append(clear);
|
|
562
568
|
}
|
|
@@ -603,7 +609,7 @@
|
|
|
603
609
|
remove.setAttribute('data-context-summary-remove', '');
|
|
604
610
|
remove.title = tr('removeContextFile', { path });
|
|
605
611
|
remove.setAttribute('aria-label', tr('removeContextFile', { path }));
|
|
606
|
-
remove
|
|
612
|
+
setCloseGlyph(remove);
|
|
607
613
|
remove.addEventListener('click', event => {
|
|
608
614
|
event.preventDefault();
|
|
609
615
|
event.stopPropagation();
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
+
else root.CodexOverleafModuleRegistry.define('ContextUsageView', [], factory);
|
|
4
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window, function () {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
let nextId = 0;
|
|
8
|
+
|
|
9
|
+
// State and ownership are supplied by the runtime. The view never estimates
|
|
10
|
+
// context from conversation text, cumulative billing totals, or model names.
|
|
11
|
+
function create(deps = {}) {
|
|
12
|
+
let state = { usage: null, compacting: false, compactingKind: '', pending: false, compacted: false };
|
|
13
|
+
let host = null;
|
|
14
|
+
let button = null;
|
|
15
|
+
let popover = null;
|
|
16
|
+
let compactButton = null;
|
|
17
|
+
let panel = null;
|
|
18
|
+
let open = false;
|
|
19
|
+
let observer = null;
|
|
20
|
+
const listeners = [];
|
|
21
|
+
const tr = (key, params) => deps.tr(key, params);
|
|
22
|
+
const locale = () => deps.getLocale?.() === 'zh' ? 'zh-CN' : 'en-US';
|
|
23
|
+
|
|
24
|
+
function bind(target, type, listener, options) {
|
|
25
|
+
if (!target?.addEventListener) return;
|
|
26
|
+
target.addEventListener(type, listener, options);
|
|
27
|
+
listeners.push(() => target.removeEventListener(type, listener, options));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function unbind() {
|
|
31
|
+
listeners.splice(0).forEach(remove => remove());
|
|
32
|
+
observer?.disconnect();
|
|
33
|
+
observer = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function mount() {
|
|
37
|
+
const currentPanel = deps.getPanel?.();
|
|
38
|
+
const currentHost = currentPanel?.querySelector('[data-context-usage]');
|
|
39
|
+
if (currentHost === host) return Boolean(host);
|
|
40
|
+
unbind();
|
|
41
|
+
host = currentHost || null;
|
|
42
|
+
panel = currentPanel;
|
|
43
|
+
button = host?.querySelector('[data-context-usage-toggle]');
|
|
44
|
+
popover = host?.querySelector('[data-context-usage-popover]');
|
|
45
|
+
compactButton = host?.querySelector('[data-context-usage-compact]');
|
|
46
|
+
open = false;
|
|
47
|
+
if (!button || !popover) return false;
|
|
48
|
+
popover.id = 'codex-context-usage-' + (++nextId);
|
|
49
|
+
button.setAttribute('aria-controls', popover.id);
|
|
50
|
+
const doc = host.ownerDocument;
|
|
51
|
+
const view = doc.defaultView;
|
|
52
|
+
bind(button, 'click', event => {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
if (open) close();
|
|
55
|
+
else {
|
|
56
|
+
deps.onOpen?.();
|
|
57
|
+
open = true;
|
|
58
|
+
popover.hidden = false;
|
|
59
|
+
button.setAttribute('aria-expanded', 'true');
|
|
60
|
+
render();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
bind(compactButton, 'click', event => {
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
if (compactButton.disabled) return;
|
|
66
|
+
close();
|
|
67
|
+
deps.onCompact?.();
|
|
68
|
+
});
|
|
69
|
+
bind(doc, 'pointerdown', event => { if (!host.contains(event.target)) close(); }, true);
|
|
70
|
+
bind(doc, 'focusin', event => { if (!host.contains(event.target)) close(); });
|
|
71
|
+
bind(doc, 'keydown', event => {
|
|
72
|
+
if (open && event.key === 'Escape') {
|
|
73
|
+
const restoreFocus = popover.contains(doc.activeElement);
|
|
74
|
+
close();
|
|
75
|
+
if (restoreFocus) button.focus();
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
bind(view, 'resize', position);
|
|
80
|
+
bind(view?.visualViewport, 'resize', position);
|
|
81
|
+
bind(view?.visualViewport, 'scroll', position);
|
|
82
|
+
bind(doc, 'scroll', position, true);
|
|
83
|
+
if (typeof view?.ResizeObserver === 'function') {
|
|
84
|
+
observer = new view.ResizeObserver(position);
|
|
85
|
+
observer.observe(panel);
|
|
86
|
+
observer.observe(button);
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function close() {
|
|
92
|
+
open = false;
|
|
93
|
+
if (popover) popover.hidden = true;
|
|
94
|
+
button?.setAttribute('aria-expanded', 'false');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function position() {
|
|
98
|
+
if (!open || !popover || !button) return;
|
|
99
|
+
const view = host.ownerDocument.defaultView;
|
|
100
|
+
const viewport = view.visualViewport;
|
|
101
|
+
const viewportLeft = viewport?.offsetLeft || 0;
|
|
102
|
+
const viewportTop = viewport?.offsetTop || 0;
|
|
103
|
+
const viewportRight = viewportLeft + (viewport?.width || view.innerWidth);
|
|
104
|
+
const viewportBottom = viewportTop + (viewport?.height || view.innerHeight);
|
|
105
|
+
const panelRect = panel.getBoundingClientRect();
|
|
106
|
+
const rect = button.getBoundingClientRect();
|
|
107
|
+
const left = Math.max(viewportLeft, panelRect.left) + 8;
|
|
108
|
+
const right = Math.min(viewportRight, panelRect.right) - 8;
|
|
109
|
+
const top = Math.max(viewportTop, panelRect.top) + 8;
|
|
110
|
+
const bottom = Math.min(viewportBottom, panelRect.bottom) - 8;
|
|
111
|
+
const width = Math.max(0, Math.min(284, right - left));
|
|
112
|
+
const above = Math.max(0, rect.top - top - 8);
|
|
113
|
+
const below = Math.max(0, bottom - rect.bottom - 8);
|
|
114
|
+
const placeAbove = above >= Math.min(160, below);
|
|
115
|
+
popover.style.width = width + 'px';
|
|
116
|
+
popover.style.maxHeight = Math.max(0, placeAbove ? above : below) + 'px';
|
|
117
|
+
popover.style.left = Math.max(left, Math.min(rect.right - width, right - width)) + 'px';
|
|
118
|
+
const height = popover.getBoundingClientRect().height;
|
|
119
|
+
popover.style.top = (placeAbove ? Math.max(top, rect.top - height - 8) : rect.bottom + 8) + 'px';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function number(value) {
|
|
123
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) return null;
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function formatTokens(value) {
|
|
128
|
+
if (value === null) return '—';
|
|
129
|
+
const unit = value >= 1e9 ? 1e9 : value >= 1e6 ? 1e6 : value >= 1e3 ? 1e3 : 1;
|
|
130
|
+
const suffix = unit === 1e9 ? 'B' : unit === 1e6 ? 'M' : unit === 1e3 ? 'K' : '';
|
|
131
|
+
return new Intl.NumberFormat(locale(), { maximumFractionDigits: unit === 1 ? 0 : 1 }).format(value / unit) + suffix;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function formatPercent(value, digits = 1) {
|
|
135
|
+
return value === null ? '—' : new Intl.NumberFormat(locale(), { style: 'percent', maximumFractionDigits: digits }).format(value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function formatLatestReply(value) {
|
|
139
|
+
const at = Date.parse(value || '');
|
|
140
|
+
if (!Number.isFinite(at)) return '';
|
|
141
|
+
const seconds = Math.max(0, Math.round((Date.now() - at) / 1000));
|
|
142
|
+
const minutes = Math.round(seconds / 60), hours = Math.round(minutes / 60), days = Math.round(hours / 24);
|
|
143
|
+
const time = seconds < 45 ? tr('contextUsageJustNow')
|
|
144
|
+
: minutes < 60 ? tr('contextUsageMinutesAgo', { count: Math.max(1, minutes) })
|
|
145
|
+
: hours < 24 ? tr('contextUsageHoursAgo', { count: hours })
|
|
146
|
+
: tr(days === 1 ? 'contextUsageDayAgo' : 'contextUsageDaysAgo', { count: days });
|
|
147
|
+
return tr('contextUsageLatestReply', { time });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function text(name, value) {
|
|
151
|
+
const node = host.querySelector('[data-context-usage-' + name + ']');
|
|
152
|
+
if (node) node.textContent = value;
|
|
153
|
+
return node;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// The bar spans the reported window; its filled part splits by the latest
|
|
157
|
+
// response's counters. Cached input is drawn inside input, never added to it.
|
|
158
|
+
function renderBreakdown(used, capacity) {
|
|
159
|
+
const input = number(state.usage?.inputTokens), output = number(state.usage?.outputTokens);
|
|
160
|
+
const cachedValue = number(state.usage?.cachedInputTokens);
|
|
161
|
+
const cached = input !== null && cachedValue !== null && cachedValue <= input ? cachedValue : null;
|
|
162
|
+
const total = input !== null && output !== null && Number.isSafeInteger(input + output)
|
|
163
|
+
&& input + output > 0 ? input + output : null;
|
|
164
|
+
const filled = used !== null && capacity !== null ? Math.min(1, used / capacity) : 0;
|
|
165
|
+
const widths = total === null
|
|
166
|
+
? { cached: 0, input: filled, output: 0 }
|
|
167
|
+
: { cached: filled * (cached || 0) / total, input: filled * (input - (cached || 0)) / total, output: filled * output / total };
|
|
168
|
+
for (const [name, width] of Object.entries(widths)) {
|
|
169
|
+
const node = host.querySelector('[data-context-usage-bar] [data-seg="' + name + '"]');
|
|
170
|
+
if (node) node.style.width = (width * 100).toFixed(3) + '%';
|
|
171
|
+
}
|
|
172
|
+
const legend = host.querySelector('[data-context-usage-breakdown]');
|
|
173
|
+
legend.hidden = input === null && output === null;
|
|
174
|
+
text('input-label', tr('contextUsageInput'));
|
|
175
|
+
text('output-label', tr('contextUsageOutput'));
|
|
176
|
+
text('cached-label', tr('contextUsageCacheHit'));
|
|
177
|
+
text('input', formatTokens(input));
|
|
178
|
+
text('output', formatTokens(output));
|
|
179
|
+
text('cached', formatTokens(cached));
|
|
180
|
+
text('input-share', total === null ? '' : formatPercent(input / total, 0));
|
|
181
|
+
text('output-share', total === null ? '' : formatPercent(output / total, 0));
|
|
182
|
+
const rate = text('cache-rate', input > 0 && cached !== null ? formatPercent(cached / input, 0) : '—');
|
|
183
|
+
if (rate) rate.title = tr('contextUsageCacheHitRate');
|
|
184
|
+
legend.title = total === null ? '' : tr('contextUsageBreakdownBasis');
|
|
185
|
+
host.querySelector('[data-context-usage-cache]').hidden = input === null || cached === null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function render() {
|
|
189
|
+
if (!mount()) return;
|
|
190
|
+
const used = number(state.usage?.usedTokens);
|
|
191
|
+
const capacityValue = number(state.usage?.contextWindow);
|
|
192
|
+
const capacity = capacityValue > 0 ? capacityValue : null;
|
|
193
|
+
const percent = used !== null && capacity !== null ? used / capacity * 100 : null;
|
|
194
|
+
const level = percent === null ? '' : deps.level?.(percent) || 'normal';
|
|
195
|
+
const percentage = percent === null ? '' : new Intl.NumberFormat(locale(), { maximumFractionDigits: 1 }).format(percent);
|
|
196
|
+
const amount = tr('contextUsageAmount', { used: formatTokens(used), capacity: formatTokens(capacity) });
|
|
197
|
+
const summary = tr('contextUsageSummary', { amount, percentage: percent === null ? '—' : percentage + '%' });
|
|
198
|
+
const compactingText = state.compactingKind === 'auto' ? tr('contextUsageCompactingAuto') : tr('contextUsageCompacting');
|
|
199
|
+
const compacting = state.compacting;
|
|
200
|
+
const pending = !compacting && state.pending;
|
|
201
|
+
const availability = deps.getCompactAvailability?.() || { ok: true };
|
|
202
|
+
// No sample and nothing in progress: show the empty state. Before the first
|
|
203
|
+
// message there is no thread, so the line says when usage will appear.
|
|
204
|
+
const empty = used === null && !compacting && !pending;
|
|
205
|
+
const noThread = availability.ok === false && availability.reason === 'compactNoThread';
|
|
206
|
+
const emptyText = noThread ? tr('contextUsageBeforeFirstMessage') : tr('contextUsageUnavailable');
|
|
207
|
+
host.dataset.compacting = compacting ? 'true' : 'false';
|
|
208
|
+
host.dataset.empty = empty ? 'true' : 'false';
|
|
209
|
+
host.dataset.unknown = percent === null ? 'true' : 'false';
|
|
210
|
+
host.dataset.pending = pending ? 'true' : 'false';
|
|
211
|
+
host.dataset.level = level;
|
|
212
|
+
host.style.setProperty('--context-usage-percent', String(Math.max(0, Math.min(100, percent || 0))));
|
|
213
|
+
button.title = compacting ? compactingText : pending ? tr(state.compacted ? 'contextUsagePending' : 'contextUsagePendingDetail')
|
|
214
|
+
: used === null ? emptyText : summary;
|
|
215
|
+
button.setAttribute('aria-label', button.title);
|
|
216
|
+
text('title', tr('contextUsageTitle'));
|
|
217
|
+
// Without a usable sample, the line under the title carries the state.
|
|
218
|
+
text('amount', used !== null ? amount : compacting ? compactingText
|
|
219
|
+
: pending ? tr('contextUsagePendingDetail') : emptyText);
|
|
220
|
+
host.querySelector('[data-context-usage-spinner]').hidden = !compacting;
|
|
221
|
+
const chip = text('chip', tr('contextUsageCompactedChip'));
|
|
222
|
+
if (chip) chip.hidden = !pending || !state.compacted;
|
|
223
|
+
host.querySelector('[data-context-usage-percent-wrap]').hidden = compacting || pending;
|
|
224
|
+
text('percent', percent === null ? '—' : percentage);
|
|
225
|
+
host.querySelector('[data-context-usage-percent-sign]').hidden = percent === null;
|
|
226
|
+
const figure = host.querySelector('[data-context-usage-figure]');
|
|
227
|
+
if (figure) figure.title = percent === null ? '' : tr('contextUsageEstimate');
|
|
228
|
+
const levelNode = text('level', level === 'near-limit' ? tr('contextUsageNearLimit') : level === 'high' ? tr('contextUsageHigh') : '');
|
|
229
|
+
if (levelNode) levelNode.hidden = compacting || (level !== 'high' && level !== 'near-limit');
|
|
230
|
+
// A sample shown while compacting gets an explicit note; so does a missing capacity.
|
|
231
|
+
const noteText = used === null ? '' : compacting ? compactingText : capacity === null ? tr('contextUsageCapacityUnknown') : '';
|
|
232
|
+
text('note-text', noteText);
|
|
233
|
+
const note = host.querySelector('[data-context-usage-note]');
|
|
234
|
+
if (note) {
|
|
235
|
+
note.hidden = !noteText;
|
|
236
|
+
note.dataset.kind = compacting ? 'compacting' : 'info';
|
|
237
|
+
}
|
|
238
|
+
renderBreakdown(used, capacity);
|
|
239
|
+
const reported = text('reported', used === null ? '' : formatLatestReply(state.usage?.observedAt));
|
|
240
|
+
if (reported) reported.hidden = !reported.textContent;
|
|
241
|
+
if (compactButton) {
|
|
242
|
+
// Without a conversation there is nothing to compact; a busy state keeps the disabled button.
|
|
243
|
+
compactButton.hidden = noThread;
|
|
244
|
+
compactButton.disabled = availability.ok === false;
|
|
245
|
+
compactButton.title = availability.ok === false ? tr(availability.reason || 'compactBusy') : tr('contextUsageCompactHint');
|
|
246
|
+
text('compact-label', tr('contextUsageCompactAction'));
|
|
247
|
+
}
|
|
248
|
+
const foot = host.querySelector('[data-context-usage-foot]');
|
|
249
|
+
if (foot) foot.hidden = (!compactButton || compactButton.hidden) && (!reported || reported.hidden);
|
|
250
|
+
popover.setAttribute('aria-label', tr('contextUsageTitle'));
|
|
251
|
+
position();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
setState(value = {}) {
|
|
256
|
+
state = { usage: value.usage || null, compacting: Boolean(value.compacting),
|
|
257
|
+
compactingKind: value.compactingKind || '', pending: Boolean(value.pending), compacted: Boolean(value.compacted) };
|
|
258
|
+
render();
|
|
259
|
+
},
|
|
260
|
+
render,
|
|
261
|
+
close,
|
|
262
|
+
destroy() { close(); unbind(); host = button = popover = compactButton = panel = null; }
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { create };
|
|
267
|
+
});
|
|
@@ -103,7 +103,12 @@
|
|
|
103
103
|
try {
|
|
104
104
|
result = await spec.run({ collectOnly: true });
|
|
105
105
|
} catch (error) {
|
|
106
|
-
result = projectableResult(() => ({
|
|
106
|
+
result = projectableResult(() => ({
|
|
107
|
+
status: 'failed',
|
|
108
|
+
summary: tr('diagnosticsCheckErrored'),
|
|
109
|
+
nextStep: tr('runOutcomeNext'),
|
|
110
|
+
technical: error?.message || String(error)
|
|
111
|
+
}));
|
|
107
112
|
}
|
|
108
113
|
const bucket = diagnosticsHealthBucket(result?.status);
|
|
109
114
|
if (bucket === 'fail') {
|
|
@@ -118,7 +123,9 @@
|
|
|
118
123
|
status: result?.status || 'info',
|
|
119
124
|
title: tr(spec.label),
|
|
120
125
|
summary: translated?.summary || translated?.subtitle || '',
|
|
121
|
-
nextStep: bucket === 'ok' ? '' : (translated?.nextStep || '')
|
|
126
|
+
nextStep: bucket === 'ok' ? '' : (translated?.nextStep || ''),
|
|
127
|
+
installCommand: translated?.installCommand,
|
|
128
|
+
technical: translated?.technical
|
|
122
129
|
};
|
|
123
130
|
});
|
|
124
131
|
}
|
|
@@ -211,13 +218,15 @@
|
|
|
211
218
|
const codexOk = environment.codex?.ok === true;
|
|
212
219
|
const latexTools = environment.latex?.available || [];
|
|
213
220
|
const latexOk = latexTools.length > 0;
|
|
214
|
-
const
|
|
221
|
+
const facts = [
|
|
215
222
|
codexOk
|
|
216
|
-
?
|
|
217
|
-
|
|
223
|
+
? (environment.codex.path
|
|
224
|
+
? { label: 'Codex', value: environment.codex.path, kind: 'path' }
|
|
225
|
+
: { label: 'Codex', value: tx('Found on PATH', '已在 PATH 中找到') })
|
|
226
|
+
: { label: 'Codex', value: tx('Not found', '未找到'), tone: 'fail' },
|
|
218
227
|
latexOk
|
|
219
|
-
?
|
|
220
|
-
:
|
|
228
|
+
? { label: 'LaTeX', value: latexTools.join(', ') }
|
|
229
|
+
: { label: 'LaTeX', value: tx('Not found', '未找到'), tone: 'warn' }
|
|
221
230
|
];
|
|
222
231
|
if (codexOk && latexOk) {
|
|
223
232
|
return {
|
|
@@ -225,7 +234,7 @@
|
|
|
225
234
|
subtitle: tx('Codex and LaTeX are available.', 'Codex 和 LaTeX 都可以使用。'),
|
|
226
235
|
status: 'completed',
|
|
227
236
|
summary: tx('Codex is connected and local LaTeX tools are available. Codex can read, edit, and locally check this Overleaf project.', 'Codex 已连接,本机 LaTeX 工具可用。你可以让 Codex 读取、修改并本地检查 Overleaf 项目。'),
|
|
228
|
-
|
|
237
|
+
facts,
|
|
229
238
|
technical: JSON.stringify(environment, null, 2)
|
|
230
239
|
};
|
|
231
240
|
}
|
|
@@ -236,7 +245,7 @@
|
|
|
236
245
|
summary: codexOk
|
|
237
246
|
? tx('Local Codex can run, but LaTeX compile tools were not found. File edits are unaffected, but compile verification is limited.', '本机 Codex 可以运行,但没有找到 LaTeX 编译工具;修改文件不受影响,编译验证会受限。')
|
|
238
247
|
: tx('The extension reached the native host, but local Codex was not found, so tasks cannot start.', '插件已经连上 native host,但没有找到本机 Codex,所以不能启动任务。'),
|
|
239
|
-
|
|
248
|
+
facts,
|
|
240
249
|
nextStep: codexOk
|
|
241
250
|
? tx('For compile verification, make sure latexmk or pdflatex is available on your Terminal PATH.', '如需编译验证,请确认 latexmk 或 pdflatex 在终端 PATH 中可用。')
|
|
242
251
|
: tx('Make sure codex works in Terminal, then reload the extension.', '请确认终端里可以运行 codex,然后重新加载扩展。'),
|
|
@@ -281,7 +290,7 @@
|
|
|
281
290
|
subtitle: messages.subtitle,
|
|
282
291
|
status: 'failed',
|
|
283
292
|
summary: messages.summary,
|
|
284
|
-
|
|
293
|
+
facts: formatNativeCompatibilityFacts(compatibility, statusValue),
|
|
285
294
|
nextStep: messages.nextStep,
|
|
286
295
|
installCommand: compatibility.installCommand,
|
|
287
296
|
technical: formatNativeCompatibilityTechnicalDetails(compatibility, response)
|
|
@@ -320,14 +329,24 @@
|
|
|
320
329
|
}
|
|
321
330
|
}
|
|
322
331
|
|
|
323
|
-
function
|
|
332
|
+
function formatNativeCompatibilityFacts(compatibility = {}, statusValue = '') {
|
|
324
333
|
const native = compatibility.native || {};
|
|
325
334
|
return [
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
335
|
+
{
|
|
336
|
+
label: tx('Extension version', '扩展版本'),
|
|
337
|
+
value: compatibility.extensionVersion || 'unknown',
|
|
338
|
+
tone: statusValue === 'extension_too_old' ? 'fail' : undefined
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
label: tx('Native version', 'Native 版本'),
|
|
342
|
+
value: native.version || 'unknown',
|
|
343
|
+
tone: statusValue === 'native_too_old' ? 'fail' : undefined
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
label: tx('Native protocol', 'Native 协议'),
|
|
347
|
+
value: formatNativeProtocolVersion(native),
|
|
348
|
+
tone: statusValue === 'protocol_unsupported' ? 'fail' : undefined
|
|
349
|
+
}
|
|
331
350
|
];
|
|
332
351
|
}
|
|
333
352
|
|
|
@@ -397,8 +416,13 @@
|
|
|
397
416
|
const failure = mirrorState.failureActive && mirrorState.lastFailure ? { ...mirrorState.lastFailure } : null;
|
|
398
417
|
const disabledResult = projectableResult(() => {
|
|
399
418
|
const result = {
|
|
400
|
-
title: tr('diagnosticsOtTitle'),
|
|
401
|
-
|
|
419
|
+
title: tr('diagnosticsOtTitle'),
|
|
420
|
+
subtitle: tr('diagnosticsOtSubtitle'),
|
|
421
|
+
status: 'completed',
|
|
422
|
+
summary: tr('diagnosticsOtSummaryDisabled'),
|
|
423
|
+
facts: [],
|
|
424
|
+
nextStep: '',
|
|
425
|
+
technical: ''
|
|
402
426
|
};
|
|
403
427
|
if (failure) {
|
|
404
428
|
result.status = 'warning';
|
|
@@ -491,10 +515,14 @@
|
|
|
491
515
|
status: enabled && !fallback && !bridgeFailed ? 'completed' : (enabled || bridgeFailed ? 'warning' : 'completed'),
|
|
492
516
|
summary: tr(failureActive ? otWarmMirrorController.getFailureMessageKey(warmMirrorState.lastErrorCode)
|
|
493
517
|
: enabled ? 'diagnosticsOtSummaryEnabled' : 'diagnosticsOtSummaryDisabled'),
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
518
|
+
facts: [
|
|
519
|
+
{ label: tr('otStatus'), value: formatOtStatusLabel(statusValue) },
|
|
520
|
+
{ label: tr('otFreshFiles'), value: String(otFreshFileCount) },
|
|
521
|
+
{
|
|
522
|
+
label: tr('otFallback'),
|
|
523
|
+
value: fallback ? tr('yes') : tr('no'),
|
|
524
|
+
tone: enabled && fallback ? 'warn' : undefined
|
|
525
|
+
}
|
|
498
526
|
],
|
|
499
527
|
nextStep: fallback ? tr('diagnosticsOtNextStep') : '',
|
|
500
528
|
technical: formatOtDiagnosticsTechnicalDetails({
|
|
@@ -563,10 +591,23 @@
|
|
|
563
591
|
const reviewingOk = probe?.reviewing?.ok === true;
|
|
564
592
|
const writable = probe?.capabilities?.editor?.write !== false;
|
|
565
593
|
const readable = probe?.editor?.ok === true;
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
594
|
+
const reviewingNeeded = mode !== 'ask';
|
|
595
|
+
const facts = [
|
|
596
|
+
{
|
|
597
|
+
label: tx('Current file', '当前文件'),
|
|
598
|
+
value: readable ? tx('Read', '已读取') : tx('Not verified', '未确认'),
|
|
599
|
+
tone: readable ? undefined : 'warn'
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
label: tx('Write access', '写入'),
|
|
603
|
+
value: writable ? tx('Checked again before writing', '写入前会再次验证') : tx('Not writable yet', '暂不可写'),
|
|
604
|
+
tone: writable ? undefined : 'warn'
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
label: tx('Track changes', '留痕'),
|
|
608
|
+
value: reviewingOk ? tx('Verified', '已确认') : tx('Not verified', '未确认'),
|
|
609
|
+
tone: reviewingOk || !reviewingNeeded ? undefined : 'warn'
|
|
610
|
+
}
|
|
570
611
|
];
|
|
571
612
|
|
|
572
613
|
if (readable && writable) {
|
|
@@ -575,7 +616,7 @@
|
|
|
575
616
|
subtitle: formatProbeStatusBar(probe),
|
|
576
617
|
status: reviewingOk || mode === 'ask' ? 'completed' : 'warning',
|
|
577
618
|
summary: tx('The current Overleaf file can be read and written. The extension still verifies before writing to avoid overwriting new user or collaborator edits.', '当前 Overleaf 文件可以读取和写入。写入前插件仍会再次验证,避免覆盖用户或协作者的新改动。'),
|
|
578
|
-
|
|
619
|
+
facts,
|
|
579
620
|
nextStep: reviewingOk || mode === 'ask' ? '' : tx('If you need tracked writes, turn on Overleaf Reviewing/Track Changes first.', '如果需要留痕写入,请先在 Overleaf 开启 Reviewing/Track Changes。'),
|
|
580
621
|
technical: formatPageDiagnosticsTechnicalDetails(probe)
|
|
581
622
|
};
|
|
@@ -588,7 +629,7 @@
|
|
|
588
629
|
summary: readable
|
|
589
630
|
? tx('The extension read the current Overleaf file, but has not verified that the current editor can be written.', '插件已读到当前 Overleaf 文件,但还没有确认当前编辑器可以写入。')
|
|
590
631
|
: tx('The extension has not verified that the current editor can be written. Usually this means no .tex file is open yet, or Overleaf is still loading.', '插件没有确认当前编辑器可以写入。通常是还没点开 .tex 文件,或者 Overleaf 页面仍在加载。'),
|
|
591
|
-
|
|
632
|
+
facts,
|
|
592
633
|
nextStep: tx('Open the target .tex file from the left Overleaf file tree, wait for the editor to load, then check again.', '在 Overleaf 左侧文件树点开要处理的 .tex 文件,等编辑器加载完成后重新检测。'),
|
|
593
634
|
technical: formatPageDiagnosticsTechnicalDetails(probe)
|
|
594
635
|
};
|
|
@@ -602,10 +643,10 @@
|
|
|
602
643
|
const warnings = getProjectSnapshotWarnings(project);
|
|
603
644
|
const warningText = [...warnings.blocking, ...warnings.nonBlocking].map(formatProjectSnapshotWarning);
|
|
604
645
|
const source = project?.capabilities?.method || 'unknown';
|
|
605
|
-
const
|
|
606
|
-
tx(
|
|
607
|
-
tx(
|
|
608
|
-
|
|
646
|
+
const facts = [
|
|
647
|
+
{ label: tx('Text files', '文本文件'), value: tx(String(textCount), `${textCount} 个`) },
|
|
648
|
+
{ label: tx('Asset files', '资源文件'), value: tx(String(binaryCount), `${binaryCount} 个`) },
|
|
649
|
+
{ label: tx('Current file', '当前文件'), value: project?.activePath || tx('Not detected', '未识别') }
|
|
609
650
|
];
|
|
610
651
|
if (fullProject && files.length) {
|
|
611
652
|
return {
|
|
@@ -616,7 +657,8 @@
|
|
|
616
657
|
`The extension read the full Overleaf project: ${textCount} text file(s)${binaryCount ? `, ${binaryCount} asset file(s)` : ''}.`,
|
|
617
658
|
`插件已读到完整 Overleaf 项目:${textCount} 个文本文件${binaryCount ? `,${binaryCount} 个资源文件` : ''}。`
|
|
618
659
|
),
|
|
619
|
-
|
|
660
|
+
facts,
|
|
661
|
+
notes: warningText,
|
|
620
662
|
technical: formatProjectSnapshotLog(project)
|
|
621
663
|
};
|
|
622
664
|
}
|
|
@@ -630,7 +672,8 @@
|
|
|
630
672
|
`没有读到完整的 Overleaf 项目。当前只读到 ${textCount} 个文本文件${binaryCount ? `,${binaryCount} 个资源文件` : ''}。`
|
|
631
673
|
)
|
|
632
674
|
: tx('The full Overleaf project was not read, and no usable file content was available.', '没有读到完整的 Overleaf 项目,也没有拿到可用文件内容。'),
|
|
633
|
-
|
|
675
|
+
facts,
|
|
676
|
+
notes: warningText,
|
|
634
677
|
nextStep: tx('Reload Overleaf and wait for the left file tree to load, then retry. If you only want one file, use + to add @file context.', '刷新 Overleaf 页面,等左侧文件树加载完成后重试;如果只想处理一个文件,可以用 + 添加 @file 上下文。'),
|
|
635
678
|
technical: formatProjectSnapshotLog(project)
|
|
636
679
|
};
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
},
|
|
165
165
|
platform: nativeDiagnostics.platform,
|
|
166
166
|
nativeEnvironment: nativeDiagnostics.nativeEnvironment,
|
|
167
|
-
mirror,
|
|
167
|
+
mirror: mirror ?? { status: 'unavailable' },
|
|
168
168
|
auditLogs,
|
|
169
169
|
run: activeRecord?.runProjectId === projectId && matchesAccount(activeRecord, currentRunView?.runAccountScopeId)
|
|
170
170
|
? activeRecord : recentRuns[0] || {},
|