pi-studio 0.9.14 → 0.9.16
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/CHANGELOG.md +20 -1
- package/README.md +3 -2
- package/client/studio-client.js +560 -20
- package/client/studio.css +215 -5
- package/index.ts +324 -18
- package/package.json +1 -1
package/client/studio-client.js
CHANGED
|
@@ -114,6 +114,11 @@
|
|
|
114
114
|
const sendReplBtn = document.getElementById("sendReplBtn");
|
|
115
115
|
const replSendModeSelect = document.getElementById("replSendModeSelect");
|
|
116
116
|
const copyDraftBtn = document.getElementById("copyDraftBtn");
|
|
117
|
+
const suggestCompletionBtn = document.getElementById("suggestCompletionBtn");
|
|
118
|
+
const completionSuggestionPanelEl = document.getElementById("completionSuggestionPanel");
|
|
119
|
+
const completionSuggestionTextEl = document.getElementById("completionSuggestionText");
|
|
120
|
+
const completionSuggestionInsertBtn = document.getElementById("completionSuggestionInsertBtn");
|
|
121
|
+
const completionSuggestionDismissBtn = document.getElementById("completionSuggestionDismissBtn");
|
|
117
122
|
const saveAnnotatedBtn = document.getElementById("saveAnnotatedBtn");
|
|
118
123
|
const stripAnnotationsBtn = document.getElementById("stripAnnotationsBtn");
|
|
119
124
|
const highlightSelect = document.getElementById("highlightSelect");
|
|
@@ -1943,6 +1948,11 @@
|
|
|
1943
1948
|
let editorHighlightEnabled = false;
|
|
1944
1949
|
let editorLanguage = "markdown";
|
|
1945
1950
|
let responseHighlightEnabled = false;
|
|
1951
|
+
let completionSuggestionState = null;
|
|
1952
|
+
let completionSuggestionInFlight = false;
|
|
1953
|
+
let completionSuggestionRequestId = null;
|
|
1954
|
+
let completionSuggestionPendingSnapshot = null;
|
|
1955
|
+
let completionSuggestionRefocusEditorOnResult = false;
|
|
1946
1956
|
let editorHighlightRenderRaf = null;
|
|
1947
1957
|
let lineNumbersEnabled = false;
|
|
1948
1958
|
let lineNumbersRenderRaf = null;
|
|
@@ -1955,6 +1965,19 @@
|
|
|
1955
1965
|
const DEFAULT_RESPONSE_FONT_SIZE = studioUiRefreshEnabled ? 13.5 : 15;
|
|
1956
1966
|
let editorFontSize = DEFAULT_EDITOR_FONT_SIZE;
|
|
1957
1967
|
let responseFontSize = DEFAULT_RESPONSE_FONT_SIZE;
|
|
1968
|
+
let fileBrowserState = {
|
|
1969
|
+
rootDir: "",
|
|
1970
|
+
currentDir: "",
|
|
1971
|
+
relativeDir: "",
|
|
1972
|
+
parentDir: null,
|
|
1973
|
+
entries: [],
|
|
1974
|
+
omitted: 0,
|
|
1975
|
+
omittedIgnored: 0,
|
|
1976
|
+
loading: false,
|
|
1977
|
+
error: "",
|
|
1978
|
+
loaded: false,
|
|
1979
|
+
};
|
|
1980
|
+
let fileBrowserLoadNonce = 0;
|
|
1958
1981
|
let studioUiRefreshUi = null;
|
|
1959
1982
|
let studioZenModeEnabled = readStudioZenModeEnabled();
|
|
1960
1983
|
if (studioUiRefreshEnabled && document.body) {
|
|
@@ -2385,6 +2408,7 @@
|
|
|
2385
2408
|
if (!isEditorOnlyMode && replSendModeSelect) replActionLineEl.appendChild(replSendModeSelect);
|
|
2386
2409
|
const actionLineTwoEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line");
|
|
2387
2410
|
actionLineTwoEl.appendChild(copyDraftBtn);
|
|
2411
|
+
if (suggestCompletionBtn) actionLineTwoEl.appendChild(suggestCompletionBtn);
|
|
2388
2412
|
if (openCompanionBtn) actionLineTwoEl.appendChild(openCompanionBtn);
|
|
2389
2413
|
if (!isEditorOnlyMode && sendEditorBtn) actionLineTwoEl.appendChild(sendEditorBtn);
|
|
2390
2414
|
if (actionLineOneEl.childNodes.length > 0) actionsEl.appendChild(actionLineOneEl);
|
|
@@ -3667,6 +3691,8 @@
|
|
|
3667
3691
|
return;
|
|
3668
3692
|
}
|
|
3669
3693
|
|
|
3694
|
+
if (handleCompletionSuggestionAcceptKey(event)) return;
|
|
3695
|
+
|
|
3670
3696
|
if ((key === "?" || (key === "/" && event.shiftKey)) && !event.metaKey && !event.ctrlKey && !event.altKey && !isTextEntryShortcutTarget(event.target)) {
|
|
3671
3697
|
event.preventDefault();
|
|
3672
3698
|
toggleShortcuts();
|
|
@@ -4085,6 +4111,12 @@
|
|
|
4085
4111
|
}
|
|
4086
4112
|
if (referenceMetaEl instanceof HTMLElement) referenceMetaEl.hidden = false;
|
|
4087
4113
|
|
|
4114
|
+
if (rightView === "files") {
|
|
4115
|
+
const dir = fileBrowserState && fileBrowserState.currentDir ? fileBrowserState.currentDir : (getCurrentResourceDirValue() || "current Studio directory");
|
|
4116
|
+
referenceBadgeEl.textContent = "Files: " + dir;
|
|
4117
|
+
return;
|
|
4118
|
+
}
|
|
4119
|
+
|
|
4088
4120
|
if (rightView === "trace") {
|
|
4089
4121
|
const state = traceState || createEmptyTraceState();
|
|
4090
4122
|
const context = traceDisplayContext || {};
|
|
@@ -6645,6 +6677,7 @@
|
|
|
6645
6677
|
critiqueViewEl.addEventListener("scroll", handleTracePaneScroll);
|
|
6646
6678
|
critiqueViewEl.addEventListener("click", handleTracePaneClick);
|
|
6647
6679
|
critiqueViewEl.addEventListener("click", handleReplPaneClick);
|
|
6680
|
+
critiqueViewEl.addEventListener("click", handleFilesPaneClick);
|
|
6648
6681
|
critiqueViewEl.addEventListener("change", handleReplPaneChange);
|
|
6649
6682
|
}
|
|
6650
6683
|
|
|
@@ -8107,6 +8140,241 @@
|
|
|
8107
8140
|
scheduleResponsePaneRepaintNudge();
|
|
8108
8141
|
}
|
|
8109
8142
|
|
|
8143
|
+
function getFileBrowserContextKey() {
|
|
8144
|
+
const context = getHtmlPreviewResourceContextOptions();
|
|
8145
|
+
return String(context.sourcePath || "") + "\n" + String(context.resourceDir || "");
|
|
8146
|
+
}
|
|
8147
|
+
|
|
8148
|
+
function getFileBrowserLocalLinkContext() {
|
|
8149
|
+
return { sourcePath: "", resourceDir: fileBrowserState.rootDir || getCurrentResourceDirValue() || "" };
|
|
8150
|
+
}
|
|
8151
|
+
|
|
8152
|
+
function formatFileBrowserSize(size) {
|
|
8153
|
+
const value = Number(size);
|
|
8154
|
+
if (!Number.isFinite(value) || value < 0) return "";
|
|
8155
|
+
if (value < 1024) return Math.round(value) + " B";
|
|
8156
|
+
if (value < 1024 * 1024) return (value / 1024).toFixed(value < 10 * 1024 ? 1 : 0) + " KB";
|
|
8157
|
+
if (value < 1024 * 1024 * 1024) return (value / (1024 * 1024)).toFixed(value < 10 * 1024 * 1024 ? 1 : 0) + " MB";
|
|
8158
|
+
return (value / (1024 * 1024 * 1024)).toFixed(1) + " GB";
|
|
8159
|
+
}
|
|
8160
|
+
|
|
8161
|
+
function formatFileBrowserTime(ms) {
|
|
8162
|
+
const value = Number(ms);
|
|
8163
|
+
if (!Number.isFinite(value) || value <= 0) return "";
|
|
8164
|
+
try {
|
|
8165
|
+
return new Date(value).toLocaleDateString([], { month: "short", day: "numeric", year: "numeric" });
|
|
8166
|
+
} catch {
|
|
8167
|
+
return "";
|
|
8168
|
+
}
|
|
8169
|
+
}
|
|
8170
|
+
|
|
8171
|
+
function getFileBrowserKindLabel(entry) {
|
|
8172
|
+
if (!entry || entry.type === "directory") return "folder";
|
|
8173
|
+
if (entry.kind === "text") return "document";
|
|
8174
|
+
if (entry.kind === "pdf") return "PDF";
|
|
8175
|
+
if (entry.kind === "image") return "image";
|
|
8176
|
+
return entry.extension ? entry.extension.replace(/^\./, "") : "file";
|
|
8177
|
+
}
|
|
8178
|
+
|
|
8179
|
+
function buildFileBrowserPanelHtml() {
|
|
8180
|
+
const state = fileBrowserState || {};
|
|
8181
|
+
const entries = Array.isArray(state.entries) ? state.entries : [];
|
|
8182
|
+
const currentDir = state.currentDir || "";
|
|
8183
|
+
const rootDir = state.rootDir || "";
|
|
8184
|
+
const relativeDir = state.relativeDir || ".";
|
|
8185
|
+
const parentDisabled = state.parentDir ? "" : " disabled";
|
|
8186
|
+
const rows = entries.length
|
|
8187
|
+
? entries.map((entry) => {
|
|
8188
|
+
const type = entry.type === "directory" ? "directory" : "file";
|
|
8189
|
+
const kind = entry.kind || (type === "directory" ? "directory" : "other");
|
|
8190
|
+
const icon = type === "directory" ? "📁" : (kind === "pdf" ? "📄" : (kind === "image" ? "🖼️" : (kind === "text" ? "📝" : "📦")));
|
|
8191
|
+
const metaParts = [];
|
|
8192
|
+
metaParts.push(getFileBrowserKindLabel(entry));
|
|
8193
|
+
if (type === "file") metaParts.push(formatFileBrowserSize(entry.size));
|
|
8194
|
+
const time = formatFileBrowserTime(entry.mtimeMs);
|
|
8195
|
+
if (time) metaParts.push(time);
|
|
8196
|
+
const textActions = kind === "text"
|
|
8197
|
+
? "<button type='button' data-files-action='open-new' data-files-path='" + escapeHtml(entry.path) + "'>New tab</button>"
|
|
8198
|
+
: "";
|
|
8199
|
+
const openTitle = type === "directory"
|
|
8200
|
+
? "Open folder"
|
|
8201
|
+
: (kind === "text" ? "Open in editor" : (kind === "pdf" ? "Open PDF preview" : (kind === "image" ? "Open image preview" : "Copy or reveal this file")));
|
|
8202
|
+
return "<div class='files-row files-row-" + escapeHtml(type) + " files-kind-" + escapeHtml(kind) + "'>"
|
|
8203
|
+
+ "<button type='button' class='files-open-btn' data-files-action='" + (type === "directory" ? "open-dir" : "open") + "' data-files-path='" + escapeHtml(entry.path) + "' data-files-kind='" + escapeHtml(kind) + "' title='" + escapeHtml(openTitle) + "'>"
|
|
8204
|
+
+ "<span class='files-icon' aria-hidden='true'>" + icon + "</span>"
|
|
8205
|
+
+ "<span class='files-name'>" + escapeHtml(entry.name) + "</span>"
|
|
8206
|
+
+ "<span class='files-meta'>" + escapeHtml(metaParts.filter(Boolean).join(" · ")) + "</span>"
|
|
8207
|
+
+ "</button>"
|
|
8208
|
+
+ "<span class='files-actions'>"
|
|
8209
|
+
+ textActions
|
|
8210
|
+
+ "<button type='button' data-files-action='copy-path' data-files-path='" + escapeHtml(entry.path) + "'>Copy path</button>"
|
|
8211
|
+
+ (type === "file" ? "<button type='button' data-files-action='reveal' data-files-path='" + escapeHtml(entry.path) + "'>Reveal</button>" : "")
|
|
8212
|
+
+ "</span>"
|
|
8213
|
+
+ "</div>";
|
|
8214
|
+
}).join("")
|
|
8215
|
+
: "<div class='files-empty'>" + (state.loading ? "Loading files…" : "This folder is empty.") + "</div>";
|
|
8216
|
+
const notices = [];
|
|
8217
|
+
if (state.error) notices.push("<div class='files-notice files-notice-error'>" + escapeHtml(state.error) + "</div>");
|
|
8218
|
+
if (state.omitted) notices.push("<div class='files-notice'>" + escapeHtml(String(state.omitted)) + " item" + (state.omitted === 1 ? "" : "s") + " omitted.</div>");
|
|
8219
|
+
if (state.omittedIgnored) notices.push("<div class='files-notice'>" + escapeHtml(String(state.omittedIgnored)) + " heavy/cache folder" + (state.omittedIgnored === 1 ? "" : "s") + " hidden.</div>");
|
|
8220
|
+
return "<div class='files-panel'>"
|
|
8221
|
+
+ "<div class='files-toolbar'>"
|
|
8222
|
+
+ "<div class='files-path-group'><span class='files-label'>Files</span><span class='files-path' title='" + escapeHtml(currentDir) + "'>" + escapeHtml(relativeDir || ".") + "</span></div>"
|
|
8223
|
+
+ "<div class='files-toolbar-actions'>"
|
|
8224
|
+
+ "<button type='button' data-files-action='parent'" + parentDisabled + ">Parent</button>"
|
|
8225
|
+
+ "<button type='button' data-files-action='refresh'>Refresh</button>"
|
|
8226
|
+
+ (rootDir ? "<button type='button' data-files-action='copy-root' data-files-path='" + escapeHtml(rootDir) + "'>Copy root</button>" : "")
|
|
8227
|
+
+ "</div>"
|
|
8228
|
+
+ "</div>"
|
|
8229
|
+
+ "<div class='files-subtitle'>Root: <span title='" + escapeHtml(rootDir) + "'>" + escapeHtml(rootDir || "current Studio directory") + "</span></div>"
|
|
8230
|
+
+ notices.join("")
|
|
8231
|
+
+ "<div class='files-list' role='list'>" + rows + "</div>"
|
|
8232
|
+
+ "</div>";
|
|
8233
|
+
}
|
|
8234
|
+
|
|
8235
|
+
function renderFilesView() {
|
|
8236
|
+
if (!critiqueViewEl) return;
|
|
8237
|
+
const contextKey = getFileBrowserContextKey();
|
|
8238
|
+
if (fileBrowserState.contextKey !== contextKey) {
|
|
8239
|
+
fileBrowserState = {
|
|
8240
|
+
rootDir: "",
|
|
8241
|
+
currentDir: "",
|
|
8242
|
+
relativeDir: "",
|
|
8243
|
+
parentDir: null,
|
|
8244
|
+
entries: [],
|
|
8245
|
+
omitted: 0,
|
|
8246
|
+
omittedIgnored: 0,
|
|
8247
|
+
loading: false,
|
|
8248
|
+
error: "",
|
|
8249
|
+
loaded: false,
|
|
8250
|
+
contextKey,
|
|
8251
|
+
};
|
|
8252
|
+
}
|
|
8253
|
+
finishPreviewRender(critiqueViewEl);
|
|
8254
|
+
critiqueViewEl.innerHTML = buildFileBrowserPanelHtml();
|
|
8255
|
+
critiqueViewEl.classList.remove("response-scroll-resetting");
|
|
8256
|
+
if (!fileBrowserState.loaded && !fileBrowserState.loading) {
|
|
8257
|
+
loadFileBrowserDirectory("");
|
|
8258
|
+
}
|
|
8259
|
+
scheduleResponsePaneRepaintNudge();
|
|
8260
|
+
}
|
|
8261
|
+
|
|
8262
|
+
async function loadFileBrowserDirectory(dir, options) {
|
|
8263
|
+
const context = getHtmlPreviewResourceContextOptions();
|
|
8264
|
+
const contextKey = getFileBrowserContextKey();
|
|
8265
|
+
const nonce = ++fileBrowserLoadNonce;
|
|
8266
|
+
fileBrowserState = {
|
|
8267
|
+
...fileBrowserState,
|
|
8268
|
+
contextKey,
|
|
8269
|
+
loading: true,
|
|
8270
|
+
error: "",
|
|
8271
|
+
};
|
|
8272
|
+
if (rightView === "files") {
|
|
8273
|
+
finishPreviewRender(critiqueViewEl);
|
|
8274
|
+
critiqueViewEl.innerHTML = buildFileBrowserPanelHtml();
|
|
8275
|
+
}
|
|
8276
|
+
try {
|
|
8277
|
+
const query = {};
|
|
8278
|
+
if (dir) query.dir = String(dir);
|
|
8279
|
+
if (context.sourcePath) query.sourcePath = context.sourcePath;
|
|
8280
|
+
if (context.resourceDir) query.resourceDir = context.resourceDir;
|
|
8281
|
+
const payload = await fetchStudioJson("/file-browser", { query });
|
|
8282
|
+
if (nonce !== fileBrowserLoadNonce) return;
|
|
8283
|
+
fileBrowserState = {
|
|
8284
|
+
rootDir: typeof payload.rootDir === "string" ? payload.rootDir : "",
|
|
8285
|
+
currentDir: typeof payload.currentDir === "string" ? payload.currentDir : "",
|
|
8286
|
+
relativeDir: typeof payload.relativeDir === "string" ? payload.relativeDir : ".",
|
|
8287
|
+
parentDir: typeof payload.parentDir === "string" ? payload.parentDir : null,
|
|
8288
|
+
entries: Array.isArray(payload.entries) ? payload.entries : [],
|
|
8289
|
+
omitted: Number(payload.omitted) || 0,
|
|
8290
|
+
omittedIgnored: Number(payload.omittedIgnored) || 0,
|
|
8291
|
+
loading: false,
|
|
8292
|
+
error: "",
|
|
8293
|
+
loaded: true,
|
|
8294
|
+
contextKey,
|
|
8295
|
+
};
|
|
8296
|
+
if (rightView === "files") {
|
|
8297
|
+
finishPreviewRender(critiqueViewEl);
|
|
8298
|
+
critiqueViewEl.innerHTML = buildFileBrowserPanelHtml();
|
|
8299
|
+
scheduleResponsePaneRepaintNudge();
|
|
8300
|
+
}
|
|
8301
|
+
if (options && options.user) setStatus("Loaded file list.", "success");
|
|
8302
|
+
} catch (error) {
|
|
8303
|
+
if (nonce !== fileBrowserLoadNonce) return;
|
|
8304
|
+
fileBrowserState = {
|
|
8305
|
+
...fileBrowserState,
|
|
8306
|
+
loading: false,
|
|
8307
|
+
error: (error && error.message) ? error.message : String(error || "Could not load files."),
|
|
8308
|
+
loaded: true,
|
|
8309
|
+
};
|
|
8310
|
+
if (rightView === "files") {
|
|
8311
|
+
finishPreviewRender(critiqueViewEl);
|
|
8312
|
+
critiqueViewEl.innerHTML = buildFileBrowserPanelHtml();
|
|
8313
|
+
scheduleResponsePaneRepaintNudge();
|
|
8314
|
+
}
|
|
8315
|
+
}
|
|
8316
|
+
}
|
|
8317
|
+
|
|
8318
|
+
async function openFileBrowserEntry(path, kind) {
|
|
8319
|
+
const context = getFileBrowserLocalLinkContext();
|
|
8320
|
+
if (kind === "text") {
|
|
8321
|
+
await openPreviewDocumentHere(path, context);
|
|
8322
|
+
return;
|
|
8323
|
+
}
|
|
8324
|
+
if (kind === "pdf") {
|
|
8325
|
+
openPreviewPdfLink(path, path, context);
|
|
8326
|
+
return;
|
|
8327
|
+
}
|
|
8328
|
+
if (kind === "image") {
|
|
8329
|
+
await openPreviewImageLink(path, path, context);
|
|
8330
|
+
return;
|
|
8331
|
+
}
|
|
8332
|
+
setStatus("No Studio preview for this file type. Use Copy path or Reveal.", "warning");
|
|
8333
|
+
}
|
|
8334
|
+
|
|
8335
|
+
async function handleFilesPaneClick(event) {
|
|
8336
|
+
if (rightView !== "files") return;
|
|
8337
|
+
const target = event.target;
|
|
8338
|
+
const actionEl = target instanceof Element ? target.closest("[data-files-action]") : null;
|
|
8339
|
+
if (!actionEl) return;
|
|
8340
|
+
event.preventDefault();
|
|
8341
|
+
const action = actionEl.getAttribute("data-files-action") || "";
|
|
8342
|
+
const path = actionEl.getAttribute("data-files-path") || "";
|
|
8343
|
+
const kind = actionEl.getAttribute("data-files-kind") || getPreviewLocalLinkKind(path);
|
|
8344
|
+
try {
|
|
8345
|
+
if (action === "parent") {
|
|
8346
|
+
if (fileBrowserState.parentDir) await loadFileBrowserDirectory(fileBrowserState.parentDir, { user: true });
|
|
8347
|
+
return;
|
|
8348
|
+
}
|
|
8349
|
+
if (action === "refresh") {
|
|
8350
|
+
await loadFileBrowserDirectory(fileBrowserState.currentDir || "", { user: true });
|
|
8351
|
+
return;
|
|
8352
|
+
}
|
|
8353
|
+
if (action === "open-dir") {
|
|
8354
|
+
await loadFileBrowserDirectory(path, { user: true });
|
|
8355
|
+
return;
|
|
8356
|
+
}
|
|
8357
|
+
if (action === "open") {
|
|
8358
|
+
await openFileBrowserEntry(path, kind);
|
|
8359
|
+
return;
|
|
8360
|
+
}
|
|
8361
|
+
if (action === "open-new") {
|
|
8362
|
+
await openPreviewDocumentInNewEditor(path, null, getFileBrowserLocalLinkContext());
|
|
8363
|
+
return;
|
|
8364
|
+
}
|
|
8365
|
+
if (action === "copy-path" || action === "copy-root") {
|
|
8366
|
+
const ok = await writeTextToClipboard(path);
|
|
8367
|
+
setStatus(ok ? "Copied path." : "Clipboard write failed.", ok ? "success" : "warning");
|
|
8368
|
+
return;
|
|
8369
|
+
}
|
|
8370
|
+
if (action === "reveal") {
|
|
8371
|
+
await revealPreviewLocalLink(path, getFileBrowserLocalLinkContext());
|
|
8372
|
+
}
|
|
8373
|
+
} catch (error) {
|
|
8374
|
+
setStatus((error && error.message) ? error.message : String(error || "File action failed."), "warning");
|
|
8375
|
+
}
|
|
8376
|
+
}
|
|
8377
|
+
|
|
8110
8378
|
function renderActiveResult() {
|
|
8111
8379
|
if (rightView === "trace") {
|
|
8112
8380
|
renderTraceView();
|
|
@@ -8118,6 +8386,11 @@
|
|
|
8118
8386
|
return;
|
|
8119
8387
|
}
|
|
8120
8388
|
|
|
8389
|
+
if (rightView === "files") {
|
|
8390
|
+
renderFilesView();
|
|
8391
|
+
return;
|
|
8392
|
+
}
|
|
8393
|
+
|
|
8121
8394
|
if (rightView === "editor-preview") {
|
|
8122
8395
|
const editorText = prepareEditorTextForPreview(sourceTextEl.value || "");
|
|
8123
8396
|
if (!editorText.trim()) {
|
|
@@ -8192,7 +8465,7 @@
|
|
|
8192
8465
|
: normalizeForCompare(sourceTextEl.value);
|
|
8193
8466
|
const responseLoaded = hasResponse && normalizedEditor === latestResponseNormalized;
|
|
8194
8467
|
const isCritiqueResponse = hasResponse && latestResponseIsStructuredCritique;
|
|
8195
|
-
const showingAuxiliaryRightPane = rightView === "trace" || rightView === "repl";
|
|
8468
|
+
const showingAuxiliaryRightPane = rightView === "trace" || rightView === "repl" || rightView === "files";
|
|
8196
8469
|
|
|
8197
8470
|
if (responseWrapEl) {
|
|
8198
8471
|
responseWrapEl.hidden = showingAuxiliaryRightPane;
|
|
@@ -8233,6 +8506,8 @@
|
|
|
8233
8506
|
: (exportingReplJournal ? "Export record" : "Export right preview");
|
|
8234
8507
|
if (rightView === "trace") {
|
|
8235
8508
|
exportPdfBtn.title = "Working view does not support preview export.";
|
|
8509
|
+
} else if (rightView === "files") {
|
|
8510
|
+
exportPdfBtn.title = "Files view does not support preview export.";
|
|
8236
8511
|
} else if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
8237
8512
|
exportPdfBtn.title = "No Studio REPL record entries to export for this session yet.";
|
|
8238
8513
|
} else if (rightView === "markdown") {
|
|
@@ -8391,6 +8666,10 @@
|
|
|
8391
8666
|
if (loadGitDiffBtn) loadGitDiffBtn.disabled = uiBusy;
|
|
8392
8667
|
syncRunAndCritiqueButtons();
|
|
8393
8668
|
copyDraftBtn.disabled = uiBusy;
|
|
8669
|
+
if (suggestCompletionBtn) {
|
|
8670
|
+
suggestCompletionBtn.disabled = uiBusy || completionSuggestionInFlight || wsState !== "Ready" || !String(sourceTextEl.value || "").trim();
|
|
8671
|
+
suggestCompletionBtn.textContent = completionSuggestionInFlight ? "Suggesting…" : "Suggest";
|
|
8672
|
+
}
|
|
8394
8673
|
if (openCompanionBtn) openCompanionBtn.disabled = uiBusy || wsState !== "Ready";
|
|
8395
8674
|
if (highlightSelect) highlightSelect.disabled = uiBusy;
|
|
8396
8675
|
if (lineNumbersSelect) lineNumbersSelect.disabled = uiBusy;
|
|
@@ -8465,9 +8744,24 @@
|
|
|
8465
8744
|
return "source:" + normalized.source + ":" + normalized.label;
|
|
8466
8745
|
}
|
|
8467
8746
|
|
|
8747
|
+
function getWorkspacePersistenceStorage() {
|
|
8748
|
+
try {
|
|
8749
|
+
return window.sessionStorage || null;
|
|
8750
|
+
} catch {
|
|
8751
|
+
return null;
|
|
8752
|
+
}
|
|
8753
|
+
}
|
|
8754
|
+
|
|
8755
|
+
function clearLegacyWorkspacePersistenceStorage() {
|
|
8756
|
+
try {
|
|
8757
|
+
if (window.localStorage) window.localStorage.removeItem(STUDIO_WORKSPACE_STORAGE_KEY);
|
|
8758
|
+
} catch {}
|
|
8759
|
+
}
|
|
8760
|
+
|
|
8468
8761
|
function readPersistedWorkspaceState() {
|
|
8469
8762
|
try {
|
|
8470
|
-
const
|
|
8763
|
+
const storage = getWorkspacePersistenceStorage();
|
|
8764
|
+
const raw = storage ? storage.getItem(STUDIO_WORKSPACE_STORAGE_KEY) : null;
|
|
8471
8765
|
if (!raw) return null;
|
|
8472
8766
|
const parsed = JSON.parse(raw);
|
|
8473
8767
|
if (!parsed || typeof parsed !== "object" || parsed.version !== 1) return null;
|
|
@@ -8495,7 +8789,7 @@
|
|
|
8495
8789
|
sourceState: normalizeWorkspaceSourceState(sourceState),
|
|
8496
8790
|
resourceDir: getCurrentResourceDirValue(),
|
|
8497
8791
|
editorView,
|
|
8498
|
-
rightView,
|
|
8792
|
+
rightView: isEditorOnlyMode ? "editor-preview" : rightView,
|
|
8499
8793
|
editorLanguage,
|
|
8500
8794
|
followLatest,
|
|
8501
8795
|
responseHistoryIndex,
|
|
@@ -8509,13 +8803,15 @@
|
|
|
8509
8803
|
function persistWorkspaceStateNow() {
|
|
8510
8804
|
if (!workspacePersistenceReady) return;
|
|
8511
8805
|
try {
|
|
8512
|
-
|
|
8806
|
+
const storage = getWorkspacePersistenceStorage();
|
|
8807
|
+
if (!storage) return;
|
|
8808
|
+
clearLegacyWorkspacePersistenceStorage();
|
|
8513
8809
|
const payload = buildWorkspacePersistencePayload();
|
|
8514
8810
|
if (payload.text.length > STUDIO_WORKSPACE_MAX_TEXT_CHARS) {
|
|
8515
|
-
|
|
8811
|
+
storage.removeItem(STUDIO_WORKSPACE_STORAGE_KEY);
|
|
8516
8812
|
return;
|
|
8517
8813
|
}
|
|
8518
|
-
|
|
8814
|
+
storage.setItem(STUDIO_WORKSPACE_STORAGE_KEY, JSON.stringify(payload));
|
|
8519
8815
|
} catch {
|
|
8520
8816
|
// Ignore browser storage failures and quota limits.
|
|
8521
8817
|
}
|
|
@@ -8544,8 +8840,10 @@
|
|
|
8544
8840
|
workspacePersistTimer = null;
|
|
8545
8841
|
}
|
|
8546
8842
|
try {
|
|
8547
|
-
|
|
8843
|
+
const storage = getWorkspacePersistenceStorage();
|
|
8844
|
+
if (storage) storage.removeItem(STUDIO_WORKSPACE_STORAGE_KEY);
|
|
8548
8845
|
} catch {}
|
|
8846
|
+
clearLegacyWorkspacePersistenceStorage();
|
|
8549
8847
|
}
|
|
8550
8848
|
|
|
8551
8849
|
function applyPersistedWorkspaceState(state) {
|
|
@@ -8563,11 +8861,15 @@
|
|
|
8563
8861
|
setEditorLanguage(state.editorLanguage.trim());
|
|
8564
8862
|
}
|
|
8565
8863
|
editorView = state.editorView === "preview" ? "preview" : "markdown";
|
|
8566
|
-
rightView =
|
|
8567
|
-
? "preview"
|
|
8568
|
-
: (state.rightView === "
|
|
8569
|
-
? "
|
|
8570
|
-
: (state.rightView === "
|
|
8864
|
+
rightView = isEditorOnlyMode
|
|
8865
|
+
? "editor-preview"
|
|
8866
|
+
: (state.rightView === "preview"
|
|
8867
|
+
? "preview"
|
|
8868
|
+
: (state.rightView === "editor-preview"
|
|
8869
|
+
? "editor-preview"
|
|
8870
|
+
: (state.rightView === "repl"
|
|
8871
|
+
? "repl"
|
|
8872
|
+
: (state.rightView === "files" ? "files" : ((state.rightView === "trace" || state.rightView === "thinking") ? "trace" : "markdown")))));
|
|
8571
8873
|
if (typeof state.followLatest === "boolean") {
|
|
8572
8874
|
followLatest = state.followLatest;
|
|
8573
8875
|
}
|
|
@@ -8592,7 +8894,7 @@
|
|
|
8592
8894
|
setStatus("Studio is busy.", "warning");
|
|
8593
8895
|
return;
|
|
8594
8896
|
}
|
|
8595
|
-
const confirmed = window.confirm("
|
|
8897
|
+
const confirmed = window.confirm("Reset the editor to a fresh blank draft in this browser tab? Saved files and responses are not changed.");
|
|
8596
8898
|
if (!confirmed) return;
|
|
8597
8899
|
const preservedResponseState = {
|
|
8598
8900
|
responseHistory: Array.isArray(responseHistory) ? responseHistory.slice() : [],
|
|
@@ -8634,7 +8936,7 @@
|
|
|
8634
8936
|
if (followSelect) followSelect.value = followLatest ? "on" : "off";
|
|
8635
8937
|
refreshResponseUi();
|
|
8636
8938
|
persistWorkspaceStateNow();
|
|
8637
|
-
setStatus("Editor
|
|
8939
|
+
setStatus("Editor reset to a fresh blank draft. Saved files and responses were not changed.", "success");
|
|
8638
8940
|
}
|
|
8639
8941
|
|
|
8640
8942
|
function setEditorText(nextText, options) {
|
|
@@ -8701,6 +9003,197 @@
|
|
|
8701
9003
|
}
|
|
8702
9004
|
}
|
|
8703
9005
|
|
|
9006
|
+
function hideCompletionSuggestion() {
|
|
9007
|
+
completionSuggestionState = null;
|
|
9008
|
+
if (completionSuggestionTextEl) completionSuggestionTextEl.textContent = "";
|
|
9009
|
+
if (completionSuggestionPanelEl) completionSuggestionPanelEl.hidden = true;
|
|
9010
|
+
}
|
|
9011
|
+
|
|
9012
|
+
function showCompletionSuggestion(state) {
|
|
9013
|
+
completionSuggestionState = state;
|
|
9014
|
+
if (completionSuggestionTextEl) completionSuggestionTextEl.textContent = state && state.suggestion ? state.suggestion : "";
|
|
9015
|
+
if (completionSuggestionPanelEl) completionSuggestionPanelEl.hidden = false;
|
|
9016
|
+
}
|
|
9017
|
+
|
|
9018
|
+
function focusSourceTextNoScroll() {
|
|
9019
|
+
if (!sourceTextEl || typeof sourceTextEl.focus !== "function") return;
|
|
9020
|
+
try {
|
|
9021
|
+
sourceTextEl.focus({ preventScroll: true });
|
|
9022
|
+
} catch {
|
|
9023
|
+
try { sourceTextEl.focus(); } catch {}
|
|
9024
|
+
}
|
|
9025
|
+
}
|
|
9026
|
+
|
|
9027
|
+
function focusSourceEditorForCompletion() {
|
|
9028
|
+
const snapshot = snapshotStudioScrollablePositions();
|
|
9029
|
+
if (editorView !== "markdown") {
|
|
9030
|
+
setEditorView("markdown");
|
|
9031
|
+
scheduleStudioScrollablePositionRestore(snapshot);
|
|
9032
|
+
}
|
|
9033
|
+
window.setTimeout(focusSourceTextNoScroll, 0);
|
|
9034
|
+
}
|
|
9035
|
+
|
|
9036
|
+
function isCompletionSuggestionRequestShortcut(event) {
|
|
9037
|
+
if (!event) return false;
|
|
9038
|
+
const key = typeof event.key === "string" ? event.key : "";
|
|
9039
|
+
const code = typeof event.code === "string" ? event.code : "";
|
|
9040
|
+
const commandSpace = (event.metaKey || event.ctrlKey)
|
|
9041
|
+
&& event.shiftKey
|
|
9042
|
+
&& !event.altKey
|
|
9043
|
+
&& (code === "Space" || key === " " || key === "Spacebar");
|
|
9044
|
+
const optionTab = event.altKey
|
|
9045
|
+
&& !event.metaKey
|
|
9046
|
+
&& !event.ctrlKey
|
|
9047
|
+
&& !event.shiftKey
|
|
9048
|
+
&& key === "Tab";
|
|
9049
|
+
return commandSpace || optionTab;
|
|
9050
|
+
}
|
|
9051
|
+
|
|
9052
|
+
function handleCompletionSuggestionAcceptKey(event) {
|
|
9053
|
+
if (!event || !completionSuggestionState) return false;
|
|
9054
|
+
if (event.key !== "Tab" || event.shiftKey || event.metaKey || event.ctrlKey || event.altKey) return false;
|
|
9055
|
+
const target = event.target;
|
|
9056
|
+
const focusIsUnclaimed = target === document.body || target === document.documentElement;
|
|
9057
|
+
const targetCanAccept = focusIsUnclaimed
|
|
9058
|
+
|| target === sourceTextEl
|
|
9059
|
+
|| target === suggestCompletionBtn
|
|
9060
|
+
|| Boolean(completionSuggestionPanelEl && target instanceof Element && completionSuggestionPanelEl.contains(target));
|
|
9061
|
+
if (!targetCanAccept) return false;
|
|
9062
|
+
event.preventDefault();
|
|
9063
|
+
insertCompletionSuggestion();
|
|
9064
|
+
return true;
|
|
9065
|
+
}
|
|
9066
|
+
|
|
9067
|
+
function shouldRefocusEditorForCompletionRequest() {
|
|
9068
|
+
const activeEl = document.activeElement;
|
|
9069
|
+
return activeEl === sourceTextEl
|
|
9070
|
+
|| activeEl === suggestCompletionBtn
|
|
9071
|
+
|| activeEl === document.body
|
|
9072
|
+
|| activeEl === document.documentElement
|
|
9073
|
+
|| Boolean(completionSuggestionPanelEl && activeEl instanceof Element && completionSuggestionPanelEl.contains(activeEl));
|
|
9074
|
+
}
|
|
9075
|
+
|
|
9076
|
+
function requestCompletionSuggestion() {
|
|
9077
|
+
if (isEditorOnlyMode && !sourceTextEl) return;
|
|
9078
|
+
if (completionSuggestionInFlight) {
|
|
9079
|
+
setStatus("Suggestion request already in progress.", "warning");
|
|
9080
|
+
return;
|
|
9081
|
+
}
|
|
9082
|
+
const text = String(sourceTextEl.value || "");
|
|
9083
|
+
if (!text.trim()) {
|
|
9084
|
+
setStatus("Editor is empty.", "warning");
|
|
9085
|
+
return;
|
|
9086
|
+
}
|
|
9087
|
+
const selectionStart = typeof sourceTextEl.selectionStart === "number" ? sourceTextEl.selectionStart : text.length;
|
|
9088
|
+
const selectionEnd = typeof sourceTextEl.selectionEnd === "number" ? sourceTextEl.selectionEnd : selectionStart;
|
|
9089
|
+
const requestId = makeRequestId();
|
|
9090
|
+
completionSuggestionInFlight = true;
|
|
9091
|
+
completionSuggestionRequestId = requestId;
|
|
9092
|
+
completionSuggestionPendingSnapshot = { text, selectionStart, selectionEnd };
|
|
9093
|
+
completionSuggestionRefocusEditorOnResult = shouldRefocusEditorForCompletionRequest();
|
|
9094
|
+
hideCompletionSuggestion();
|
|
9095
|
+
syncActionButtons();
|
|
9096
|
+
setStatus("Generating completion suggestion…", "warning");
|
|
9097
|
+
const sent = sendMessage({
|
|
9098
|
+
type: "completion_suggestion_request",
|
|
9099
|
+
requestId,
|
|
9100
|
+
text,
|
|
9101
|
+
selectionStart,
|
|
9102
|
+
selectionEnd,
|
|
9103
|
+
language: editorLanguage || "",
|
|
9104
|
+
label: sourceState && sourceState.label ? sourceState.label : "Studio editor",
|
|
9105
|
+
path: sourceState && sourceState.path ? sourceState.path : undefined,
|
|
9106
|
+
});
|
|
9107
|
+
if (!sent) {
|
|
9108
|
+
completionSuggestionInFlight = false;
|
|
9109
|
+
completionSuggestionRequestId = null;
|
|
9110
|
+
completionSuggestionPendingSnapshot = null;
|
|
9111
|
+
completionSuggestionRefocusEditorOnResult = false;
|
|
9112
|
+
syncActionButtons();
|
|
9113
|
+
}
|
|
9114
|
+
}
|
|
9115
|
+
|
|
9116
|
+
function insertCompletionSuggestion() {
|
|
9117
|
+
const state = completionSuggestionState;
|
|
9118
|
+
if (!state || typeof state.suggestion !== "string") {
|
|
9119
|
+
setStatus("No suggestion to insert.", "warning");
|
|
9120
|
+
return;
|
|
9121
|
+
}
|
|
9122
|
+
const currentText = String(sourceTextEl.value || "");
|
|
9123
|
+
const useOriginalRange = currentText === state.baseText;
|
|
9124
|
+
const start = useOriginalRange
|
|
9125
|
+
? Math.max(0, Math.min(state.selectionStart, currentText.length))
|
|
9126
|
+
: (typeof sourceTextEl.selectionStart === "number" ? sourceTextEl.selectionStart : currentText.length);
|
|
9127
|
+
const end = useOriginalRange
|
|
9128
|
+
? Math.max(start, Math.min(state.selectionEnd, currentText.length))
|
|
9129
|
+
: (typeof sourceTextEl.selectionEnd === "number" ? sourceTextEl.selectionEnd : start);
|
|
9130
|
+
const nextText = currentText.slice(0, start) + state.suggestion + currentText.slice(end);
|
|
9131
|
+
const caret = start + state.suggestion.length;
|
|
9132
|
+
applySourceTextEdit(nextText, caret, caret);
|
|
9133
|
+
hideCompletionSuggestion();
|
|
9134
|
+
focusSourceTextNoScroll();
|
|
9135
|
+
setStatus("Inserted completion suggestion.", "success");
|
|
9136
|
+
}
|
|
9137
|
+
|
|
9138
|
+
function handleCompletionSuggestionServerMessage(message) {
|
|
9139
|
+
if (!message || typeof message !== "object") return false;
|
|
9140
|
+
if (
|
|
9141
|
+
message.type !== "completion_suggestion_progress"
|
|
9142
|
+
&& message.type !== "completion_suggestion_result"
|
|
9143
|
+
&& message.type !== "completion_suggestion_error"
|
|
9144
|
+
) return false;
|
|
9145
|
+
if (typeof message.requestId === "string" && completionSuggestionRequestId && message.requestId !== completionSuggestionRequestId) {
|
|
9146
|
+
return true;
|
|
9147
|
+
}
|
|
9148
|
+
if (message.type === "completion_suggestion_progress") {
|
|
9149
|
+
setStatus(typeof message.message === "string" ? message.message : "Generating suggestion…", "warning");
|
|
9150
|
+
return true;
|
|
9151
|
+
}
|
|
9152
|
+
const pendingSnapshot = completionSuggestionPendingSnapshot;
|
|
9153
|
+
const shouldRefocusEditor = completionSuggestionRefocusEditorOnResult;
|
|
9154
|
+
completionSuggestionInFlight = false;
|
|
9155
|
+
completionSuggestionRequestId = null;
|
|
9156
|
+
completionSuggestionPendingSnapshot = null;
|
|
9157
|
+
completionSuggestionRefocusEditorOnResult = false;
|
|
9158
|
+
syncActionButtons();
|
|
9159
|
+
if (message.type === "completion_suggestion_error") {
|
|
9160
|
+
setStatus(typeof message.message === "string" ? message.message : "Suggestion failed.", "warning");
|
|
9161
|
+
return true;
|
|
9162
|
+
}
|
|
9163
|
+
const suggestion = typeof message.suggestion === "string" ? message.suggestion : "";
|
|
9164
|
+
if (!suggestion.trim()) {
|
|
9165
|
+
setStatus("Model returned an empty suggestion.", "warning");
|
|
9166
|
+
return true;
|
|
9167
|
+
}
|
|
9168
|
+
const text = String(sourceTextEl.value || "");
|
|
9169
|
+
if (pendingSnapshot && text !== pendingSnapshot.text) {
|
|
9170
|
+
setStatus("Editor changed while the suggestion was generating. Please request a fresh suggestion.", "warning");
|
|
9171
|
+
return true;
|
|
9172
|
+
}
|
|
9173
|
+
const baseText = pendingSnapshot ? pendingSnapshot.text : text;
|
|
9174
|
+
const start = Math.max(0, Math.min(pendingSnapshot ? pendingSnapshot.selectionStart : (Number(message.selectionStart) || 0), baseText.length));
|
|
9175
|
+
const end = Math.max(start, Math.min(pendingSnapshot ? pendingSnapshot.selectionEnd : (Number(message.selectionEnd) || start), baseText.length));
|
|
9176
|
+
showCompletionSuggestion({
|
|
9177
|
+
suggestion,
|
|
9178
|
+
baseText,
|
|
9179
|
+
selectionStart: start,
|
|
9180
|
+
selectionEnd: end,
|
|
9181
|
+
});
|
|
9182
|
+
const activeEl = document.activeElement;
|
|
9183
|
+
if (
|
|
9184
|
+
shouldRefocusEditor
|
|
9185
|
+
|| activeEl === sourceTextEl
|
|
9186
|
+
|| activeEl === suggestCompletionBtn
|
|
9187
|
+
|| activeEl === document.body
|
|
9188
|
+
|| activeEl === document.documentElement
|
|
9189
|
+
|| Boolean(completionSuggestionPanelEl && activeEl instanceof Element && completionSuggestionPanelEl.contains(activeEl))
|
|
9190
|
+
) {
|
|
9191
|
+
focusSourceEditorForCompletion();
|
|
9192
|
+
}
|
|
9193
|
+
setStatus("Suggestion ready. Press Tab to insert it, or use the Insert suggestion button.", "success");
|
|
9194
|
+
return true;
|
|
9195
|
+
}
|
|
9196
|
+
|
|
8704
9197
|
function getSourceTextLineEditBounds(text, selectionStart, selectionEnd) {
|
|
8705
9198
|
const source = String(text || "");
|
|
8706
9199
|
const safeStart = Math.max(0, Math.min(Math.floor(Number(selectionStart) || 0), source.length));
|
|
@@ -8780,7 +9273,14 @@
|
|
|
8780
9273
|
}
|
|
8781
9274
|
|
|
8782
9275
|
function handleSourceTextTabKey(event) {
|
|
8783
|
-
if (!event
|
|
9276
|
+
if (!event) return;
|
|
9277
|
+
if (isCompletionSuggestionRequestShortcut(event)) {
|
|
9278
|
+
event.preventDefault();
|
|
9279
|
+
requestCompletionSuggestion();
|
|
9280
|
+
return;
|
|
9281
|
+
}
|
|
9282
|
+
if (handleCompletionSuggestionAcceptKey(event)) return;
|
|
9283
|
+
if (event.key !== "Tab" || event.metaKey || event.ctrlKey || event.altKey) return;
|
|
8784
9284
|
event.preventDefault();
|
|
8785
9285
|
if (event.shiftKey) {
|
|
8786
9286
|
unindentSourceTextSelection();
|
|
@@ -8833,7 +9333,9 @@
|
|
|
8833
9333
|
? "editor-preview"
|
|
8834
9334
|
: (nextView === "repl"
|
|
8835
9335
|
? "repl"
|
|
8836
|
-
: (
|
|
9336
|
+
: (nextView === "files"
|
|
9337
|
+
? "files"
|
|
9338
|
+
: ((nextView === "trace" || nextView === "thinking") ? "trace" : "markdown"))));
|
|
8837
9339
|
rightViewSelect.value = rightView;
|
|
8838
9340
|
if (rightView === "trace" && previousView !== "trace") {
|
|
8839
9341
|
traceAutoScroll = true;
|
|
@@ -9173,6 +9675,11 @@
|
|
|
9173
9675
|
".diff", ".patch",
|
|
9174
9676
|
]);
|
|
9175
9677
|
const PREVIEW_LOCAL_IMAGE_LINK_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
9678
|
+
const PREVIEW_LOCAL_TEXT_LINK_FILENAMES = new Set([
|
|
9679
|
+
".dockerignore", ".editorconfig", ".env", ".env.example", ".eslintignore", ".gitattributes",
|
|
9680
|
+
".gitignore", ".gitmodules", ".npmignore", ".prettierignore", "dockerfile", "gemfile",
|
|
9681
|
+
"justfile", "license", "makefile", "rakefile", "readme",
|
|
9682
|
+
]);
|
|
9176
9683
|
let previewLinkMenuEl = null;
|
|
9177
9684
|
let activePreviewLinkContext = null;
|
|
9178
9685
|
|
|
@@ -9220,10 +9727,17 @@
|
|
|
9220
9727
|
return match ? ("." + match[1].toLowerCase()) : "";
|
|
9221
9728
|
}
|
|
9222
9729
|
|
|
9730
|
+
function getPreviewLocalLinkFilename(href) {
|
|
9731
|
+
const path = stripPreviewLocalLinkUrlSuffix(href).replace(/\\/g, "/");
|
|
9732
|
+
const parts = path.split("/");
|
|
9733
|
+
return (parts.pop() || "").toLowerCase();
|
|
9734
|
+
}
|
|
9735
|
+
|
|
9223
9736
|
function getPreviewLocalLinkKind(href) {
|
|
9224
9737
|
const ext = getPreviewLocalLinkExtension(href);
|
|
9738
|
+
const name = getPreviewLocalLinkFilename(href);
|
|
9225
9739
|
if (ext === ".pdf") return "pdf";
|
|
9226
|
-
if (PREVIEW_LOCAL_TEXT_LINK_EXTENSIONS.has(ext)) return "text";
|
|
9740
|
+
if (PREVIEW_LOCAL_TEXT_LINK_EXTENSIONS.has(ext) || PREVIEW_LOCAL_TEXT_LINK_FILENAMES.has(name)) return "text";
|
|
9227
9741
|
if (PREVIEW_LOCAL_IMAGE_LINK_EXTENSIONS.has(ext)) return "image";
|
|
9228
9742
|
return "other";
|
|
9229
9743
|
}
|
|
@@ -9240,9 +9754,11 @@
|
|
|
9240
9754
|
function getEffectivePreviewLinkContext(contextOverride) {
|
|
9241
9755
|
const fallback = getHtmlPreviewResourceContextOptions();
|
|
9242
9756
|
const context = contextOverride && typeof contextOverride === "object" ? contextOverride : null;
|
|
9757
|
+
const hasSourcePath = Boolean(context && Object.prototype.hasOwnProperty.call(context, "sourcePath"));
|
|
9758
|
+
const hasResourceDir = Boolean(context && Object.prototype.hasOwnProperty.call(context, "resourceDir"));
|
|
9243
9759
|
return {
|
|
9244
|
-
sourcePath:
|
|
9245
|
-
resourceDir:
|
|
9760
|
+
sourcePath: hasSourcePath ? String(context.sourcePath || "") : (fallback.sourcePath || ""),
|
|
9761
|
+
resourceDir: hasResourceDir ? String(context.resourceDir || "") : (fallback.resourceDir || ""),
|
|
9246
9762
|
};
|
|
9247
9763
|
}
|
|
9248
9764
|
|
|
@@ -15706,6 +16222,8 @@
|
|
|
15706
16222
|
updateFooterMeta();
|
|
15707
16223
|
}
|
|
15708
16224
|
|
|
16225
|
+
if (handleCompletionSuggestionServerMessage(message)) return;
|
|
16226
|
+
|
|
15709
16227
|
if (
|
|
15710
16228
|
message.type === "quiz_progress" ||
|
|
15711
16229
|
message.type === "quiz_generated" ||
|
|
@@ -16957,6 +17475,9 @@
|
|
|
16957
17475
|
sourceTextEl.addEventListener("keydown", handleSourceTextTabKey);
|
|
16958
17476
|
|
|
16959
17477
|
sourceTextEl.addEventListener("input", () => {
|
|
17478
|
+
if (completionSuggestionState && sourceTextEl.value !== completionSuggestionState.baseText) {
|
|
17479
|
+
hideCompletionSuggestion();
|
|
17480
|
+
}
|
|
16960
17481
|
if (activePreviewCommentSelection) {
|
|
16961
17482
|
clearPreviewCommentSelection();
|
|
16962
17483
|
}
|
|
@@ -16965,6 +17486,7 @@
|
|
|
16965
17486
|
scheduleEditorMetaUpdate();
|
|
16966
17487
|
updateEditorSelectionCommentUi();
|
|
16967
17488
|
updateOutlineUi();
|
|
17489
|
+
syncActionButtons();
|
|
16968
17490
|
if (isReviewNotesOpen() && reviewNotes.length > 0) {
|
|
16969
17491
|
renderReviewNotesList();
|
|
16970
17492
|
updateReviewNotesUi();
|
|
@@ -17420,6 +17942,24 @@
|
|
|
17420
17942
|
}
|
|
17421
17943
|
});
|
|
17422
17944
|
|
|
17945
|
+
if (suggestCompletionBtn) {
|
|
17946
|
+
suggestCompletionBtn.addEventListener("click", () => {
|
|
17947
|
+
requestCompletionSuggestion();
|
|
17948
|
+
});
|
|
17949
|
+
}
|
|
17950
|
+
if (completionSuggestionInsertBtn) {
|
|
17951
|
+
completionSuggestionInsertBtn.addEventListener("click", () => {
|
|
17952
|
+
insertCompletionSuggestion();
|
|
17953
|
+
});
|
|
17954
|
+
}
|
|
17955
|
+
if (completionSuggestionDismissBtn) {
|
|
17956
|
+
completionSuggestionDismissBtn.addEventListener("click", () => {
|
|
17957
|
+
hideCompletionSuggestion();
|
|
17958
|
+
focusSourceTextNoScroll();
|
|
17959
|
+
setStatus("Dismissed completion suggestion.");
|
|
17960
|
+
});
|
|
17961
|
+
}
|
|
17962
|
+
|
|
17423
17963
|
if (reviewNotesBtn) {
|
|
17424
17964
|
reviewNotesBtn.addEventListener("click", () => {
|
|
17425
17965
|
toggleReviewNotes();
|
|
@@ -17891,7 +18431,7 @@
|
|
|
17891
18431
|
renderSourcePreview();
|
|
17892
18432
|
workspacePersistenceReady = true;
|
|
17893
18433
|
if (workspaceRestoredFromBrowser) {
|
|
17894
|
-
setStatus("Restored editor workspace from this browser tab. Use
|
|
18434
|
+
setStatus("Restored editor workspace from this browser tab. Use Reset editor to discard it.", "success");
|
|
17895
18435
|
}
|
|
17896
18436
|
connect();
|
|
17897
18437
|
} catch (error) {
|