codex-overleaf-link 2.5.1 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -398
- package/README.zh-CN.md +153 -404
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +78 -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 +11 -2
- package/extension/src/content/composerPanel.js +92 -12
- package/extension/src/content/contentRuntime.js +920 -276
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsPanel.js +18 -0
- package/extension/src/content/generated/content.bundle.js +4496 -924
- 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 +2 -2
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +42 -5
- package/extension/src/content/modelPicker.js +1 -0
- package/extension/src/content/nativeCompatibilityController.js +29 -10
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/pageBridgeClient.js +2 -2
- package/extension/src/content/panelMaintenance.js +6 -2
- package/extension/src/content/panelRenderer.js +51 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectSettingsCoordinator.js +30 -6
- package/extension/src/content/providerSettingsDialog.js +2 -1
- package/extension/src/content/recentProjects.js +4 -3
- 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 +9 -5
- package/extension/src/content/runRecoveryActions.js +2 -1
- package/extension/src/content/runResultActions.js +22 -20
- package/extension/src/content/runReviewController.js +94 -35
- package/extension/src/content/runTimelineView.js +224 -25
- package/extension/src/content/scopedPersistencePanelState.js +92 -23
- 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 +4 -3
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateNotice.js +60 -6
- package/extension/src/content/writebackOrchestrator.js +35 -22
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +9 -1
- package/extension/src/page/writebackRouter.js +24 -77
- package/extension/src/pageBridge.js +107 -66
- package/extension/src/popup.js +1 -1
- package/extension/src/shared/agentTranscript.js +350 -17
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/compileAdapter.js +58 -17
- package/extension/src/shared/composerAttachmentDraft.js +6 -1
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/failureReasons.js +42 -3
- package/extension/src/shared/i18n.js +299 -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/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -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 +72 -63
- package/extension/src/shared/storageDb.js +35 -3
- package/extension/src/shared/storageMigration.js +7 -1
- 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 +1106 -490
- 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 +126 -17
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/taskRunnerRuntime.js +18 -6
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/updateManager.js +120 -15
- 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
|
+
});
|
|
@@ -19,9 +19,14 @@
|
|
|
19
19
|
<div class="codex-diagnostics-wrap">
|
|
20
20
|
<button type="button" class="codex-diagnostics-trigger" data-diagnostics-menu title="Diagnostics" aria-label="Diagnostics" aria-expanded="false">
|
|
21
21
|
<span class="codex-diagnostics-dot" data-diagnostics-health-dot data-health="unknown"></span>
|
|
22
|
+
<span class="codex-diagnostics-label" data-diagnostics-health-label></span>
|
|
22
23
|
</button>
|
|
23
24
|
<div class="codex-diagnostics-menu" data-diagnostics-popover hidden>
|
|
24
25
|
<div class="codex-diagnostics-hint" data-i18n="diagnosticsHint">Use when Codex cannot run, write, or read files</div>
|
|
26
|
+
<button type="button" data-refresh>
|
|
27
|
+
<span class="codex-diagnostics-refresh-title">${options.icons?.svg?.('refresh') || ''}<span data-i18n="refreshProbeTitle">Refresh file status</span></span>
|
|
28
|
+
<small data-i18n="refreshProbeSubtitle">Re-reads Overleaf state; does not sync or modify files</small>
|
|
29
|
+
</button>
|
|
25
30
|
<button type="button" class="codex-diagnostics-runall" data-diagnostics-run-all>
|
|
26
31
|
<span data-i18n="diagnosticsRunAllTitle">Run all checks</span>
|
|
27
32
|
<small data-i18n="diagnosticsRunAllSubtitle">Connection, write access, project read, OT mirror</small>
|
|
@@ -81,6 +86,10 @@
|
|
|
81
86
|
|
|
82
87
|
function bindStaticActions(instance) {
|
|
83
88
|
const root = instance.container;
|
|
89
|
+
root.querySelector('[data-refresh]')?.addEventListener('click', () => {
|
|
90
|
+
closeMenu(instance);
|
|
91
|
+
instance.callbacks.onRefresh?.();
|
|
92
|
+
});
|
|
84
93
|
root.querySelector('[data-diagnostics-menu]')?.addEventListener('click', () => toggleMenu(instance));
|
|
85
94
|
root.querySelector('[data-diagnostics-run-all]')?.addEventListener('click', () => instance.callbacks.onRunAll?.());
|
|
86
95
|
root.querySelector('[data-diagnostics-native-env]')?.addEventListener('click', () => instance.callbacks.onNativeEnvironment?.());
|
|
@@ -488,6 +497,15 @@
|
|
|
488
497
|
const tooltip = t(instance, tooltipKey);
|
|
489
498
|
trigger.title = tooltip;
|
|
490
499
|
trigger.setAttribute('aria-label', tooltip);
|
|
500
|
+
trigger.dataset.health = health;
|
|
501
|
+
}
|
|
502
|
+
const label = instance?.container?.querySelector('[data-diagnostics-health-label]');
|
|
503
|
+
if (label) {
|
|
504
|
+
label.textContent = t(instance, {
|
|
505
|
+
ok: 'diagnosticsPillOk',
|
|
506
|
+
warn: 'diagnosticsPillWarn',
|
|
507
|
+
fail: 'diagnosticsPillFail'
|
|
508
|
+
}[health] || 'diagnosticsPillUnknown');
|
|
491
509
|
}
|
|
492
510
|
}
|
|
493
511
|
|