pi-studio 0.9.49 → 0.9.51
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 +25 -0
- package/README.md +21 -3
- package/client/studio-client.js +1152 -78
- package/client/studio-side-question-helpers.js +284 -0
- package/client/studio.css +538 -13
- package/index.ts +1301 -15
- package/package.json +5 -5
- package/shared/studio-side-question-context.js +225 -0
- package/shared/studio-side-question-git.js +145 -0
- package/shared/studio-side-question-tools.js +122 -0
- package/shared/studio-side-question.js +109 -0
package/client/studio-client.js
CHANGED
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
const critiqueBtn = document.getElementById("critiqueBtn");
|
|
84
84
|
const showMeBtn = document.getElementById("showMeBtn");
|
|
85
85
|
const showMeResponseBtn = document.getElementById("showMeResponseBtn");
|
|
86
|
+
const askAsideBtn = document.getElementById("askAsideBtn");
|
|
86
87
|
const quizBtn = document.getElementById("quizBtn");
|
|
87
88
|
const lensSelect = document.getElementById("lensSelect");
|
|
88
89
|
const importFileBtn = document.getElementById("importFileBtn");
|
|
@@ -99,6 +100,10 @@
|
|
|
99
100
|
const copyResponseBtn = document.getElementById("copyResponseBtn");
|
|
100
101
|
const exportPreviewControlsEl = document.getElementById("exportPreviewControls");
|
|
101
102
|
const exportPreviewMenuEl = document.getElementById("exportPreviewMenu");
|
|
103
|
+
const exportSideThreadMarkdownSaveBtn = document.getElementById("exportSideThreadMarkdownSaveBtn");
|
|
104
|
+
const exportSideThreadMarkdownCopyBtn = document.getElementById("exportSideThreadMarkdownCopyBtn");
|
|
105
|
+
const exportSideThreadMarkdownEditorBtn = document.getElementById("exportSideThreadMarkdownEditorBtn");
|
|
106
|
+
const exportSideThreadRenderSeparatorEl = document.getElementById("exportSideThreadRenderSeparator");
|
|
102
107
|
const exportPreviewPdfStudioBtn = document.getElementById("exportPreviewPdfStudioBtn");
|
|
103
108
|
const exportPreviewPdfBtn = document.getElementById("exportPreviewPdfBtn");
|
|
104
109
|
const exportPreviewHtmlStudioBtn = document.getElementById("exportPreviewHtmlStudioBtn");
|
|
@@ -116,6 +121,10 @@
|
|
|
116
121
|
const sendEditorBtn = document.getElementById("sendEditorBtn");
|
|
117
122
|
const openCompanionBtn = document.getElementById("openCompanionBtn");
|
|
118
123
|
const getEditorBtn = document.getElementById("getEditorBtn");
|
|
124
|
+
const studioHeaderEl = document.getElementById("studioHeader");
|
|
125
|
+
const hideStudioHeaderBtn = document.getElementById("hideStudioHeaderBtn");
|
|
126
|
+
const studioHeaderRevealEl = document.getElementById("studioHeaderReveal");
|
|
127
|
+
const studioHeaderRevealBtn = document.getElementById("studioHeaderRevealBtn");
|
|
119
128
|
const zenModeBtn = document.getElementById("zenModeBtn");
|
|
120
129
|
const sendRunBtn = document.getElementById("sendRunBtn");
|
|
121
130
|
const queueSteerBtn = document.getElementById("queueSteerBtn");
|
|
@@ -184,7 +193,7 @@
|
|
|
184
193
|
: "full";
|
|
185
194
|
const isEditorOnlyMode = studioMode === "editor-only";
|
|
186
195
|
const isSshStudioSession = Boolean(document.body && document.body.dataset && document.body.dataset.sshSession === "1");
|
|
187
|
-
const EDITOR_ONLY_RIGHT_VIEW_ALLOWED = new Set(["editor-preview", "editor-quarto-preview", "files", "changes", "repl"]);
|
|
196
|
+
const EDITOR_ONLY_RIGHT_VIEW_ALLOWED = new Set(["editor-preview", "editor-quarto-preview", "files", "changes", "repl", "side-questions"]);
|
|
188
197
|
const RIGHT_VIEW_LABELS = {
|
|
189
198
|
markdown: "Response (Raw)",
|
|
190
199
|
preview: "Response (Preview)",
|
|
@@ -194,6 +203,7 @@
|
|
|
194
203
|
changes: "Changes",
|
|
195
204
|
files: "Files",
|
|
196
205
|
repl: "REPL",
|
|
206
|
+
"side-questions": "Side questions",
|
|
197
207
|
};
|
|
198
208
|
const RIGHT_VIEW_NUMERIC_SHORTCUTS = {
|
|
199
209
|
Digit1: "markdown",
|
|
@@ -203,6 +213,7 @@
|
|
|
203
213
|
Digit5: "changes",
|
|
204
214
|
Digit6: "files",
|
|
205
215
|
Digit7: "repl",
|
|
216
|
+
Digit8: "side-questions",
|
|
206
217
|
Numpad1: "markdown",
|
|
207
218
|
Numpad2: "preview",
|
|
208
219
|
Numpad3: "editor-preview",
|
|
@@ -210,6 +221,7 @@
|
|
|
210
221
|
Numpad5: "changes",
|
|
211
222
|
Numpad6: "files",
|
|
212
223
|
Numpad7: "repl",
|
|
224
|
+
Numpad8: "side-questions",
|
|
213
225
|
};
|
|
214
226
|
|
|
215
227
|
const navigationHelpers = globalThis.PiStudioNavigationHelpers;
|
|
@@ -238,6 +250,16 @@
|
|
|
238
250
|
if (!showMeHelpers || typeof showMeHelpers.chooseStudioShowMeFocus !== "function") {
|
|
239
251
|
throw new Error("Studio Show me helpers failed to load.");
|
|
240
252
|
}
|
|
253
|
+
const sideQuestionHelpers = globalThis.PiStudioSideQuestionHelpers;
|
|
254
|
+
if (
|
|
255
|
+
!sideQuestionHelpers
|
|
256
|
+
|| typeof sideQuestionHelpers.buildStudioSideQuestionTranscriptMarkdown !== "function"
|
|
257
|
+
|| typeof sideQuestionHelpers.chooseStudioSideQuestionFocus !== "function"
|
|
258
|
+
|| typeof sideQuestionHelpers.findStudioSideQuestionSection !== "function"
|
|
259
|
+
|| typeof sideQuestionHelpers.formatStudioSideQuestionTranscriptFilename !== "function"
|
|
260
|
+
) {
|
|
261
|
+
throw new Error("Studio side-question helpers failed to load.");
|
|
262
|
+
}
|
|
241
263
|
const studioTabStateId = navigationHelpers.ensureStudioTabStateId(window);
|
|
242
264
|
const initialQueryParams = new URLSearchParams(window.location.search || "");
|
|
243
265
|
const initialPaneFocusTarget = navigationHelpers.readPaneFocusTarget(window.location);
|
|
@@ -331,13 +353,15 @@
|
|
|
331
353
|
? "editor-preview"
|
|
332
354
|
: (raw === "editor-quarto-preview"
|
|
333
355
|
? "editor-quarto-preview"
|
|
334
|
-
: (raw === "
|
|
335
|
-
? "
|
|
336
|
-
: (raw === "
|
|
356
|
+
: (raw === "side-questions"
|
|
357
|
+
? "side-questions"
|
|
358
|
+
: (raw === "repl"
|
|
359
|
+
? "repl"
|
|
360
|
+
: (raw === "files"
|
|
337
361
|
? "files"
|
|
338
362
|
: (raw === "changes"
|
|
339
363
|
? "changes"
|
|
340
|
-
: ((raw === "trace" || raw === "thinking") ? "trace" : "markdown"))))));
|
|
364
|
+
: ((raw === "trace" || raw === "thinking") ? "trace" : "markdown")))))));
|
|
341
365
|
}
|
|
342
366
|
|
|
343
367
|
function normalizeRightViewValue(nextView) {
|
|
@@ -376,8 +400,8 @@
|
|
|
376
400
|
option.disabled = (isEditorOnlyMode && !EDITOR_ONLY_RIGHT_VIEW_ALLOWED.has(option.value)) || (isQuartoOption && !quartoRelevant);
|
|
377
401
|
});
|
|
378
402
|
rightViewSelect.title = isEditorOnlyMode
|
|
379
|
-
? "Editor-only views: Editor Preview, contextual Quarto Preview for .qmd/.md/.markdown files, Changes, Files, or
|
|
380
|
-
: "Right pane view mode. F7 cycles, including contextual Quarto Preview for file-backed .qmd, .md, and .markdown documents; Cmd/Ctrl+Alt+1–
|
|
403
|
+
? "Editor-only views: Editor Preview, contextual Quarto Preview for .qmd/.md/.markdown files, Changes, Files, REPL, or Side questions. F7 cycles; Cmd/Ctrl+Alt+3/5/6/7/8 switch directly to numbered right-pane views, and Cmd/Ctrl+Alt+Q opens Side questions."
|
|
404
|
+
: "Right pane view mode. F7 cycles, including contextual Quarto Preview for file-backed .qmd, .md, and .markdown documents; Cmd/Ctrl+Alt+1–8 switches directly between the numbered views. Cmd/Ctrl+Alt+P/E/W keep their mnemonic Preview/Editor Preview/Working shortcuts.";
|
|
381
405
|
}
|
|
382
406
|
|
|
383
407
|
function getInitialRightView(source) {
|
|
@@ -437,12 +461,49 @@
|
|
|
437
461
|
const RENDERED_PREVIEW_IMAGE_FETCH_TIMEOUT_MS = 8_000;
|
|
438
462
|
const EDITOR_TAB_TEXT = " ";
|
|
439
463
|
const QUIZ_DEFAULT_COUNT = 5;
|
|
464
|
+
const SIDE_QUESTION_THINKING_STORAGE_KEY = "piStudio.sideQuestionThinking";
|
|
465
|
+
const SIDE_QUESTION_GATHER_STORAGE_KEY = "piStudio.sideQuestionGatherScope";
|
|
466
|
+
const SIDE_QUESTION_TOOLS_STORAGE_KEY = "piStudio.sideQuestionTools";
|
|
467
|
+
const SIDE_QUESTION_TRANSCRIPT_RENDER_MAX_CHARS = 400_000;
|
|
440
468
|
const COMPLETION_CONTEXT_STORAGE_KEY = "piStudio.completionContextMode";
|
|
441
469
|
const COMPLETION_MODEL_STORAGE_KEY = "piStudio.completionModel";
|
|
442
470
|
const COMPLETION_CONTEXT_MAX_CHARS = 12000;
|
|
443
471
|
const QUIZ_SCOPES = ["editor", "selection", "file", "folder", "repo"];
|
|
444
472
|
const QUIZ_ANGLES = ["general", "scientist", "mathematician", "statistician", "developer", "reviewer"];
|
|
445
473
|
const QUIZ_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
474
|
+
let sideQuestionState = null;
|
|
475
|
+
let sideQuestionWebSearchAvailable = false;
|
|
476
|
+
let sideQuestionAvailablePiTools = [];
|
|
477
|
+
let sideQuestionPreviewRenderNonce = 0;
|
|
478
|
+
let sideQuestionContextRefreshHandle = null;
|
|
479
|
+
let sideQuestionMarkdownExportRequest = null;
|
|
480
|
+
const sideQuestionMarkdownRenderCache = new Map();
|
|
481
|
+
let sideQuestionUi = {
|
|
482
|
+
focusMode: "auto",
|
|
483
|
+
gatherScope: (() => {
|
|
484
|
+
try {
|
|
485
|
+
const value = window.localStorage && window.localStorage.getItem(SIDE_QUESTION_GATHER_STORAGE_KEY);
|
|
486
|
+
return value === "none" || value === "folder" || value === "repo" || value === "custom" ? value : "";
|
|
487
|
+
} catch { return ""; }
|
|
488
|
+
})(),
|
|
489
|
+
customPath: "",
|
|
490
|
+
includeConversation: false,
|
|
491
|
+
gitContext: false,
|
|
492
|
+
webSearch: false,
|
|
493
|
+
toolIds: (() => {
|
|
494
|
+
try {
|
|
495
|
+
const parsed = JSON.parse((window.localStorage && window.localStorage.getItem(SIDE_QUESTION_TOOLS_STORAGE_KEY)) || "[]");
|
|
496
|
+
return Array.isArray(parsed) ? [...new Set(parsed.filter((id) => typeof id === "string" && /^[a-f0-9]{24}$/i.test(id)).map((id) => id.toLowerCase()))].slice(0, 12) : [];
|
|
497
|
+
} catch { return []; }
|
|
498
|
+
})(),
|
|
499
|
+
thinking: (() => {
|
|
500
|
+
try {
|
|
501
|
+
const value = window.localStorage && window.localStorage.getItem(SIDE_QUESTION_THINKING_STORAGE_KEY);
|
|
502
|
+
return ["off", "minimal", "low", "medium", "high"].includes(value) ? value : "low";
|
|
503
|
+
} catch { return "low"; }
|
|
504
|
+
})(),
|
|
505
|
+
draft: "",
|
|
506
|
+
};
|
|
446
507
|
let quizOverlayEl = null;
|
|
447
508
|
let quizDialogEl = null;
|
|
448
509
|
let quizPreviewRenderNonce = 0;
|
|
@@ -2187,6 +2248,7 @@
|
|
|
2187
2248
|
let lineNumbersRenderRaf = null;
|
|
2188
2249
|
let annotationsEnabled = true;
|
|
2189
2250
|
const STUDIO_ZEN_MODE_STORAGE_KEY = "piStudio.zenMode";
|
|
2251
|
+
const STUDIO_HEADER_HIDDEN_STORAGE_KEY = "piStudio.headerHidden";
|
|
2190
2252
|
const studioUiRefreshEnabled = readStudioUiRefreshEnabled();
|
|
2191
2253
|
const EDITOR_FONT_SIZE_OPTIONS = [10, 11, 12, 13, 14, 15, 16, 18];
|
|
2192
2254
|
const RESPONSE_FONT_SIZE_OPTIONS = [11, 12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5, 16, 18, 20];
|
|
@@ -2239,12 +2301,17 @@
|
|
|
2239
2301
|
let fileBrowserLoadNonce = 0;
|
|
2240
2302
|
let studioUiRefreshUi = null;
|
|
2241
2303
|
let studioZenModeEnabled = readStudioZenModeEnabled();
|
|
2304
|
+
let studioHeaderHidden = readStudioHeaderHiddenEnabled();
|
|
2305
|
+
let studioHeaderRevealTimer = null;
|
|
2242
2306
|
if (studioUiRefreshEnabled && document.body) {
|
|
2243
2307
|
document.body.classList.add("studio-ui-refresh");
|
|
2244
2308
|
}
|
|
2245
2309
|
if (studioZenModeEnabled && document.body) {
|
|
2246
2310
|
document.body.classList.add("studio-zen-mode");
|
|
2247
2311
|
}
|
|
2312
|
+
if ((studioZenModeEnabled || studioHeaderHidden) && document.body) {
|
|
2313
|
+
document.body.classList.add("studio-header-hidden");
|
|
2314
|
+
}
|
|
2248
2315
|
let scratchpadText = "";
|
|
2249
2316
|
let scratchpadReturnFocusEl = null;
|
|
2250
2317
|
let scratchpadPersistTimer = null;
|
|
@@ -2308,22 +2375,129 @@
|
|
|
2308
2375
|
}
|
|
2309
2376
|
}
|
|
2310
2377
|
|
|
2311
|
-
function
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2378
|
+
function readStudioHeaderHiddenEnabled() {
|
|
2379
|
+
const normalize = (value) => String(value == null ? "" : value).trim().toLowerCase();
|
|
2380
|
+
const isTruthy = (value) => ["1", "true", "yes", "on", "hidden"].indexOf(normalize(value)) !== -1;
|
|
2381
|
+
try {
|
|
2382
|
+
const stored = window.localStorage ? window.localStorage.getItem(STUDIO_HEADER_HIDDEN_STORAGE_KEY) : null;
|
|
2383
|
+
if (stored === null) return false;
|
|
2384
|
+
return isTruthy(stored);
|
|
2385
|
+
} catch {
|
|
2386
|
+
return false;
|
|
2387
|
+
}
|
|
2317
2388
|
}
|
|
2318
2389
|
|
|
2319
|
-
function
|
|
2320
|
-
|
|
2390
|
+
function persistStudioHeaderHiddenEnabled() {
|
|
2391
|
+
try {
|
|
2392
|
+
window.localStorage && window.localStorage.setItem(STUDIO_HEADER_HIDDEN_STORAGE_KEY, studioHeaderHidden ? "1" : "0");
|
|
2393
|
+
} catch {}
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
function focusStudioChromeControl(control) {
|
|
2397
|
+
if (!control || typeof control.focus !== "function") return;
|
|
2398
|
+
window.setTimeout(() => {
|
|
2399
|
+
try {
|
|
2400
|
+
control.focus({ preventScroll: true });
|
|
2401
|
+
} catch {
|
|
2402
|
+
try { control.focus(); } catch {}
|
|
2403
|
+
}
|
|
2404
|
+
}, 0);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
function showStudioHeaderRevealControl() {
|
|
2408
|
+
if (!studioHeaderRevealEl || !studioHeaderRevealBtn) return;
|
|
2409
|
+
studioHeaderRevealEl.classList.add("is-open");
|
|
2410
|
+
if (studioHeaderRevealTimer) window.clearTimeout(studioHeaderRevealTimer);
|
|
2411
|
+
studioHeaderRevealTimer = window.setTimeout(() => {
|
|
2412
|
+
studioHeaderRevealTimer = null;
|
|
2413
|
+
studioHeaderRevealEl.classList.remove("is-open");
|
|
2414
|
+
}, 1800);
|
|
2415
|
+
focusStudioChromeControl(studioHeaderRevealBtn);
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
function syncStudioHeaderVisibilityUi() {
|
|
2419
|
+
const hidden = Boolean(studioZenModeEnabled || studioHeaderHidden);
|
|
2420
|
+
if (document.body) document.body.classList.toggle("studio-header-hidden", hidden);
|
|
2421
|
+
if (studioHeaderRevealEl) {
|
|
2422
|
+
studioHeaderRevealEl.hidden = !hidden;
|
|
2423
|
+
studioHeaderRevealEl.dataset.mode = studioZenModeEnabled ? "zen" : "header";
|
|
2424
|
+
if (!hidden) studioHeaderRevealEl.classList.remove("is-open");
|
|
2425
|
+
}
|
|
2426
|
+
if (!hidden && studioHeaderRevealTimer) {
|
|
2427
|
+
window.clearTimeout(studioHeaderRevealTimer);
|
|
2428
|
+
studioHeaderRevealTimer = null;
|
|
2429
|
+
}
|
|
2430
|
+
if (studioHeaderRevealBtn) {
|
|
2431
|
+
const label = studioZenModeEnabled ? "Exit Zen" : "Show header";
|
|
2432
|
+
studioHeaderRevealBtn.textContent = label;
|
|
2433
|
+
studioHeaderRevealBtn.setAttribute("aria-label", label);
|
|
2434
|
+
studioHeaderRevealBtn.title = studioZenModeEnabled
|
|
2435
|
+
? "Exit Zen and restore the Studio header. Shortcut: F9."
|
|
2436
|
+
: "Restore the Studio header.";
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
function syncStudioZenModeUi() {
|
|
2441
|
+
if (document.body) document.body.classList.toggle("studio-zen-mode", studioZenModeEnabled);
|
|
2442
|
+
if (zenModeBtn) {
|
|
2443
|
+
zenModeBtn.textContent = studioZenModeEnabled ? "Exit Zen" : "Zen";
|
|
2444
|
+
zenModeBtn.title = studioZenModeEnabled
|
|
2445
|
+
? "Exit Zen and restore the Studio header. Shortcut: F9."
|
|
2446
|
+
: "Hide the Studio header and secondary controls. Shortcut: F9.";
|
|
2447
|
+
zenModeBtn.setAttribute("aria-pressed", studioZenModeEnabled ? "true" : "false");
|
|
2448
|
+
}
|
|
2449
|
+
syncStudioHeaderVisibilityUi();
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
function setStudioHeaderHidden(enabled, options) {
|
|
2453
|
+
const nextHidden = Boolean(enabled);
|
|
2454
|
+
const moveFocusToReveal = Boolean(
|
|
2455
|
+
nextHidden
|
|
2456
|
+
&& (
|
|
2457
|
+
(options && options.focusReveal)
|
|
2458
|
+
|| (studioHeaderEl && document.activeElement && studioHeaderEl.contains(document.activeElement))
|
|
2459
|
+
)
|
|
2460
|
+
);
|
|
2461
|
+
studioHeaderHidden = nextHidden;
|
|
2462
|
+
persistStudioHeaderHiddenEnabled();
|
|
2463
|
+
closeStudioUiRefreshMenus();
|
|
2464
|
+
closeExportPreviewMenu();
|
|
2465
|
+
syncStudioHeaderVisibilityUi();
|
|
2466
|
+
if (moveFocusToReveal) showStudioHeaderRevealControl();
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
function setStudioZenMode(enabled, options) {
|
|
2470
|
+
const nextEnabled = Boolean(enabled);
|
|
2471
|
+
const moveFocusToReveal = Boolean(
|
|
2472
|
+
nextEnabled
|
|
2473
|
+
&& (
|
|
2474
|
+
(options && options.focusReveal)
|
|
2475
|
+
|| (studioHeaderEl && document.activeElement && studioHeaderEl.contains(document.activeElement))
|
|
2476
|
+
)
|
|
2477
|
+
);
|
|
2478
|
+
studioZenModeEnabled = nextEnabled;
|
|
2479
|
+
if (!studioZenModeEnabled) {
|
|
2480
|
+
studioHeaderHidden = false;
|
|
2481
|
+
persistStudioHeaderHiddenEnabled();
|
|
2482
|
+
}
|
|
2321
2483
|
try {
|
|
2322
2484
|
window.localStorage && window.localStorage.setItem(STUDIO_ZEN_MODE_STORAGE_KEY, studioZenModeEnabled ? "1" : "0");
|
|
2323
2485
|
} catch {}
|
|
2324
2486
|
closeStudioUiRefreshMenus();
|
|
2325
2487
|
closeExportPreviewMenu();
|
|
2326
2488
|
syncStudioZenModeUi();
|
|
2489
|
+
if (moveFocusToReveal) showStudioHeaderRevealControl();
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
function restoreStudioHeaderFromReveal() {
|
|
2493
|
+
const wasZenMode = studioZenModeEnabled;
|
|
2494
|
+
if (wasZenMode) {
|
|
2495
|
+
setStudioZenMode(false);
|
|
2496
|
+
} else {
|
|
2497
|
+
setStudioHeaderHidden(false);
|
|
2498
|
+
}
|
|
2499
|
+
focusStudioChromeControl(wasZenMode ? zenModeBtn : hideStudioHeaderBtn);
|
|
2500
|
+
setStatus(wasZenMode ? "Zen mode off. Studio header restored." : "Studio header shown.");
|
|
2327
2501
|
}
|
|
2328
2502
|
|
|
2329
2503
|
function makeStudioUiRefreshElement(tagName, className, text) {
|
|
@@ -2600,7 +2774,7 @@
|
|
|
2600
2774
|
if (!isEditorOnlyMode && critiqueBtn && lensSelect) {
|
|
2601
2775
|
const reviewButton = makeStudioUiRefreshElement("button", "studio-refresh-tool-tab studio-refresh-review-btn", "Review");
|
|
2602
2776
|
reviewMenu = makeStudioUiRefreshMenu(reviewButton, "review", "studio-refresh-review-anchor");
|
|
2603
|
-
appendStudioUiRefreshMenuSection(reviewMenu.menu, "Action", [critiqueBtn, showMeBtn, showMeResponseBtn, quizBtn]);
|
|
2777
|
+
appendStudioUiRefreshMenuSection(reviewMenu.menu, "Action", [critiqueBtn, showMeBtn, showMeResponseBtn, askAsideBtn, quizBtn]);
|
|
2604
2778
|
appendStudioUiRefreshMenuSection(reviewMenu.menu, "Setting", [lensSelect]);
|
|
2605
2779
|
}
|
|
2606
2780
|
|
|
@@ -2724,6 +2898,7 @@
|
|
|
2724
2898
|
if (reviewNotesBtn) headerToolsEl.appendChild(reviewNotesBtn);
|
|
2725
2899
|
if (outlineBtn) headerToolsEl.appendChild(outlineBtn);
|
|
2726
2900
|
if (scratchpadBtn) headerToolsEl.appendChild(scratchpadBtn);
|
|
2901
|
+
if (isEditorOnlyMode && askAsideBtn) headerToolsEl.appendChild(askAsideBtn);
|
|
2727
2902
|
if (reviewMenu) headerToolsEl.appendChild(reviewMenu.anchor);
|
|
2728
2903
|
headerTopEl.appendChild(headerToolsEl);
|
|
2729
2904
|
|
|
@@ -2924,9 +3099,9 @@
|
|
|
2924
3099
|
|
|
2925
3100
|
function getIdleStatus() {
|
|
2926
3101
|
if (isEditorOnlyMode) {
|
|
2927
|
-
return "Editor-only mode: edit, browse files, annotate, preview, save, suggest, refresh file-backed text, or send to a REPL.";
|
|
3102
|
+
return "Editor-only mode: edit, browse files, annotate, preview, save, suggest, ask a side question, refresh file-backed text, or send to a REPL.";
|
|
2928
3103
|
}
|
|
2929
|
-
return "Edit, load, or annotate text, then run, save, review, or ask
|
|
3104
|
+
return "Edit, load, or annotate text, then run, save, review, explain, or ask a side question.";
|
|
2930
3105
|
}
|
|
2931
3106
|
|
|
2932
3107
|
function normalizeTerminalPhase(phase) {
|
|
@@ -4644,6 +4819,16 @@
|
|
|
4644
4819
|
return;
|
|
4645
4820
|
}
|
|
4646
4821
|
|
|
4822
|
+
const isSideQuestionsShortcut = (key.toLowerCase() === "q" || code === "KeyQ")
|
|
4823
|
+
&& (event.metaKey || event.ctrlKey)
|
|
4824
|
+
&& event.altKey
|
|
4825
|
+
&& !event.shiftKey;
|
|
4826
|
+
if (isSideQuestionsShortcut) {
|
|
4827
|
+
event.preventDefault();
|
|
4828
|
+
switchRightPaneToView("side-questions");
|
|
4829
|
+
return;
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4647
4832
|
const isContentFocusShortcut = key === "F8" && !event.metaKey && !event.ctrlKey && !event.altKey;
|
|
4648
4833
|
if (isContentFocusShortcut) {
|
|
4649
4834
|
event.preventDefault();
|
|
@@ -4658,8 +4843,17 @@
|
|
|
4658
4843
|
const isZenModeShortcut = key === "F9" && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
|
|
4659
4844
|
if (isZenModeShortcut) {
|
|
4660
4845
|
event.preventDefault();
|
|
4661
|
-
|
|
4662
|
-
|
|
4846
|
+
const restoringHeaderOnly = Boolean(!studioZenModeEnabled && studioHeaderHidden);
|
|
4847
|
+
if (studioZenModeEnabled) {
|
|
4848
|
+
setStudioZenMode(false);
|
|
4849
|
+
} else if (studioHeaderHidden) {
|
|
4850
|
+
setStudioHeaderHidden(false);
|
|
4851
|
+
} else {
|
|
4852
|
+
setStudioZenMode(true);
|
|
4853
|
+
}
|
|
4854
|
+
setStatus(studioZenModeEnabled
|
|
4855
|
+
? "Zen mode on. Studio header hidden; press F9 to exit."
|
|
4856
|
+
: (restoringHeaderOnly ? "Studio header restored." : "Zen mode off. Studio header restored."));
|
|
4663
4857
|
return;
|
|
4664
4858
|
}
|
|
4665
4859
|
|
|
@@ -4893,6 +5087,19 @@
|
|
|
4893
5087
|
}
|
|
4894
5088
|
}
|
|
4895
5089
|
|
|
5090
|
+
function syncAskAsideButton() {
|
|
5091
|
+
if (!askAsideBtn) return;
|
|
5092
|
+
const running = Boolean(sideQuestionState && sideQuestionState.status === "running");
|
|
5093
|
+
askAsideBtn.disabled = wsState === "Disconnected";
|
|
5094
|
+
askAsideBtn.textContent = isEditorOnlyMode
|
|
5095
|
+
? "Side questions"
|
|
5096
|
+
: (running ? "Side question running…" : (sideQuestionState && sideQuestionState.threadId ? "Open side questions" : "Side question"));
|
|
5097
|
+
askAsideBtn.classList.toggle("request-active", running);
|
|
5098
|
+
askAsideBtn.title = running
|
|
5099
|
+
? "Open the independently running side question."
|
|
5100
|
+
: "Open side questions without adding them to the main Pi conversation.";
|
|
5101
|
+
}
|
|
5102
|
+
|
|
4896
5103
|
function syncTraceForSelectedHistoryItem() {
|
|
4897
5104
|
const item = getSelectedHistoryItem();
|
|
4898
5105
|
const total = Array.isArray(responseHistory) ? responseHistory.length : 0;
|
|
@@ -5096,6 +5303,18 @@
|
|
|
5096
5303
|
return;
|
|
5097
5304
|
}
|
|
5098
5305
|
|
|
5306
|
+
if (rightView === "side-questions") {
|
|
5307
|
+
if (sideQuestionState && sideQuestionState.threadId) {
|
|
5308
|
+
const count = sideQuestionState.messages.filter((entry) => entry.role === "assistant" && entry.status === "complete").length;
|
|
5309
|
+
referenceBadgeEl.textContent = "Side thread: " + (sideQuestionState.status === "running" ? "answering" : "ready")
|
|
5310
|
+
+ " · " + count + " answer" + (count === 1 ? "" : "s")
|
|
5311
|
+
+ " · outside main context";
|
|
5312
|
+
} else {
|
|
5313
|
+
referenceBadgeEl.textContent = "Side questions: no active thread · outside main context";
|
|
5314
|
+
}
|
|
5315
|
+
return;
|
|
5316
|
+
}
|
|
5317
|
+
|
|
5099
5318
|
if (rightView === "trace") {
|
|
5100
5319
|
const state = traceState || createEmptyTraceState();
|
|
5101
5320
|
const context = traceDisplayContext || {};
|
|
@@ -9009,6 +9228,10 @@
|
|
|
9009
9228
|
critiqueViewEl.addEventListener("click", handleFilesPaneClick);
|
|
9010
9229
|
critiqueViewEl.addEventListener("click", handleGitChangesPaneClick);
|
|
9011
9230
|
critiqueViewEl.addEventListener("click", handleStudioQuartoPreviewClick);
|
|
9231
|
+
critiqueViewEl.addEventListener("click", (event) => { void handleSideQuestionClick(event); });
|
|
9232
|
+
critiqueViewEl.addEventListener("input", handleSideQuestionInput);
|
|
9233
|
+
critiqueViewEl.addEventListener("keydown", handleSideQuestionKeydown);
|
|
9234
|
+
critiqueViewEl.addEventListener("change", handleSideQuestionChange);
|
|
9012
9235
|
critiqueViewEl.addEventListener("change", handleReplPaneChange);
|
|
9013
9236
|
critiqueViewEl.addEventListener("change", (event) => {
|
|
9014
9237
|
void handleFilesPaneChange(event);
|
|
@@ -9034,7 +9257,7 @@
|
|
|
9034
9257
|
|
|
9035
9258
|
function applyPendingResponseScrollReset() {
|
|
9036
9259
|
if (!pendingResponseScrollReset || !critiqueViewEl) return false;
|
|
9037
|
-
if (rightView === "editor-preview" || rightView === "editor-quarto-preview") return false;
|
|
9260
|
+
if (rightView === "editor-preview" || rightView === "editor-quarto-preview" || rightView === "side-questions") return false;
|
|
9038
9261
|
|
|
9039
9262
|
pendingResponseScrollReset = false;
|
|
9040
9263
|
let targetEl = replaceResponsePaneWithClone();
|
|
@@ -9043,7 +9266,7 @@
|
|
|
9043
9266
|
: (cb) => window.setTimeout(cb, 16);
|
|
9044
9267
|
const resetScroll = () => {
|
|
9045
9268
|
if (!targetEl || !targetEl.isConnected) return;
|
|
9046
|
-
if (rightView === "editor-preview" || rightView === "editor-quarto-preview") return;
|
|
9269
|
+
if (rightView === "editor-preview" || rightView === "editor-quarto-preview" || rightView === "side-questions") return;
|
|
9047
9270
|
targetEl.scrollTop = 0;
|
|
9048
9271
|
targetEl.scrollLeft = 0;
|
|
9049
9272
|
};
|
|
@@ -9301,8 +9524,10 @@
|
|
|
9301
9524
|
async function exportRightPanePdf(options) {
|
|
9302
9525
|
const exportOptions = options && typeof options === "object" ? options : {};
|
|
9303
9526
|
const openTarget = exportOptions.openTarget === "studio" ? "studio" : "default";
|
|
9527
|
+
const exportingSideThread = rightView === "side-questions";
|
|
9528
|
+
const sideThreadExportedAt = exportingSideThread ? new Date() : null;
|
|
9304
9529
|
let studioLaunch = null;
|
|
9305
|
-
if (uiBusy || previewExportInProgress) {
|
|
9530
|
+
if ((!exportingSideThread && uiBusy) || previewExportInProgress || sideQuestionMarkdownExportRequest) {
|
|
9306
9531
|
setStatus("Studio is busy.", "warning");
|
|
9307
9532
|
return;
|
|
9308
9533
|
}
|
|
@@ -9315,8 +9540,8 @@
|
|
|
9315
9540
|
|
|
9316
9541
|
const exportingReplJournal = rightView === "repl";
|
|
9317
9542
|
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
9318
|
-
if (!rightPaneShowsPreview && !exportingReplJournal) {
|
|
9319
|
-
setStatus("Switch right pane to Response (Preview), Editor (Preview), or
|
|
9543
|
+
if (!rightPaneShowsPreview && !exportingReplJournal && !exportingSideThread) {
|
|
9544
|
+
setStatus("Switch right pane to Response (Preview), Editor (Preview), REPL, or Side questions to export PDF.", "warning");
|
|
9320
9545
|
return;
|
|
9321
9546
|
}
|
|
9322
9547
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
@@ -9325,7 +9550,7 @@
|
|
|
9325
9550
|
return;
|
|
9326
9551
|
}
|
|
9327
9552
|
|
|
9328
|
-
const htmlArtifactSource = exportingReplJournal ? "" : getRightPaneHtmlArtifactSource();
|
|
9553
|
+
const htmlArtifactSource = exportingReplJournal || exportingSideThread ? "" : getRightPaneHtmlArtifactSource();
|
|
9329
9554
|
if (htmlArtifactSource) {
|
|
9330
9555
|
setStatus("PDF export does not support interactive HTML previews yet. Export as HTML or use the browser print dialog inside the preview.", "warning");
|
|
9331
9556
|
return;
|
|
@@ -9333,24 +9558,34 @@
|
|
|
9333
9558
|
|
|
9334
9559
|
const markdown = exportingReplJournal
|
|
9335
9560
|
? buildReplJournalMarkdown(replJournalExportEntries)
|
|
9336
|
-
: (
|
|
9337
|
-
?
|
|
9338
|
-
:
|
|
9561
|
+
: (exportingSideThread
|
|
9562
|
+
? buildCurrentSideQuestionTranscriptMarkdown(sideThreadExportedAt)
|
|
9563
|
+
: (rightView === "editor-preview"
|
|
9564
|
+
? prepareEditorTextForPdfExport(sourceTextEl.value)
|
|
9565
|
+
: prepareEditorTextForPreview(latestResponseMarkdown)));
|
|
9339
9566
|
if (!markdown || !markdown.trim()) {
|
|
9340
9567
|
setStatus("Nothing to export yet.", "warning");
|
|
9341
9568
|
return;
|
|
9342
9569
|
}
|
|
9570
|
+
if (exportingSideThread && markdown.length > SIDE_QUESTION_TRANSCRIPT_RENDER_MAX_CHARS) {
|
|
9571
|
+
setStatus("This side-thread transcript is too large for PDF export. Save or copy the Markdown instead.", "warning");
|
|
9572
|
+
return;
|
|
9573
|
+
}
|
|
9343
9574
|
|
|
9344
9575
|
const effectivePath = getEffectiveSavePath();
|
|
9345
|
-
const sourcePath = exportingReplJournal ? "" : (effectivePath || sourceState.path || "");
|
|
9576
|
+
const sourcePath = exportingReplJournal || exportingSideThread ? "" : (effectivePath || sourceState.path || "");
|
|
9346
9577
|
const resourceDir = (!sourcePath && resourceDirInput) ? getCurrentResourceDirValue() : "";
|
|
9347
9578
|
const isEditorPreview = rightView === "editor-preview";
|
|
9348
9579
|
const editorIsDelimitedPreview = isEditorPreview && Boolean(getDelimitedTextPreviewConfig(editorLanguage || ""));
|
|
9349
9580
|
const editorPdfLanguage = isEditorPreview ? (editorIsDelimitedPreview ? "markdown" : normalizeFenceLanguage(editorLanguage || "")) : "";
|
|
9350
|
-
const isLatex =
|
|
9351
|
-
?
|
|
9352
|
-
:
|
|
9353
|
-
|
|
9581
|
+
const isLatex = exportingSideThread
|
|
9582
|
+
? false
|
|
9583
|
+
: (isEditorPreview
|
|
9584
|
+
? editorPdfLanguage === "latex"
|
|
9585
|
+
: /\\documentclass\b|\\begin\{document\}/.test(markdown));
|
|
9586
|
+
let filenameHint = exportingSideThread
|
|
9587
|
+
? getSideQuestionTranscriptFilename(sideThreadExportedAt).replace(/\.md$/i, ".pdf")
|
|
9588
|
+
: (exportingReplJournal ? "repl-studio.pdf" : (isEditorPreview ? "studio-editor-preview.pdf" : ("studio-response-" + formatStudioExportTimestamp() + ".studio.pdf")));
|
|
9354
9589
|
if (sourcePath) {
|
|
9355
9590
|
const baseName = sourcePath.split(/[\\/]/).pop() || "studio";
|
|
9356
9591
|
const stem = baseName.replace(/\.[^.]+$/, "") || "studio";
|
|
@@ -9367,7 +9602,9 @@
|
|
|
9367
9602
|
}
|
|
9368
9603
|
previewExportInProgress = true;
|
|
9369
9604
|
updateResultActionButtons();
|
|
9370
|
-
setStatus(
|
|
9605
|
+
setStatus(exportingSideThread
|
|
9606
|
+
? (openTarget === "studio" ? "Exporting side thread as PDF for Studio…" : "Exporting side thread as PDF…")
|
|
9607
|
+
: (openTarget === "studio" ? "Exporting PDF for Studio…" : "Exporting PDF…"), "warning");
|
|
9371
9608
|
|
|
9372
9609
|
try {
|
|
9373
9610
|
const response = await fetchWithTimeout("/export-pdf?token=" + encodeURIComponent(token), {
|
|
@@ -9532,8 +9769,10 @@
|
|
|
9532
9769
|
async function exportRightPaneHtml(options) {
|
|
9533
9770
|
const exportOptions = options && typeof options === "object" ? options : {};
|
|
9534
9771
|
const openTarget = exportOptions.openTarget === "studio" ? "studio" : "browser";
|
|
9772
|
+
const exportingSideThread = rightView === "side-questions";
|
|
9773
|
+
const sideThreadExportedAt = exportingSideThread ? new Date() : null;
|
|
9535
9774
|
let studioLaunch = null;
|
|
9536
|
-
if (uiBusy || previewExportInProgress) {
|
|
9775
|
+
if ((!exportingSideThread && uiBusy) || previewExportInProgress || sideQuestionMarkdownExportRequest) {
|
|
9537
9776
|
setStatus("Studio is busy.", "warning");
|
|
9538
9777
|
return;
|
|
9539
9778
|
}
|
|
@@ -9546,8 +9785,8 @@
|
|
|
9546
9785
|
|
|
9547
9786
|
const exportingReplJournal = rightView === "repl";
|
|
9548
9787
|
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
9549
|
-
if (!rightPaneShowsPreview && !exportingReplJournal) {
|
|
9550
|
-
setStatus("Switch right pane to Response (Preview), Editor (Preview), or
|
|
9788
|
+
if (!rightPaneShowsPreview && !exportingReplJournal && !exportingSideThread) {
|
|
9789
|
+
setStatus("Switch right pane to Response (Preview), Editor (Preview), REPL, or Side questions to export HTML.", "warning");
|
|
9551
9790
|
return;
|
|
9552
9791
|
}
|
|
9553
9792
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
@@ -9556,26 +9795,36 @@
|
|
|
9556
9795
|
return;
|
|
9557
9796
|
}
|
|
9558
9797
|
|
|
9559
|
-
const htmlArtifactSource = exportingReplJournal ? "" : getRightPaneHtmlArtifactSource();
|
|
9560
|
-
const markdown = exportingReplJournal
|
|
9561
|
-
?
|
|
9562
|
-
:
|
|
9798
|
+
const htmlArtifactSource = exportingReplJournal || exportingSideThread ? "" : getRightPaneHtmlArtifactSource();
|
|
9799
|
+
const markdown = exportingReplJournal
|
|
9800
|
+
? buildReplJournalMarkdown(replJournalExportEntries)
|
|
9801
|
+
: (exportingSideThread
|
|
9802
|
+
? buildCurrentSideQuestionTranscriptMarkdown(sideThreadExportedAt)
|
|
9803
|
+
: (htmlArtifactSource || (rightView === "editor-preview"
|
|
9804
|
+
? prepareEditorTextForHtmlExport(sourceTextEl.value)
|
|
9805
|
+
: prepareEditorTextForPreview(latestResponseMarkdown))));
|
|
9563
9806
|
if (!markdown || !markdown.trim()) {
|
|
9564
9807
|
setStatus("Nothing to export yet.", "warning");
|
|
9565
9808
|
return;
|
|
9566
9809
|
}
|
|
9810
|
+
if (exportingSideThread && markdown.length > SIDE_QUESTION_TRANSCRIPT_RENDER_MAX_CHARS) {
|
|
9811
|
+
setStatus("This side-thread transcript is too large for HTML export. Save or copy the Markdown instead.", "warning");
|
|
9812
|
+
return;
|
|
9813
|
+
}
|
|
9567
9814
|
|
|
9568
9815
|
const effectivePath = getEffectiveSavePath();
|
|
9569
|
-
const sourcePath = exportingReplJournal ? "" : (effectivePath || sourceState.path || "");
|
|
9816
|
+
const sourcePath = exportingReplJournal || exportingSideThread ? "" : (effectivePath || sourceState.path || "");
|
|
9570
9817
|
const resourceDir = (!sourcePath && resourceDirInput) ? getCurrentResourceDirValue() : "";
|
|
9571
9818
|
const isEditorPreview = rightView === "editor-preview";
|
|
9572
9819
|
const editorIsDelimitedPreview = isEditorPreview && Boolean(getDelimitedTextPreviewConfig(editorLanguage || ""));
|
|
9573
9820
|
const editorHtmlLanguage = htmlArtifactSource ? "html" : (isEditorPreview ? (editorIsDelimitedPreview ? "markdown" : normalizeFenceLanguage(editorLanguage || "")) : "");
|
|
9574
|
-
const isLatex = htmlArtifactSource ? false : (isEditorPreview
|
|
9821
|
+
const isLatex = exportingSideThread ? false : (htmlArtifactSource ? false : (isEditorPreview
|
|
9575
9822
|
? editorHtmlLanguage === "latex"
|
|
9576
|
-
: /\\documentclass\b|\\begin\{document\}/.test(markdown));
|
|
9577
|
-
let filenameHint =
|
|
9578
|
-
|
|
9823
|
+
: /\\documentclass\b|\\begin\{document\}/.test(markdown)));
|
|
9824
|
+
let filenameHint = exportingSideThread
|
|
9825
|
+
? getSideQuestionTranscriptFilename(sideThreadExportedAt).replace(/\.md$/i, ".html")
|
|
9826
|
+
: (exportingReplJournal ? "repl-studio.html" : (isEditorPreview ? "studio-editor-preview.html" : ("studio-response-" + formatStudioExportTimestamp() + ".studio.html")));
|
|
9827
|
+
let titleHint = exportingSideThread ? "Pi Studio side questions" : (exportingReplJournal ? "Studio REPL Record" : (isEditorPreview ? "Studio editor preview" : "Studio response preview"));
|
|
9579
9828
|
if (sourcePath) {
|
|
9580
9829
|
const baseName = sourcePath.split(/[\\/]/).pop() || "studio";
|
|
9581
9830
|
const stem = baseName.replace(/\.[^.]+$/, "") || "studio";
|
|
@@ -9593,7 +9842,9 @@
|
|
|
9593
9842
|
}
|
|
9594
9843
|
previewExportInProgress = true;
|
|
9595
9844
|
updateResultActionButtons();
|
|
9596
|
-
setStatus(
|
|
9845
|
+
setStatus(exportingSideThread
|
|
9846
|
+
? (openTarget === "studio" ? "Exporting side thread as HTML for Studio…" : "Exporting side thread as HTML…")
|
|
9847
|
+
: (openTarget === "studio" ? "Exporting HTML for Studio…" : "Exporting HTML…"), "warning");
|
|
9597
9848
|
|
|
9598
9849
|
try {
|
|
9599
9850
|
const response = await fetchWithTimeout("/export-html?token=" + encodeURIComponent(token), {
|
|
@@ -9768,6 +10019,15 @@
|
|
|
9768
10019
|
|
|
9769
10020
|
function exportRightPaneFormat(format) {
|
|
9770
10021
|
closeExportPreviewMenu();
|
|
10022
|
+
if (format === "side-markdown-save") {
|
|
10023
|
+
return saveSideQuestionTranscriptMarkdown();
|
|
10024
|
+
}
|
|
10025
|
+
if (format === "side-markdown-copy") {
|
|
10026
|
+
return copySideQuestionTranscriptMarkdown();
|
|
10027
|
+
}
|
|
10028
|
+
if (format === "side-markdown-editor") {
|
|
10029
|
+
return openSideQuestionTranscriptInEditor();
|
|
10030
|
+
}
|
|
9771
10031
|
if (format === "html-studio") {
|
|
9772
10032
|
return exportRightPaneHtml({ openTarget: "studio" });
|
|
9773
10033
|
}
|
|
@@ -11647,10 +11907,650 @@
|
|
|
11647
11907
|
}
|
|
11648
11908
|
}
|
|
11649
11909
|
|
|
11910
|
+
function isSideQuestionConnectionReady() {
|
|
11911
|
+
return Boolean(ws && ws.readyState === WebSocket.OPEN);
|
|
11912
|
+
}
|
|
11913
|
+
|
|
11914
|
+
function getSideQuestionSelectedResponseText() {
|
|
11915
|
+
const selected = getSelectedHistoryItem();
|
|
11916
|
+
return selected && typeof selected.markdown === "string" ? selected.markdown : latestResponseMarkdown;
|
|
11917
|
+
}
|
|
11918
|
+
|
|
11919
|
+
function getSideQuestionGatherScope() {
|
|
11920
|
+
if (sideQuestionUi.gatherScope) return sideQuestionUi.gatherScope;
|
|
11921
|
+
return sideQuestionHelpers.getDefaultStudioSideQuestionGatherScope({
|
|
11922
|
+
sourcePath: getEffectiveSavePath() || sourceState.path || "",
|
|
11923
|
+
resourceDir: getCurrentResourceDirValue(),
|
|
11924
|
+
});
|
|
11925
|
+
}
|
|
11926
|
+
|
|
11927
|
+
function getSideQuestionFocus() {
|
|
11928
|
+
return sideQuestionHelpers.chooseStudioSideQuestionFocus({
|
|
11929
|
+
mode: sideQuestionUi.focusMode,
|
|
11930
|
+
editorText: sourceTextEl.value || "",
|
|
11931
|
+
responseText: getSideQuestionSelectedResponseText(),
|
|
11932
|
+
selectionStart: sourceTextEl.selectionStart,
|
|
11933
|
+
selectionEnd: sourceTextEl.selectionEnd,
|
|
11934
|
+
language: editorLanguage,
|
|
11935
|
+
});
|
|
11936
|
+
}
|
|
11937
|
+
|
|
11938
|
+
function persistSideQuestionToolSelection() {
|
|
11939
|
+
try {
|
|
11940
|
+
if (window.localStorage) window.localStorage.setItem(SIDE_QUESTION_TOOLS_STORAGE_KEY, JSON.stringify(sideQuestionUi.toolIds.slice(0, 12)));
|
|
11941
|
+
} catch {}
|
|
11942
|
+
}
|
|
11943
|
+
|
|
11944
|
+
function applySideQuestionToolCatalog(value) {
|
|
11945
|
+
const seen = new Set();
|
|
11946
|
+
sideQuestionAvailablePiTools = (Array.isArray(value) ? value : []).flatMap((entry) => {
|
|
11947
|
+
if (!entry || typeof entry !== "object" || typeof entry.id !== "string" || typeof entry.name !== "string") return [];
|
|
11948
|
+
const id = entry.id.trim().toLowerCase();
|
|
11949
|
+
const name = entry.name.trim();
|
|
11950
|
+
if (!/^[a-f0-9]{24}$/.test(id) || !name || name.length > 200 || seen.has(id)) return [];
|
|
11951
|
+
seen.add(id);
|
|
11952
|
+
return [{
|
|
11953
|
+
id,
|
|
11954
|
+
name,
|
|
11955
|
+
description: typeof entry.description === "string" ? entry.description.trim().slice(0, 1000) : "",
|
|
11956
|
+
source: typeof entry.source === "string" ? entry.source.trim().slice(0, 500) : "Extension tool",
|
|
11957
|
+
gateway: entry.gateway === true,
|
|
11958
|
+
}];
|
|
11959
|
+
}).slice(0, 200);
|
|
11960
|
+
const available = new Set(sideQuestionAvailablePiTools.map((tool) => tool.id));
|
|
11961
|
+
const nextSelection = sideQuestionUi.toolIds.filter((id) => available.has(id)).slice(0, 12);
|
|
11962
|
+
if (nextSelection.length !== sideQuestionUi.toolIds.length || nextSelection.some((id, index) => id !== sideQuestionUi.toolIds[index])) {
|
|
11963
|
+
sideQuestionUi.toolIds = nextSelection;
|
|
11964
|
+
persistSideQuestionToolSelection();
|
|
11965
|
+
}
|
|
11966
|
+
}
|
|
11967
|
+
|
|
11968
|
+
function normalizeSideQuestionState(value) {
|
|
11969
|
+
const state = value && typeof value === "object" ? value : {};
|
|
11970
|
+
const messages = Array.isArray(state.messages) ? state.messages.map((entry) => ({
|
|
11971
|
+
id: typeof entry.id === "string" ? entry.id : makeRequestId(),
|
|
11972
|
+
role: entry.role === "assistant" ? "assistant" : "user",
|
|
11973
|
+
text: typeof entry.text === "string" ? entry.text : "",
|
|
11974
|
+
createdAt: typeof entry.createdAt === "number" ? entry.createdAt : Date.now(),
|
|
11975
|
+
status: entry.status === "streaming" || entry.status === "error" ? entry.status : "complete",
|
|
11976
|
+
})).slice(-24) : [];
|
|
11977
|
+
const activity = Array.isArray(state.activity) ? state.activity.map((entry) => ({
|
|
11978
|
+
id: typeof entry.id === "string" ? entry.id : makeRequestId(),
|
|
11979
|
+
toolName: typeof entry.toolName === "string" ? entry.toolName : "tool",
|
|
11980
|
+
label: typeof entry.label === "string" ? entry.label : "Gathering context",
|
|
11981
|
+
status: entry.status === "running" || entry.status === "error" ? entry.status : "complete",
|
|
11982
|
+
})).slice(-40) : [];
|
|
11983
|
+
const context = state.context && typeof state.context === "object" ? {
|
|
11984
|
+
focusKind: String(state.context.focusKind || "editor"),
|
|
11985
|
+
focusLabel: String(state.context.focusLabel || "Studio editor context"),
|
|
11986
|
+
gatherScope: String(state.context.gatherScope || "none"),
|
|
11987
|
+
contextRoot: String(state.context.contextRoot || ""),
|
|
11988
|
+
includeConversation: state.context.includeConversation === true,
|
|
11989
|
+
gitContextRequested: state.context.gitContextRequested === true,
|
|
11990
|
+
gitSnapshot: state.context.gitSnapshot && typeof state.context.gitSnapshot === "object" ? {
|
|
11991
|
+
capturedAt: Number.isFinite(state.context.gitSnapshot.capturedAt) ? state.context.gitSnapshot.capturedAt : null,
|
|
11992
|
+
branch: String(state.context.gitSnapshot.branch || ""),
|
|
11993
|
+
head: String(state.context.gitSnapshot.head || ""),
|
|
11994
|
+
changeCount: Number.isFinite(state.context.gitSnapshot.changeCount) ? Math.max(0, Math.floor(state.context.gitSnapshot.changeCount)) : 0,
|
|
11995
|
+
recentCommitCount: Number.isFinite(state.context.gitSnapshot.recentCommitCount) ? Math.max(0, Math.floor(state.context.gitSnapshot.recentCommitCount)) : 0,
|
|
11996
|
+
truncated: state.context.gitSnapshot.truncated === true,
|
|
11997
|
+
} : null,
|
|
11998
|
+
webSearchRequested: state.context.webSearchRequested === true,
|
|
11999
|
+
webSearchAvailable: state.context.webSearchAvailable === true,
|
|
12000
|
+
tools: Array.isArray(state.context.tools) ? state.context.tools.flatMap((tool) => {
|
|
12001
|
+
if (!tool || typeof tool !== "object" || typeof tool.name !== "string") return [];
|
|
12002
|
+
return [{
|
|
12003
|
+
id: typeof tool.id === "string" ? tool.id : "",
|
|
12004
|
+
name: tool.name,
|
|
12005
|
+
description: typeof tool.description === "string" ? tool.description : "",
|
|
12006
|
+
source: typeof tool.source === "string" ? tool.source : "Extension tool",
|
|
12007
|
+
gateway: tool.gateway === true,
|
|
12008
|
+
}];
|
|
12009
|
+
}).slice(0, 12) : [],
|
|
12010
|
+
} : null;
|
|
12011
|
+
return {
|
|
12012
|
+
threadId: typeof state.threadId === "string" && state.threadId ? state.threadId : null,
|
|
12013
|
+
status: state.status === "running" || state.status === "error" ? state.status : "idle",
|
|
12014
|
+
requestId: typeof state.requestId === "string" ? state.requestId : null,
|
|
12015
|
+
createdAt: Number.isFinite(state.createdAt) ? state.createdAt : null,
|
|
12016
|
+
updatedAt: Number.isFinite(state.updatedAt) ? state.updatedAt : null,
|
|
12017
|
+
context,
|
|
12018
|
+
modelLabel: String(state.modelLabel || ""),
|
|
12019
|
+
thinking: String(state.thinking || "low"),
|
|
12020
|
+
messages,
|
|
12021
|
+
activity,
|
|
12022
|
+
error: String(state.error || ""),
|
|
12023
|
+
};
|
|
12024
|
+
}
|
|
12025
|
+
|
|
12026
|
+
function getLatestCompletedSideQuestionAnswer() {
|
|
12027
|
+
if (!sideQuestionState || !Array.isArray(sideQuestionState.messages)) return null;
|
|
12028
|
+
return [...sideQuestionState.messages].reverse().find((entry) => entry.role === "assistant" && entry.status === "complete" && entry.text.trim()) || null;
|
|
12029
|
+
}
|
|
12030
|
+
|
|
12031
|
+
function canExportSideQuestionTranscript() {
|
|
12032
|
+
return Boolean(
|
|
12033
|
+
sideQuestionState
|
|
12034
|
+
&& sideQuestionState.threadId
|
|
12035
|
+
&& sideQuestionState.status !== "running"
|
|
12036
|
+
&& Array.isArray(sideQuestionState.messages)
|
|
12037
|
+
&& sideQuestionState.messages.length > 0
|
|
12038
|
+
);
|
|
12039
|
+
}
|
|
12040
|
+
|
|
12041
|
+
function buildCurrentSideQuestionTranscriptMarkdown(exportedAt) {
|
|
12042
|
+
if (!canExportSideQuestionTranscript()) return "";
|
|
12043
|
+
return sideQuestionHelpers.buildStudioSideQuestionTranscriptMarkdown(sideQuestionState, {
|
|
12044
|
+
exportedAt: exportedAt instanceof Date ? exportedAt : new Date(),
|
|
12045
|
+
});
|
|
12046
|
+
}
|
|
12047
|
+
|
|
12048
|
+
function getSideQuestionTranscriptFilename(date) {
|
|
12049
|
+
return sideQuestionHelpers.formatStudioSideQuestionTranscriptFilename(date instanceof Date ? date : new Date());
|
|
12050
|
+
}
|
|
12051
|
+
|
|
12052
|
+
function getSideQuestionTranscriptSuggestedPath(date) {
|
|
12053
|
+
const filename = getSideQuestionTranscriptFilename(date);
|
|
12054
|
+
const contextRoot = sideQuestionState && sideQuestionState.context ? String(sideQuestionState.context.contextRoot || "") : "";
|
|
12055
|
+
const directory = getCurrentResourceDirValue() || contextRoot || ".";
|
|
12056
|
+
return directory.replace(/[\\/]$/, "") + "/" + filename;
|
|
12057
|
+
}
|
|
12058
|
+
|
|
12059
|
+
function getSideQuestionEditorLineRange(focus) {
|
|
12060
|
+
if (!focus || !Number.isFinite(focus.start) || !Number.isFinite(focus.end)) return "";
|
|
12061
|
+
const source = sourceTextEl.value || "";
|
|
12062
|
+
const start = Math.max(0, Math.min(source.length, Math.floor(focus.start)));
|
|
12063
|
+
const end = Math.max(start, Math.min(source.length, Math.floor(focus.end)));
|
|
12064
|
+
const firstLine = source.slice(0, start).split("\n").length;
|
|
12065
|
+
const lastOffset = end > start ? end - 1 : start;
|
|
12066
|
+
const lastLine = source.slice(0, lastOffset).split("\n").length;
|
|
12067
|
+
return firstLine === lastLine ? "line " + firstLine : "lines " + firstLine + "–" + lastLine;
|
|
12068
|
+
}
|
|
12069
|
+
|
|
12070
|
+
function getSideQuestionContextSummary() {
|
|
12071
|
+
const focus = getSideQuestionFocus();
|
|
12072
|
+
const scope = getSideQuestionGatherScope();
|
|
12073
|
+
const sourcePath = getEffectiveSavePath() || sourceState.path || "";
|
|
12074
|
+
const resourceDir = getCurrentResourceDirValue();
|
|
12075
|
+
const rootHint = scope === "custom"
|
|
12076
|
+
? sideQuestionUi.customPath
|
|
12077
|
+
: (scope === "repo"
|
|
12078
|
+
? "Current repository"
|
|
12079
|
+
: (sourcePath ? dirnameForDisplayPath(sourcePath) : (resourceDir || "current Pi working directory")));
|
|
12080
|
+
const lineRange = getSideQuestionEditorLineRange(focus);
|
|
12081
|
+
const attachment = focus.focusKind === "none"
|
|
12082
|
+
? focus.focusLabel
|
|
12083
|
+
: focus.focusLabel + (lineRange ? " · " + lineRange : "") + " · " + String(focus.focusText.length).toLocaleString("en-US") + " chars";
|
|
12084
|
+
return {
|
|
12085
|
+
focus,
|
|
12086
|
+
scope,
|
|
12087
|
+
rootHint,
|
|
12088
|
+
sourcePath,
|
|
12089
|
+
resourceDir,
|
|
12090
|
+
attachmentText: attachment,
|
|
12091
|
+
relatedFilesText: scope === "none" ? "None" : (rootHint || scope) + " · read only as needed",
|
|
12092
|
+
gitContextText: scope === "repo" && sideQuestionUi.gitContext
|
|
12093
|
+
? "Status, staged and unstaged changes, and up to 20 recent commits · read only · frozen when thread starts"
|
|
12094
|
+
: "",
|
|
12095
|
+
};
|
|
12096
|
+
}
|
|
12097
|
+
|
|
12098
|
+
function buildSideQuestionContextPayload() {
|
|
12099
|
+
const summary = getSideQuestionContextSummary();
|
|
12100
|
+
return {
|
|
12101
|
+
focusKind: summary.focus.focusKind,
|
|
12102
|
+
focusLabel: summary.focus.focusLabel,
|
|
12103
|
+
focusText: summary.focus.focusText,
|
|
12104
|
+
sourcePath: summary.sourcePath || undefined,
|
|
12105
|
+
resourceDir: summary.resourceDir || undefined,
|
|
12106
|
+
gatherScope: summary.scope,
|
|
12107
|
+
contextPath: summary.scope === "custom" ? sideQuestionUi.customPath.trim() : undefined,
|
|
12108
|
+
includeConversation: sideQuestionUi.includeConversation,
|
|
12109
|
+
gitContext: summary.scope === "repo" && sideQuestionUi.gitContext,
|
|
12110
|
+
webSearch: sideQuestionUi.webSearch && sideQuestionWebSearchAvailable,
|
|
12111
|
+
toolIds: sideQuestionUi.toolIds.slice(0, 12),
|
|
12112
|
+
thinking: sideQuestionUi.thinking,
|
|
12113
|
+
};
|
|
12114
|
+
}
|
|
12115
|
+
|
|
12116
|
+
async function renderSideQuestionMarkdownToHtml(markdown) {
|
|
12117
|
+
const source = String(markdown || "");
|
|
12118
|
+
if (sideQuestionMarkdownRenderCache.has(source)) return sideQuestionMarkdownRenderCache.get(source);
|
|
12119
|
+
const renderedHtml = await renderMarkdownWithPandoc(source, { includeEditorLanguage: false });
|
|
12120
|
+
const sanitized = sanitizeRenderedHtml(renderedHtml, source, { stripMarkdownHtmlComments: true });
|
|
12121
|
+
sideQuestionMarkdownRenderCache.set(source, sanitized);
|
|
12122
|
+
while (sideQuestionMarkdownRenderCache.size > 60) {
|
|
12123
|
+
const firstKey = sideQuestionMarkdownRenderCache.keys().next().value;
|
|
12124
|
+
if (!firstKey) break;
|
|
12125
|
+
sideQuestionMarkdownRenderCache.delete(firstKey);
|
|
12126
|
+
}
|
|
12127
|
+
return sanitized;
|
|
12128
|
+
}
|
|
12129
|
+
|
|
12130
|
+
async function renderSideQuestionMarkdownFields(nonce) {
|
|
12131
|
+
if (!critiqueViewEl || rightView !== "side-questions") return;
|
|
12132
|
+
const targets = Array.from(critiqueViewEl.querySelectorAll("[data-side-question-markdown]")).filter((target) => target instanceof HTMLElement);
|
|
12133
|
+
for (const target of targets) {
|
|
12134
|
+
const markdown = target.getAttribute("data-side-question-markdown") || "";
|
|
12135
|
+
if (!markdown.trim()) continue;
|
|
12136
|
+
try {
|
|
12137
|
+
const html = await renderSideQuestionMarkdownToHtml(markdown);
|
|
12138
|
+
if (nonce !== sideQuestionPreviewRenderNonce || rightView !== "side-questions" || !critiqueViewEl.contains(target)) return;
|
|
12139
|
+
target.innerHTML = html;
|
|
12140
|
+
await renderAnnotationMathInElement(target);
|
|
12141
|
+
await renderMermaidInElement(target);
|
|
12142
|
+
await renderMathFallbackInElement(target);
|
|
12143
|
+
decorateCopyablePreviewBlocks(target);
|
|
12144
|
+
} catch (error) {
|
|
12145
|
+
console.error("Side-question markdown preview failed:", error);
|
|
12146
|
+
target.classList.add("side-question-markdown-failed");
|
|
12147
|
+
}
|
|
12148
|
+
}
|
|
12149
|
+
}
|
|
12150
|
+
|
|
12151
|
+
function sideQuestionSelectOptions(values, current) {
|
|
12152
|
+
return values.map(([value, label]) => "<option value='" + escapeHtml(value) + "'" + (value === current ? " selected" : "") + ">" + escapeHtml(label) + "</option>").join("");
|
|
12153
|
+
}
|
|
12154
|
+
|
|
12155
|
+
function renderSideQuestionPiToolPicker() {
|
|
12156
|
+
const selected = new Set(sideQuestionUi.toolIds);
|
|
12157
|
+
const selectedCount = sideQuestionUi.toolIds.length;
|
|
12158
|
+
if (!sideQuestionAvailablePiTools.length) {
|
|
12159
|
+
return "<div class='side-question-tool-empty'><strong>Additional Pi tools</strong><span>No eligible extension tools are currently available.</span></div>";
|
|
12160
|
+
}
|
|
12161
|
+
const groups = new Map();
|
|
12162
|
+
for (const tool of sideQuestionAvailablePiTools) {
|
|
12163
|
+
if (!groups.has(tool.source)) groups.set(tool.source, []);
|
|
12164
|
+
groups.get(tool.source).push(tool);
|
|
12165
|
+
}
|
|
12166
|
+
const groupHtml = [...groups.entries()].map(([source, tools]) => {
|
|
12167
|
+
const toolHtml = tools.map((tool) => {
|
|
12168
|
+
const description = tool.description || "No description supplied by this extension.";
|
|
12169
|
+
return "<label class='side-question-tool-option' title='" + escapeHtml(description) + "'>"
|
|
12170
|
+
+ "<input type='checkbox' data-side-question-tool='" + escapeHtml(tool.id) + "' data-side-question-tool-name='" + escapeHtml(tool.name) + "'" + (selected.has(tool.id) ? " checked" : "") + ">"
|
|
12171
|
+
+ "<span><code>" + escapeHtml(tool.name) + "</code><small>" + escapeHtml(description) + "</small></span>"
|
|
12172
|
+
+ (tool.gateway ? "<em>gateway</em>" : "")
|
|
12173
|
+
+ "</label>";
|
|
12174
|
+
}).join("");
|
|
12175
|
+
return "<section class='side-question-tool-group'><h3>" + escapeHtml(source) + "</h3>" + toolHtml + "</section>";
|
|
12176
|
+
}).join("");
|
|
12177
|
+
return "<details class='side-question-tool-picker'" + (selectedCount ? " open" : "") + ">"
|
|
12178
|
+
+ "<summary>Additional Pi tools · " + selectedCount + " selected</summary>"
|
|
12179
|
+
+ "<p>Selecting a tool loads its owning extension into the isolated side runtime. Selection is remembered, then frozen; a gateway may expose further configured services.</p>"
|
|
12180
|
+
+ "<div class='side-question-tool-groups'>" + groupHtml + "</div>"
|
|
12181
|
+
+ "</details>";
|
|
12182
|
+
}
|
|
12183
|
+
|
|
12184
|
+
function renderSideQuestionSetup() {
|
|
12185
|
+
const summary = getSideQuestionContextSummary();
|
|
12186
|
+
const scope = summary.scope;
|
|
12187
|
+
const webDisabled = !sideQuestionWebSearchAvailable;
|
|
12188
|
+
return "<div class='side-question-empty'>"
|
|
12189
|
+
+ "<div class='side-question-intro'><h2>Side question</h2><p>Ask something without adding it to the main Pi conversation. Choose the starting text and whether Studio may look through related files.</p></div>"
|
|
12190
|
+
+ "<div class='side-question-context-grid'>"
|
|
12191
|
+
+ "<label>Starting text<select data-side-question-field='focusMode' aria-describedby='sideQuestionContextRule'>" + sideQuestionSelectOptions([
|
|
12192
|
+
["auto", "Automatic"], ["selection", "Editor selection only"], ["section", "Heading block or nearby text at cursor"], ["editor", "Whole editor document"], ["response", "Displayed response"], ["none", "No starting text"],
|
|
12193
|
+
], sideQuestionUi.focusMode) + "</select></label>"
|
|
12194
|
+
+ "<label>Also use files from<select data-side-question-field='gatherScope'>" + sideQuestionSelectOptions([
|
|
12195
|
+
["none", "No other files"], ["folder", "Same folder as document"], ["repo", "Repository"], ["custom", "Choose a folder"],
|
|
12196
|
+
], scope) + "</select></label>"
|
|
12197
|
+
+ "<label>Thinking<select data-side-question-field='thinking'>" + sideQuestionSelectOptions([
|
|
12198
|
+
["off", "Off"], ["minimal", "Minimal"], ["low", "Low"], ["medium", "Medium"], ["high", "High"],
|
|
12199
|
+
], sideQuestionUi.thinking) + "</select></label>"
|
|
12200
|
+
+ "</div>"
|
|
12201
|
+
+ "<details class='side-question-context-rule'><summary id='sideQuestionContextRule'>Automatic: selection → heading block at cursor → nearby text</summary><p>A heading block starts at the nearest Markdown/LaTeX heading above the cursor and ends before the next heading of the same or higher level. With no heading, Studio uses the surrounding text block or a nearby excerpt.</p></details>"
|
|
12202
|
+
+ (scope === "custom" ? "<label class='side-question-path-label'>Folder path<input data-side-question-field='customPath' type='text' value='" + escapeHtml(sideQuestionUi.customPath) + "' placeholder='Folder on the computer running Pi'></label>" : "")
|
|
12203
|
+
+ "<div class='side-question-checks'>"
|
|
12204
|
+
+ "<label><input data-side-question-field='includeConversation' type='checkbox'" + (sideQuestionUi.includeConversation ? " checked" : "") + "> Include the current main conversation snapshot</label>"
|
|
12205
|
+
+ (scope === "repo" ? "<label title='Capture Git status, staged and unstaged changes, and up to 20 recent commits when this thread starts.'><input data-side-question-field='gitContext' type='checkbox'" + (sideQuestionUi.gitContext ? " checked" : "") + "> Include Git context</label>" : "")
|
|
12206
|
+
+ "<label title='" + (webDisabled ? "Set BRAVE_API_KEY before starting Pi to enable web search." : "Allow this side thread to search the web when useful.") + "'><input data-side-question-field='webSearch' type='checkbox'" + (sideQuestionUi.webSearch ? " checked" : "") + (webDisabled ? " disabled" : "") + "> Allow web search" + (webDisabled ? " (unavailable)" : "") + "</label>"
|
|
12207
|
+
+ "</div>"
|
|
12208
|
+
+ renderSideQuestionPiToolPicker()
|
|
12209
|
+
+ "<dl class='side-question-context-summary'><div><dt>Starting text</dt><dd>" + escapeHtml(summary.attachmentText) + "</dd></div><div><dt>Related files</dt><dd>" + escapeHtml(summary.relatedFilesText) + "</dd></div>" + (summary.gitContextText ? "<div><dt>Git context</dt><dd>" + escapeHtml(summary.gitContextText) + "</dd></div>" : "") + "</dl>"
|
|
12210
|
+
+ "<label class='side-question-composer-label'>Question<textarea data-side-question-field='draft' rows='4' title='Enter adds a new line. Cmd/Ctrl+Enter asks the side question.' placeholder='Ask about the starting text or anything you want checked…'>" + escapeHtml(sideQuestionUi.draft) + "</textarea></label>"
|
|
12211
|
+
+ (sideQuestionState && sideQuestionState.error ? "<div class='side-question-error'>" + escapeHtml(sideQuestionState.error) + "</div>" : "")
|
|
12212
|
+
+ "<div class='side-question-actions'><button type='button' class='side-question-primary' data-side-question-action='ask' aria-keyshortcuts='Meta+Enter Control+Enter' title='Ask side question (Cmd/Ctrl+Enter)'" + (!isSideQuestionConnectionReady() || (sideQuestionState && sideQuestionState.status === "running") || !sideQuestionUi.draft.trim() || (scope === "custom" && !sideQuestionUi.customPath.trim()) ? " disabled" : "") + ">" + (sideQuestionState && sideQuestionState.status === "running" ? "Preparing side thread…" : "Ask side question") + "</button></div>"
|
|
12213
|
+
+ "</div>";
|
|
12214
|
+
}
|
|
12215
|
+
|
|
12216
|
+
function renderSideQuestionThread() {
|
|
12217
|
+
const state = sideQuestionState;
|
|
12218
|
+
const context = state.context || {};
|
|
12219
|
+
const latestAnswer = getLatestCompletedSideQuestionAnswer();
|
|
12220
|
+
const messageHtml = state.messages.map((message) => {
|
|
12221
|
+
const roleLabel = message.role === "user" ? "You" : "Side answer";
|
|
12222
|
+
const body = message.role === "assistant" && message.status === "complete"
|
|
12223
|
+
? "<div class='side-question-message-body rendered-markdown' data-side-question-markdown='" + escapeHtml(message.text) + "'><div class='side-question-markdown-fallback'>" + escapeHtml(message.text) + "</div></div>"
|
|
12224
|
+
: "<div class='side-question-message-body side-question-plain'>" + escapeHtml(message.text || (message.status === "streaming" ? "Thinking…" : "")) + "</div>";
|
|
12225
|
+
return "<article class='side-question-message side-question-message-" + message.role + " side-question-status-" + message.status + "'>"
|
|
12226
|
+
+ "<div class='side-question-message-label'>" + roleLabel + (message.status === "streaming" ? " <span class='side-question-live'>●</span>" : "") + "</div>" + body + "</article>";
|
|
12227
|
+
}).join("");
|
|
12228
|
+
const activityHtml = state.activity.length
|
|
12229
|
+
? "<details class='side-question-activity'" + (state.status === "running" ? " open" : "") + "><summary>Gathered context · " + state.activity.length + " action" + (state.activity.length === 1 ? "" : "s") + "</summary><ul>"
|
|
12230
|
+
+ state.activity.map((entry) => "<li class='side-question-activity-" + entry.status + "'><span>" + (entry.status === "running" ? "●" : (entry.status === "error" ? "!" : "✓")) + "</span>" + escapeHtml(entry.label) + "</li>").join("") + "</ul></details>"
|
|
12231
|
+
: "";
|
|
12232
|
+
const webLabel = context.webSearchRequested
|
|
12233
|
+
? (context.webSearchAvailable ? "web allowed" : "web unavailable")
|
|
12234
|
+
: "web off";
|
|
12235
|
+
const selectedToolLabel = Array.isArray(context.tools) && context.tools.length
|
|
12236
|
+
? "Additional Pi tools: " + context.tools.map((tool) => tool.name).join(", ")
|
|
12237
|
+
: "Additional Pi tools off";
|
|
12238
|
+
const gitSnapshot = context.gitSnapshot && typeof context.gitSnapshot === "object" ? context.gitSnapshot : null;
|
|
12239
|
+
const gitCapturedLabel = gitSnapshot && gitSnapshot.capturedAt ? formatReferenceTime(gitSnapshot.capturedAt) : "";
|
|
12240
|
+
const gitLabel = gitSnapshot
|
|
12241
|
+
? "Git snapshot: " + (gitSnapshot.branch || "repository") + " · " + gitSnapshot.changeCount + " change" + (gitSnapshot.changeCount === 1 ? "" : "s") + " · " + gitSnapshot.recentCommitCount + " commit" + (gitSnapshot.recentCommitCount === 1 ? "" : "s") + (gitCapturedLabel ? " · captured " + gitCapturedLabel : "") + (gitSnapshot.truncated ? " · truncated" : "")
|
|
12242
|
+
: "";
|
|
12243
|
+
return "<div class='side-question-thread'>"
|
|
12244
|
+
+ "<div class='side-question-thread-header'><div><h2>Side questions</h2><p>Separate from the main Pi conversation.</p></div><button type='button' data-side-question-action='new'" + (state.status === "running" ? " disabled" : "") + ">New thread</button></div>"
|
|
12245
|
+
+ "<div class='side-question-context-chips'><span>" + escapeHtml(context.focusLabel || "Editor context") + "</span><span>" + escapeHtml(context.gatherScope === "none" ? "no other files" : (context.contextRoot || context.gatherScope || "local context")) + "</span>"
|
|
12246
|
+
+ (context.includeConversation ? "<span>main conversation snapshot</span>" : "") + (gitLabel ? "<span>" + escapeHtml(gitLabel) + "</span>" : "") + "<span>" + escapeHtml(webLabel) + "</span><span>" + escapeHtml(selectedToolLabel) + "</span><span>" + escapeHtml(state.modelLabel + " · " + state.thinking) + "</span></div>"
|
|
12247
|
+
+ "<div class='side-question-transcript'>" + messageHtml + "</div>"
|
|
12248
|
+
+ activityHtml
|
|
12249
|
+
+ (state.error ? "<div class='side-question-error'>" + escapeHtml(state.error) + "</div>" : "")
|
|
12250
|
+
+ (latestAnswer ? "<div class='side-question-result-actions'><button type='button' data-side-question-action='copy'>Copy latest answer</button><button type='button' data-side-question-action='insert'>Insert at editor cursor</button><button type='button' data-side-question-action='promote'>Bring to main conversation</button></div>" : "")
|
|
12251
|
+
+ "<label class='side-question-composer-label'>Follow-up<textarea data-side-question-field='draft' rows='3' title='Enter adds a new line. Cmd/Ctrl+Enter asks the follow-up.' placeholder='Ask a follow-up in this side thread…'" + (state.status === "running" ? " disabled" : "") + ">" + escapeHtml(sideQuestionUi.draft) + "</textarea></label>"
|
|
12252
|
+
+ "<div class='side-question-actions'>"
|
|
12253
|
+
+ (state.status === "running"
|
|
12254
|
+
? "<button type='button' class='side-question-stop' data-side-question-action='stop'>Stop</button>"
|
|
12255
|
+
: "<button type='button' class='side-question-primary' data-side-question-action='ask' aria-keyshortcuts='Meta+Enter Control+Enter' title='Ask follow-up (Cmd/Ctrl+Enter)'" + (!isSideQuestionConnectionReady() || !sideQuestionUi.draft.trim() ? " disabled" : "") + ">Ask follow-up</button>")
|
|
12256
|
+
+ "</div></div>";
|
|
12257
|
+
}
|
|
12258
|
+
|
|
12259
|
+
function scheduleSideQuestionContextRefresh() {
|
|
12260
|
+
if (rightView !== "side-questions" || (sideQuestionState && sideQuestionState.threadId) || sideQuestionContextRefreshHandle !== null) return;
|
|
12261
|
+
sideQuestionContextRefreshHandle = window.setTimeout(() => {
|
|
12262
|
+
sideQuestionContextRefreshHandle = null;
|
|
12263
|
+
if (rightView === "side-questions" && (!sideQuestionState || !sideQuestionState.threadId)) renderSideQuestionView();
|
|
12264
|
+
}, 40);
|
|
12265
|
+
}
|
|
12266
|
+
|
|
12267
|
+
function renderSideQuestionView(options) {
|
|
12268
|
+
if (!critiqueViewEl || rightView !== "side-questions") return;
|
|
12269
|
+
finishPreviewRender(critiqueViewEl);
|
|
12270
|
+
const scrollTop = critiqueViewEl.scrollTop;
|
|
12271
|
+
const nearBottom = critiqueViewEl.scrollHeight - critiqueViewEl.clientHeight - critiqueViewEl.scrollTop < 100;
|
|
12272
|
+
critiqueViewEl.innerHTML = sideQuestionState && sideQuestionState.threadId ? renderSideQuestionThread() : renderSideQuestionSetup();
|
|
12273
|
+
const nonce = ++sideQuestionPreviewRenderNonce;
|
|
12274
|
+
void renderSideQuestionMarkdownFields(nonce);
|
|
12275
|
+
if ((options && options.followBottom) || nearBottom) {
|
|
12276
|
+
critiqueViewEl.scrollTop = critiqueViewEl.scrollHeight;
|
|
12277
|
+
window.requestAnimationFrame(() => { if (rightView === "side-questions") critiqueViewEl.scrollTop = critiqueViewEl.scrollHeight; });
|
|
12278
|
+
} else {
|
|
12279
|
+
critiqueViewEl.scrollTop = scrollTop;
|
|
12280
|
+
}
|
|
12281
|
+
}
|
|
12282
|
+
|
|
12283
|
+
function submitSideQuestion() {
|
|
12284
|
+
const question = String(sideQuestionUi.draft || "").trim();
|
|
12285
|
+
if (!question) {
|
|
12286
|
+
setStatus("Enter a side question first.", "warning");
|
|
12287
|
+
return;
|
|
12288
|
+
}
|
|
12289
|
+
if (!isSideQuestionConnectionReady()) {
|
|
12290
|
+
setStatus("Studio is disconnected.", "warning");
|
|
12291
|
+
return;
|
|
12292
|
+
}
|
|
12293
|
+
const requestId = makeRequestId();
|
|
12294
|
+
const message = {
|
|
12295
|
+
type: "side_question_ask_request",
|
|
12296
|
+
requestId,
|
|
12297
|
+
question,
|
|
12298
|
+
};
|
|
12299
|
+
if (sideQuestionState && sideQuestionState.threadId) {
|
|
12300
|
+
message.threadId = sideQuestionState.threadId;
|
|
12301
|
+
} else {
|
|
12302
|
+
message.context = buildSideQuestionContextPayload();
|
|
12303
|
+
if (message.context.gatherScope === "custom" && !String(message.context.contextPath || "").trim()) {
|
|
12304
|
+
setStatus("Choose a custom context path first.", "warning");
|
|
12305
|
+
return;
|
|
12306
|
+
}
|
|
12307
|
+
}
|
|
12308
|
+
if (!sendMessage(message)) return;
|
|
12309
|
+
sideQuestionUi.draft = "";
|
|
12310
|
+
if (!sideQuestionState) sideQuestionState = normalizeSideQuestionState(null);
|
|
12311
|
+
sideQuestionState.status = "running";
|
|
12312
|
+
sideQuestionState.requestId = requestId;
|
|
12313
|
+
sideQuestionState.error = "";
|
|
12314
|
+
renderSideQuestionView({ followBottom: true });
|
|
12315
|
+
updateReferenceBadge();
|
|
12316
|
+
syncAskAsideButton();
|
|
12317
|
+
updateResultActionButtons();
|
|
12318
|
+
setStatus("Side question running independently of the main conversation…", "warning");
|
|
12319
|
+
}
|
|
12320
|
+
|
|
12321
|
+
function sendSideQuestionMarkdownExport(path, content, overwrite) {
|
|
12322
|
+
if (sideQuestionMarkdownExportRequest) {
|
|
12323
|
+
setStatus("A side-thread Markdown export is already in progress.", "warning");
|
|
12324
|
+
return false;
|
|
12325
|
+
}
|
|
12326
|
+
if (!isSideQuestionConnectionReady()) {
|
|
12327
|
+
setStatus("Studio is disconnected.", "warning");
|
|
12328
|
+
return false;
|
|
12329
|
+
}
|
|
12330
|
+
const requestId = makeRequestId();
|
|
12331
|
+
sideQuestionMarkdownExportRequest = {
|
|
12332
|
+
requestId,
|
|
12333
|
+
threadId: sideQuestionState && sideQuestionState.threadId ? sideQuestionState.threadId : "",
|
|
12334
|
+
path: String(path || ""),
|
|
12335
|
+
content: String(content || ""),
|
|
12336
|
+
};
|
|
12337
|
+
const sent = sendMessage({
|
|
12338
|
+
type: "side_question_export_markdown_request",
|
|
12339
|
+
requestId,
|
|
12340
|
+
threadId: sideQuestionMarkdownExportRequest.threadId,
|
|
12341
|
+
path: String(path || ""),
|
|
12342
|
+
content: String(content || ""),
|
|
12343
|
+
overwrite: overwrite === true,
|
|
12344
|
+
});
|
|
12345
|
+
if (!sent) sideQuestionMarkdownExportRequest = null;
|
|
12346
|
+
updateResultActionButtons();
|
|
12347
|
+
if (sent) setStatus(overwrite ? "Replacing side-thread Markdown export…" : "Saving side-thread Markdown…", "warning");
|
|
12348
|
+
return sent;
|
|
12349
|
+
}
|
|
12350
|
+
|
|
12351
|
+
async function saveSideQuestionTranscriptMarkdown() {
|
|
12352
|
+
const exportedAt = new Date();
|
|
12353
|
+
const markdown = buildCurrentSideQuestionTranscriptMarkdown(exportedAt);
|
|
12354
|
+
if (!markdown.trim()) {
|
|
12355
|
+
setStatus("No completed side discussion is available to save.", "warning");
|
|
12356
|
+
return;
|
|
12357
|
+
}
|
|
12358
|
+
const path = await requestStudioTextInput(
|
|
12359
|
+
"Save the visible side-thread transcript and context summary as Markdown. Hidden source text and raw tool output are not included.",
|
|
12360
|
+
getSideQuestionTranscriptSuggestedPath(exportedAt),
|
|
12361
|
+
{
|
|
12362
|
+
title: "Save side-question transcript",
|
|
12363
|
+
inputLabel: "Markdown path on computer running Pi",
|
|
12364
|
+
confirmLabel: "Save Markdown",
|
|
12365
|
+
},
|
|
12366
|
+
);
|
|
12367
|
+
if (!path) return;
|
|
12368
|
+
sendSideQuestionMarkdownExport(path, markdown, false);
|
|
12369
|
+
}
|
|
12370
|
+
|
|
12371
|
+
async function copySideQuestionTranscriptMarkdown() {
|
|
12372
|
+
const markdown = buildCurrentSideQuestionTranscriptMarkdown(new Date());
|
|
12373
|
+
if (!markdown.trim()) {
|
|
12374
|
+
setStatus("No completed side discussion is available to copy.", "warning");
|
|
12375
|
+
return;
|
|
12376
|
+
}
|
|
12377
|
+
const copied = await writeTextToClipboard(markdown);
|
|
12378
|
+
setStatus(copied ? "Copied side-thread Markdown." : "Clipboard write failed.", copied ? "success" : "warning");
|
|
12379
|
+
}
|
|
12380
|
+
|
|
12381
|
+
function openSideQuestionTranscriptInEditor() {
|
|
12382
|
+
const exportedAt = new Date();
|
|
12383
|
+
const markdown = buildCurrentSideQuestionTranscriptMarkdown(exportedAt);
|
|
12384
|
+
if (!markdown.trim()) {
|
|
12385
|
+
setStatus("No completed side discussion is available to open.", "warning");
|
|
12386
|
+
return;
|
|
12387
|
+
}
|
|
12388
|
+
if (markdown.length > SIDE_QUESTION_TRANSCRIPT_RENDER_MAX_CHARS) {
|
|
12389
|
+
setStatus("This side-thread transcript is too large for a companion editor. Save or copy the Markdown instead.", "warning");
|
|
12390
|
+
return;
|
|
12391
|
+
}
|
|
12392
|
+
requestOpenEditorOnlyDocument(markdown, {
|
|
12393
|
+
label: getSideQuestionTranscriptFilename(exportedAt),
|
|
12394
|
+
resourceDir: getCurrentResourceDirValue() || (sideQuestionState.context && sideQuestionState.context.contextRoot) || undefined,
|
|
12395
|
+
});
|
|
12396
|
+
}
|
|
12397
|
+
|
|
12398
|
+
function insertLatestSideQuestionAnswer() {
|
|
12399
|
+
const answer = getLatestCompletedSideQuestionAnswer();
|
|
12400
|
+
if (!answer) return;
|
|
12401
|
+
const current = sourceTextEl.value || "";
|
|
12402
|
+
const start = typeof sourceTextEl.selectionStart === "number" ? sourceTextEl.selectionStart : current.length;
|
|
12403
|
+
const end = typeof sourceTextEl.selectionEnd === "number" ? sourceTextEl.selectionEnd : start;
|
|
12404
|
+
const safeStart = Math.max(0, Math.min(start, current.length));
|
|
12405
|
+
const safeEnd = Math.max(safeStart, Math.min(end, current.length));
|
|
12406
|
+
const next = current.slice(0, safeStart) + answer.text + current.slice(safeEnd);
|
|
12407
|
+
setEditorText(next, { preserveScroll: false, preserveSelection: false });
|
|
12408
|
+
const caret = safeStart + answer.text.length;
|
|
12409
|
+
sourceTextEl.setSelectionRange(caret, caret);
|
|
12410
|
+
setActivePane("left");
|
|
12411
|
+
focusSourceTextNoScroll();
|
|
12412
|
+
setStatus("Inserted the latest side answer at the editor cursor.", "success");
|
|
12413
|
+
}
|
|
12414
|
+
|
|
12415
|
+
async function handleSideQuestionClick(event) {
|
|
12416
|
+
if (rightView !== "side-questions") return;
|
|
12417
|
+
const target = event && event.target instanceof Element ? event.target.closest("[data-side-question-action]") : null;
|
|
12418
|
+
if (!target || !critiqueViewEl.contains(target)) return;
|
|
12419
|
+
event.preventDefault();
|
|
12420
|
+
const action = target.getAttribute("data-side-question-action");
|
|
12421
|
+
if (action === "ask") {
|
|
12422
|
+
submitSideQuestion();
|
|
12423
|
+
} else if (action === "stop") {
|
|
12424
|
+
if (sideQuestionState && sideQuestionState.threadId && sideQuestionState.requestId) {
|
|
12425
|
+
sendMessage({ type: "side_question_cancel_request", threadId: sideQuestionState.threadId, requestId: sideQuestionState.requestId });
|
|
12426
|
+
setStatus("Stopping side question…", "warning");
|
|
12427
|
+
}
|
|
12428
|
+
} else if (action === "new") {
|
|
12429
|
+
const confirmed = !sideQuestionState || !sideQuestionState.messages.length || await requestStudioConfirmation(
|
|
12430
|
+
"Clear this ephemeral side thread and choose fresh context? Nothing has been added to the main conversation.",
|
|
12431
|
+
{ title: "Start a new side thread?", confirmLabel: "New thread", destructive: true },
|
|
12432
|
+
);
|
|
12433
|
+
if (confirmed) {
|
|
12434
|
+
sendMessage({ type: "side_question_clear_request", threadId: sideQuestionState && sideQuestionState.threadId ? sideQuestionState.threadId : undefined });
|
|
12435
|
+
sideQuestionState = null;
|
|
12436
|
+
sideQuestionUi = {
|
|
12437
|
+
...sideQuestionUi,
|
|
12438
|
+
focusMode: "auto",
|
|
12439
|
+
customPath: "",
|
|
12440
|
+
includeConversation: false,
|
|
12441
|
+
gitContext: false,
|
|
12442
|
+
webSearch: false,
|
|
12443
|
+
draft: "",
|
|
12444
|
+
};
|
|
12445
|
+
renderSideQuestionView();
|
|
12446
|
+
updateResultActionButtons();
|
|
12447
|
+
}
|
|
12448
|
+
} else if (action === "copy") {
|
|
12449
|
+
const answer = getLatestCompletedSideQuestionAnswer();
|
|
12450
|
+
if (answer) {
|
|
12451
|
+
const copied = await writeTextToClipboard(answer.text);
|
|
12452
|
+
setStatus(copied ? "Copied latest side answer." : "Could not copy side answer.", copied ? "success" : "error");
|
|
12453
|
+
}
|
|
12454
|
+
} else if (action === "insert") {
|
|
12455
|
+
insertLatestSideQuestionAnswer();
|
|
12456
|
+
} else if (action === "promote") {
|
|
12457
|
+
if (sideQuestionState && sideQuestionState.threadId) {
|
|
12458
|
+
const confirmed = await requestStudioConfirmation(
|
|
12459
|
+
"Send the latest side question and answer into the main Pi conversation? This is the only action that adds the side thread to main context.",
|
|
12460
|
+
{ title: "Bring side answer to main?", confirmLabel: "Bring to main" },
|
|
12461
|
+
);
|
|
12462
|
+
if (confirmed) sendMessage({ type: "side_question_promote_request", threadId: sideQuestionState.threadId });
|
|
12463
|
+
}
|
|
12464
|
+
}
|
|
12465
|
+
}
|
|
12466
|
+
|
|
12467
|
+
function handleSideQuestionInput(event) {
|
|
12468
|
+
if (rightView !== "side-questions") return;
|
|
12469
|
+
const target = event && event.target;
|
|
12470
|
+
if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)) return;
|
|
12471
|
+
const field = target.getAttribute("data-side-question-field");
|
|
12472
|
+
if (field === "draft") sideQuestionUi.draft = target.value;
|
|
12473
|
+
if (field === "customPath") sideQuestionUi.customPath = target.value;
|
|
12474
|
+
const askButton = critiqueViewEl.querySelector("[data-side-question-action='ask']");
|
|
12475
|
+
if (askButton) askButton.disabled = !isSideQuestionConnectionReady() || !sideQuestionUi.draft.trim() || (getSideQuestionGatherScope() === "custom" && !sideQuestionUi.customPath.trim());
|
|
12476
|
+
}
|
|
12477
|
+
|
|
12478
|
+
function handleSideQuestionKeydown(event) {
|
|
12479
|
+
if (rightView !== "side-questions" || !event || event.isComposing) return;
|
|
12480
|
+
const target = event.target;
|
|
12481
|
+
if (!(target instanceof HTMLTextAreaElement) || target.getAttribute("data-side-question-field") !== "draft") return;
|
|
12482
|
+
const submitShortcut = event.key === "Enter"
|
|
12483
|
+
&& (event.metaKey || event.ctrlKey)
|
|
12484
|
+
&& !event.altKey
|
|
12485
|
+
&& !event.shiftKey;
|
|
12486
|
+
if (!submitShortcut) return;
|
|
12487
|
+
event.preventDefault();
|
|
12488
|
+
event.stopPropagation();
|
|
12489
|
+
if (!sideQuestionState || sideQuestionState.status !== "running") submitSideQuestion();
|
|
12490
|
+
}
|
|
12491
|
+
|
|
12492
|
+
async function handleSideQuestionChange(event) {
|
|
12493
|
+
if (rightView !== "side-questions") return;
|
|
12494
|
+
const target = event && event.target;
|
|
12495
|
+
if (!(target instanceof HTMLInputElement || target instanceof HTMLSelectElement)) return;
|
|
12496
|
+
const toolId = target.getAttribute("data-side-question-tool");
|
|
12497
|
+
if (toolId) {
|
|
12498
|
+
const tool = sideQuestionAvailablePiTools.find((candidate) => candidate.id === toolId);
|
|
12499
|
+
if (!tool) return;
|
|
12500
|
+
const selected = new Set(sideQuestionUi.toolIds);
|
|
12501
|
+
if (target.checked) {
|
|
12502
|
+
if (tool.gateway && !selected.has(toolId)) {
|
|
12503
|
+
const confirmed = await requestStudioConfirmation(
|
|
12504
|
+
`“${tool.name}” is a gateway tool. Selecting it may expose additional services and actions configured behind that extension, subject to the extension's own permissions.`,
|
|
12505
|
+
{ title: "Allow gateway tool in side questions?", confirmLabel: "Allow gateway" },
|
|
12506
|
+
);
|
|
12507
|
+
if (!confirmed) {
|
|
12508
|
+
target.checked = false;
|
|
12509
|
+
renderSideQuestionView();
|
|
12510
|
+
return;
|
|
12511
|
+
}
|
|
12512
|
+
}
|
|
12513
|
+
if (selected.size >= 12 && !selected.has(toolId)) {
|
|
12514
|
+
setStatus("Select at most 12 additional Pi tools.", "warning");
|
|
12515
|
+
renderSideQuestionView();
|
|
12516
|
+
return;
|
|
12517
|
+
}
|
|
12518
|
+
selected.add(toolId);
|
|
12519
|
+
} else {
|
|
12520
|
+
selected.delete(toolId);
|
|
12521
|
+
}
|
|
12522
|
+
sideQuestionUi.toolIds = [...selected];
|
|
12523
|
+
persistSideQuestionToolSelection();
|
|
12524
|
+
renderSideQuestionView();
|
|
12525
|
+
return;
|
|
12526
|
+
}
|
|
12527
|
+
const field = target.getAttribute("data-side-question-field");
|
|
12528
|
+
if (!field) return;
|
|
12529
|
+
if (field === "focusMode") sideQuestionUi.focusMode = target.value;
|
|
12530
|
+
if (field === "gatherScope") {
|
|
12531
|
+
sideQuestionUi.gatherScope = target.value;
|
|
12532
|
+
if (sideQuestionUi.gatherScope !== "repo") sideQuestionUi.gitContext = false;
|
|
12533
|
+
try { if (window.localStorage) window.localStorage.setItem(SIDE_QUESTION_GATHER_STORAGE_KEY, sideQuestionUi.gatherScope); } catch {}
|
|
12534
|
+
}
|
|
12535
|
+
if (field === "thinking") {
|
|
12536
|
+
sideQuestionUi.thinking = target.value;
|
|
12537
|
+
try { if (window.localStorage) window.localStorage.setItem(SIDE_QUESTION_THINKING_STORAGE_KEY, sideQuestionUi.thinking); } catch {}
|
|
12538
|
+
}
|
|
12539
|
+
if (field === "includeConversation") sideQuestionUi.includeConversation = target.checked;
|
|
12540
|
+
if (field === "gitContext") sideQuestionUi.gitContext = target.checked && getSideQuestionGatherScope() === "repo";
|
|
12541
|
+
if (field === "webSearch") sideQuestionUi.webSearch = target.checked;
|
|
12542
|
+
renderSideQuestionView();
|
|
12543
|
+
}
|
|
12544
|
+
|
|
11650
12545
|
function renderActiveResult() {
|
|
11651
12546
|
if (critiqueViewEl) {
|
|
11652
12547
|
critiqueViewEl.classList.toggle("git-changes-host", rightView === "changes");
|
|
11653
12548
|
critiqueViewEl.classList.toggle("quarto-preview-host", rightView === "editor-quarto-preview");
|
|
12549
|
+
critiqueViewEl.classList.toggle("side-question-host", rightView === "side-questions");
|
|
12550
|
+
}
|
|
12551
|
+
if (rightView === "side-questions") {
|
|
12552
|
+
renderSideQuestionView();
|
|
12553
|
+
return;
|
|
11654
12554
|
}
|
|
11655
12555
|
if (rightView === "editor-quarto-preview") {
|
|
11656
12556
|
renderQuartoPreviewView();
|
|
@@ -11754,7 +12654,7 @@
|
|
|
11754
12654
|
: normalizeForCompare(sourceTextEl.value);
|
|
11755
12655
|
const responseLoaded = hasResponse && normalizedEditor === latestResponseNormalized;
|
|
11756
12656
|
const isCritiqueResponse = hasResponse && latestResponseIsStructuredCritique;
|
|
11757
|
-
const showingAuxiliaryRightPane = rightView === "trace" || rightView === "repl" || rightView === "files" || rightView === "changes" || rightView === "editor-quarto-preview";
|
|
12657
|
+
const showingAuxiliaryRightPane = rightView === "trace" || rightView === "repl" || rightView === "files" || rightView === "changes" || rightView === "editor-quarto-preview" || rightView === "side-questions";
|
|
11758
12658
|
|
|
11759
12659
|
if (responseWrapEl) {
|
|
11760
12660
|
responseWrapEl.hidden = showingAuxiliaryRightPane;
|
|
@@ -11792,28 +12692,61 @@
|
|
|
11792
12692
|
|
|
11793
12693
|
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
11794
12694
|
const exportingReplJournal = rightView === "repl";
|
|
12695
|
+
const exportingSideThread = rightView === "side-questions";
|
|
11795
12696
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
12697
|
+
const sideThreadExportText = exportingSideThread && canExportSideQuestionTranscript()
|
|
12698
|
+
? buildCurrentSideQuestionTranscriptMarkdown(new Date())
|
|
12699
|
+
: "";
|
|
11796
12700
|
const exportText = exportingReplJournal
|
|
11797
12701
|
? (replJournalExportEntries.length ? buildReplJournalMarkdown(replJournalExportEntries) : "")
|
|
11798
|
-
: (
|
|
11799
|
-
|
|
11800
|
-
|
|
12702
|
+
: (exportingSideThread
|
|
12703
|
+
? sideThreadExportText
|
|
12704
|
+
: (rightView === "editor-preview" ? prepareEditorTextForPreview(sourceTextEl.value) : latestResponseMarkdown));
|
|
12705
|
+
const canExportPreview = (rightPaneShowsPreview || exportingReplJournal || exportingSideThread) && Boolean(String(exportText || "").trim());
|
|
12706
|
+
const htmlArtifactExportSource = canExportPreview && !exportingReplJournal && !exportingSideThread ? getRightPaneHtmlArtifactSource() : "";
|
|
11801
12707
|
const isHtmlArtifactPreview = Boolean(htmlArtifactExportSource);
|
|
12708
|
+
const sideThreadRenderTooLarge = exportingSideThread && sideThreadExportText.length > SIDE_QUESTION_TRANSCRIPT_RENDER_MAX_CHARS;
|
|
12709
|
+
const exportBusy = previewExportInProgress || Boolean(sideQuestionMarkdownExportRequest) || (!exportingSideThread && uiBusy);
|
|
12710
|
+
if (exportSideThreadMarkdownSaveBtn) {
|
|
12711
|
+
exportSideThreadMarkdownSaveBtn.hidden = !exportingSideThread;
|
|
12712
|
+
exportSideThreadMarkdownSaveBtn.disabled = Boolean(sideQuestionMarkdownExportRequest) || !canExportPreview;
|
|
12713
|
+
exportSideThreadMarkdownSaveBtn.title = "Save the visible discussion and context summary as a Markdown file on the computer running Pi.";
|
|
12714
|
+
}
|
|
12715
|
+
if (exportSideThreadMarkdownCopyBtn) {
|
|
12716
|
+
exportSideThreadMarkdownCopyBtn.hidden = !exportingSideThread;
|
|
12717
|
+
exportSideThreadMarkdownCopyBtn.disabled = Boolean(sideQuestionMarkdownExportRequest) || !canExportPreview;
|
|
12718
|
+
exportSideThreadMarkdownCopyBtn.title = "Copy the visible discussion and context summary as Markdown.";
|
|
12719
|
+
}
|
|
12720
|
+
if (exportSideThreadMarkdownEditorBtn) {
|
|
12721
|
+
exportSideThreadMarkdownEditorBtn.hidden = !exportingSideThread;
|
|
12722
|
+
exportSideThreadMarkdownEditorBtn.disabled = uiBusy || Boolean(sideQuestionMarkdownExportRequest) || !canExportPreview || sideThreadRenderTooLarge;
|
|
12723
|
+
exportSideThreadMarkdownEditorBtn.title = sideThreadRenderTooLarge
|
|
12724
|
+
? "This transcript is too large for a companion editor; save or copy the Markdown instead."
|
|
12725
|
+
: "Open the Markdown transcript as an unsaved copy in a new Studio editor tab.";
|
|
12726
|
+
}
|
|
11802
12727
|
if (exportPdfBtn) {
|
|
11803
|
-
exportPdfBtn.disabled =
|
|
12728
|
+
exportPdfBtn.disabled = exportBusy || !canExportPreview;
|
|
11804
12729
|
exportPdfBtn.textContent = previewExportInProgress
|
|
11805
12730
|
? "Exporting…"
|
|
11806
|
-
: (
|
|
12731
|
+
: (sideQuestionMarkdownExportRequest
|
|
12732
|
+
? "Saving…"
|
|
12733
|
+
: (exportingSideThread ? "Export thread" : (exportingReplJournal ? "Export record" : "Export right preview")));
|
|
11807
12734
|
if (rightView === "trace") {
|
|
11808
12735
|
exportPdfBtn.title = "Working view does not support preview export.";
|
|
11809
12736
|
} else if (rightView === "files") {
|
|
11810
12737
|
exportPdfBtn.title = "Files view does not support preview export.";
|
|
11811
12738
|
} else if (rightView === "changes") {
|
|
11812
12739
|
exportPdfBtn.title = "Changes view does not support preview export.";
|
|
12740
|
+
} else if (exportingSideThread && sideQuestionState && sideQuestionState.status === "running") {
|
|
12741
|
+
exportPdfBtn.title = "Wait for the current side answer before exporting the thread.";
|
|
12742
|
+
} else if (exportingSideThread && !canExportPreview) {
|
|
12743
|
+
exportPdfBtn.title = "No completed side discussion is available to export yet.";
|
|
12744
|
+
} else if (exportingSideThread) {
|
|
12745
|
+
exportPdfBtn.title = "Save or copy Markdown, open it in an editor, or export the visible side discussion as PDF or HTML.";
|
|
11813
12746
|
} else if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
11814
12747
|
exportPdfBtn.title = "No Studio REPL record entries to export for this session yet.";
|
|
11815
12748
|
} else if (rightView === "markdown") {
|
|
11816
|
-
exportPdfBtn.title = "Switch right pane to Response (Preview), Editor (Preview), or
|
|
12749
|
+
exportPdfBtn.title = "Switch right pane to Response (Preview), Editor (Preview), REPL, or Side questions to export.";
|
|
11817
12750
|
} else if (!canExportPreview) {
|
|
11818
12751
|
exportPdfBtn.title = "Nothing to export yet.";
|
|
11819
12752
|
} else if (isHtmlArtifactPreview) {
|
|
@@ -11824,39 +12757,52 @@
|
|
|
11824
12757
|
exportPdfBtn.title = "Choose PDF export or an HTML export destination for the current right-pane preview.";
|
|
11825
12758
|
}
|
|
11826
12759
|
}
|
|
12760
|
+
if (exportSideThreadRenderSeparatorEl) exportSideThreadRenderSeparatorEl.hidden = !exportingSideThread;
|
|
11827
12761
|
if (exportPreviewPdfStudioBtn) {
|
|
11828
|
-
exportPreviewPdfStudioBtn.disabled =
|
|
11829
|
-
exportPreviewPdfStudioBtn.title =
|
|
11830
|
-
? "
|
|
11831
|
-
: (
|
|
12762
|
+
exportPreviewPdfStudioBtn.disabled = exportBusy || !canExportPreview || isHtmlArtifactPreview || sideThreadRenderTooLarge;
|
|
12763
|
+
exportPreviewPdfStudioBtn.title = sideThreadRenderTooLarge
|
|
12764
|
+
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
12765
|
+
: (isHtmlArtifactPreview
|
|
12766
|
+
? "Interactive HTML preview PDF export is not available yet."
|
|
12767
|
+
: (exportingSideThread ? "Export the side-thread transcript as PDF and open it in Studio." : (exportingReplJournal ? "Export the Studio REPL record as PDF and open it in Studio." : "Export the current right-pane preview as PDF and open it in Studio.")));
|
|
11832
12768
|
}
|
|
11833
12769
|
if (exportPreviewPdfBtn) {
|
|
11834
|
-
exportPreviewPdfBtn.disabled =
|
|
11835
|
-
exportPreviewPdfBtn.title =
|
|
11836
|
-
? "
|
|
11837
|
-
: (
|
|
12770
|
+
exportPreviewPdfBtn.disabled = exportBusy || !canExportPreview || isHtmlArtifactPreview || sideThreadRenderTooLarge;
|
|
12771
|
+
exportPreviewPdfBtn.title = sideThreadRenderTooLarge
|
|
12772
|
+
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
12773
|
+
: (isHtmlArtifactPreview
|
|
12774
|
+
? "Interactive HTML preview PDF export is not available yet."
|
|
12775
|
+
: (exportingSideThread ? "Export the side-thread transcript as PDF and open it in the default PDF viewer." : (exportingReplJournal ? "Export the Studio REPL record as PDF and open it in the default PDF viewer." : "Export the current right-pane preview as PDF and open it in the default PDF viewer.")));
|
|
11838
12776
|
}
|
|
11839
12777
|
if (exportPreviewHtmlStudioBtn) {
|
|
11840
|
-
exportPreviewHtmlStudioBtn.disabled =
|
|
11841
|
-
exportPreviewHtmlStudioBtn.title =
|
|
11842
|
-
? "
|
|
11843
|
-
: (
|
|
12778
|
+
exportPreviewHtmlStudioBtn.disabled = exportBusy || !canExportPreview || sideThreadRenderTooLarge;
|
|
12779
|
+
exportPreviewHtmlStudioBtn.title = sideThreadRenderTooLarge
|
|
12780
|
+
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
12781
|
+
: (isHtmlArtifactPreview
|
|
12782
|
+
? "Export the authored HTML preview and open it in a new Studio editor tab."
|
|
12783
|
+
: (exportingSideThread ? "Export the side-thread transcript as standalone HTML and open it in a new Studio editor tab." : (exportingReplJournal ? "Export the Studio REPL record as standalone HTML and open it in a new Studio editor tab." : "Export the current right-pane preview as standalone HTML and open it in a new Studio editor tab.")));
|
|
11844
12784
|
}
|
|
11845
12785
|
if (exportPreviewHtmlBtn) {
|
|
11846
|
-
exportPreviewHtmlBtn.disabled =
|
|
11847
|
-
exportPreviewHtmlBtn.title =
|
|
11848
|
-
? "
|
|
11849
|
-
: (
|
|
12786
|
+
exportPreviewHtmlBtn.disabled = exportBusy || !canExportPreview || sideThreadRenderTooLarge;
|
|
12787
|
+
exportPreviewHtmlBtn.title = sideThreadRenderTooLarge
|
|
12788
|
+
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
12789
|
+
: (isHtmlArtifactPreview
|
|
12790
|
+
? "Export the authored HTML preview and open it in the default browser."
|
|
12791
|
+
: (exportingSideThread ? "Export the side-thread transcript as standalone HTML and open it in the default browser." : (exportingReplJournal ? "Export the Studio REPL record as standalone HTML and open it in the default browser." : "Export the current right-pane preview as standalone HTML and open it in the default browser.")));
|
|
11850
12792
|
}
|
|
11851
12793
|
if (exportPreviewControlsEl) {
|
|
11852
12794
|
exportPreviewControlsEl.hidden = rightView === "editor-quarto-preview";
|
|
11853
12795
|
exportPreviewControlsEl.title = canExportPreview
|
|
11854
|
-
? (
|
|
11855
|
-
? "Choose a
|
|
11856
|
-
: (
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
12796
|
+
? (exportingSideThread
|
|
12797
|
+
? "Choose a durable export for the visible side discussion."
|
|
12798
|
+
: (exportingReplJournal
|
|
12799
|
+
? "Choose a format and export destination for the Studio REPL record."
|
|
12800
|
+
: (isHtmlArtifactPreview ? "Export this HTML preview to Studio or browser." : "Choose a format and export destination for the current right-pane preview.")))
|
|
12801
|
+
: (exportingSideThread
|
|
12802
|
+
? "No completed side discussion is available to export yet."
|
|
12803
|
+
: (exportingReplJournal ? "No Studio REPL record entries to export for this session yet." : "Switch right pane to a non-empty preview before exporting."));
|
|
12804
|
+
}
|
|
12805
|
+
if (!canExportPreview || previewExportInProgress || sideQuestionMarkdownExportRequest) {
|
|
11860
12806
|
closeExportPreviewMenu();
|
|
11861
12807
|
}
|
|
11862
12808
|
|
|
@@ -11866,6 +12812,7 @@
|
|
|
11866
12812
|
updateSyncBadge(normalizedEditor);
|
|
11867
12813
|
syncStudioQuartoDirtyUi();
|
|
11868
12814
|
syncShowMeButton();
|
|
12815
|
+
syncAskAsideButton();
|
|
11869
12816
|
}
|
|
11870
12817
|
|
|
11871
12818
|
function refreshResponseUi() {
|
|
@@ -13034,9 +13981,19 @@
|
|
|
13034
13981
|
if (rightView === "editor-quarto-preview") {
|
|
13035
13982
|
requestStudioQuartoPreviewCheck(false);
|
|
13036
13983
|
}
|
|
13984
|
+
if (rightView === "side-questions" && previousView !== "side-questions") {
|
|
13985
|
+
if (!sideQuestionUi.gatherScope) sideQuestionUi.gatherScope = getSideQuestionGatherScope();
|
|
13986
|
+
sendMessage({ type: "side_question_get_state" });
|
|
13987
|
+
}
|
|
13037
13988
|
|
|
13038
13989
|
refreshResponseUi();
|
|
13039
13990
|
syncActionButtons();
|
|
13991
|
+
if (rightView === "side-questions" && previousView !== "side-questions") {
|
|
13992
|
+
window.setTimeout(() => {
|
|
13993
|
+
const composer = critiqueViewEl && critiqueViewEl.querySelector("[data-side-question-field='draft']");
|
|
13994
|
+
if (composer instanceof HTMLTextAreaElement) composer.focus({ preventScroll: true });
|
|
13995
|
+
}, 0);
|
|
13996
|
+
}
|
|
13040
13997
|
scheduleWorkspacePersistence();
|
|
13041
13998
|
}
|
|
13042
13999
|
|
|
@@ -20157,6 +21114,7 @@
|
|
|
20157
21114
|
quizBtn.disabled = true;
|
|
20158
21115
|
quizBtn.title = "Quiz is unavailable in editor-only mode.";
|
|
20159
21116
|
}
|
|
21117
|
+
syncAskAsideButton();
|
|
20160
21118
|
syncStudioUiRefreshReviewTrigger();
|
|
20161
21119
|
return;
|
|
20162
21120
|
}
|
|
@@ -20231,6 +21189,7 @@
|
|
|
20231
21189
|
: "Open an active quiz for the current editor selection or document.");
|
|
20232
21190
|
}
|
|
20233
21191
|
syncShowMeButton();
|
|
21192
|
+
syncAskAsideButton();
|
|
20234
21193
|
syncStudioUiRefreshReviewTrigger();
|
|
20235
21194
|
}
|
|
20236
21195
|
|
|
@@ -20391,6 +21350,82 @@
|
|
|
20391
21350
|
return;
|
|
20392
21351
|
}
|
|
20393
21352
|
|
|
21353
|
+
if (message.type === "side_question_state") {
|
|
21354
|
+
sideQuestionWebSearchAvailable = message.webSearchAvailable === true;
|
|
21355
|
+
if (Array.isArray(message.availablePiTools)) applySideQuestionToolCatalog(message.availablePiTools);
|
|
21356
|
+
const previousStatus = sideQuestionState && sideQuestionState.status;
|
|
21357
|
+
sideQuestionState = normalizeSideQuestionState(message.state);
|
|
21358
|
+
if (rightView === "side-questions") renderSideQuestionView();
|
|
21359
|
+
updateReferenceBadge();
|
|
21360
|
+
syncAskAsideButton();
|
|
21361
|
+
updateResultActionButtons();
|
|
21362
|
+
if (rightView === "side-questions" && previousStatus === "running" && sideQuestionState.status === "idle") {
|
|
21363
|
+
setStatus(agentBusyFromServer
|
|
21364
|
+
? "Side answer ready; the main Pi turn is still running. Main conversation unchanged."
|
|
21365
|
+
: "Side answer ready. Main conversation unchanged.", "success");
|
|
21366
|
+
}
|
|
21367
|
+
return;
|
|
21368
|
+
}
|
|
21369
|
+
|
|
21370
|
+
if (message.type === "side_question_markdown_exported") {
|
|
21371
|
+
if (!sideQuestionMarkdownExportRequest || sideQuestionMarkdownExportRequest.requestId !== message.requestId) return;
|
|
21372
|
+
sideQuestionMarkdownExportRequest = null;
|
|
21373
|
+
updateResultActionButtons();
|
|
21374
|
+
setStatus(typeof message.message === "string" ? message.message : "Saved side-question transcript.", "success");
|
|
21375
|
+
return;
|
|
21376
|
+
}
|
|
21377
|
+
|
|
21378
|
+
if (message.type === "side_question_markdown_export_conflict") {
|
|
21379
|
+
if (!sideQuestionMarkdownExportRequest || sideQuestionMarkdownExportRequest.requestId !== message.requestId) return;
|
|
21380
|
+
const pendingExport = sideQuestionMarkdownExportRequest;
|
|
21381
|
+
sideQuestionMarkdownExportRequest = null;
|
|
21382
|
+
updateResultActionButtons();
|
|
21383
|
+
void (async () => {
|
|
21384
|
+
const displayPath = typeof message.path === "string" && message.path ? message.path : pendingExport.path;
|
|
21385
|
+
const confirmed = await requestStudioConfirmation("Replace the existing Markdown transcript at " + displayPath + "?", {
|
|
21386
|
+
title: "Replace transcript file?",
|
|
21387
|
+
confirmLabel: "Replace",
|
|
21388
|
+
destructive: true,
|
|
21389
|
+
});
|
|
21390
|
+
if (!confirmed) {
|
|
21391
|
+
setStatus("Side-thread Markdown export cancelled.", "warning");
|
|
21392
|
+
return;
|
|
21393
|
+
}
|
|
21394
|
+
if (!sideQuestionState || sideQuestionState.threadId !== pendingExport.threadId) {
|
|
21395
|
+
setStatus("That side thread is no longer active; export cancelled.", "warning");
|
|
21396
|
+
return;
|
|
21397
|
+
}
|
|
21398
|
+
sendSideQuestionMarkdownExport(pendingExport.path, pendingExport.content, true);
|
|
21399
|
+
})();
|
|
21400
|
+
return;
|
|
21401
|
+
}
|
|
21402
|
+
|
|
21403
|
+
if (message.type === "side_question_markdown_export_error") {
|
|
21404
|
+
if (!sideQuestionMarkdownExportRequest || sideQuestionMarkdownExportRequest.requestId !== message.requestId) return;
|
|
21405
|
+
sideQuestionMarkdownExportRequest = null;
|
|
21406
|
+
updateResultActionButtons();
|
|
21407
|
+
setStatus(typeof message.message === "string" ? message.message : "Could not save side-question transcript.", "error");
|
|
21408
|
+
return;
|
|
21409
|
+
}
|
|
21410
|
+
|
|
21411
|
+
if (message.type === "side_question_error") {
|
|
21412
|
+
if (!sideQuestionState) sideQuestionState = normalizeSideQuestionState(null);
|
|
21413
|
+
sideQuestionState.status = "error";
|
|
21414
|
+
sideQuestionState.requestId = null;
|
|
21415
|
+
sideQuestionState.error = typeof message.message === "string" ? message.message : "Side question failed.";
|
|
21416
|
+
if (rightView === "side-questions") renderSideQuestionView();
|
|
21417
|
+
updateReferenceBadge();
|
|
21418
|
+
syncAskAsideButton();
|
|
21419
|
+
updateResultActionButtons();
|
|
21420
|
+
setStatus(sideQuestionState.error, "error");
|
|
21421
|
+
return;
|
|
21422
|
+
}
|
|
21423
|
+
|
|
21424
|
+
if (message.type === "side_question_promoted") {
|
|
21425
|
+
setStatus(typeof message.message === "string" ? message.message : "Side answer sent to the main conversation.", "success");
|
|
21426
|
+
return;
|
|
21427
|
+
}
|
|
21428
|
+
|
|
20394
21429
|
if (message.type === "quarto_preview_context") {
|
|
20395
21430
|
const requestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
20396
21431
|
const context = normalizeStudioQuartoContext(message.context);
|
|
@@ -20472,6 +21507,11 @@
|
|
|
20472
21507
|
if (typeof message.modelLabel === "string") {
|
|
20473
21508
|
modelLabel = message.modelLabel;
|
|
20474
21509
|
}
|
|
21510
|
+
sideQuestionWebSearchAvailable = message.webSearchAvailable === true;
|
|
21511
|
+
if (Array.isArray(message.availablePiTools)) applySideQuestionToolCatalog(message.availablePiTools);
|
|
21512
|
+
if (message.sideQuestion && typeof message.sideQuestion === "object") {
|
|
21513
|
+
sideQuestionState = normalizeSideQuestionState(message.sideQuestion);
|
|
21514
|
+
}
|
|
20475
21515
|
if (Array.isArray(message.suggestionModels)) {
|
|
20476
21516
|
updateCompletionSuggestionModelOptions(message.suggestionModels);
|
|
20477
21517
|
}
|
|
@@ -20570,6 +21610,8 @@
|
|
|
20570
21610
|
requestStudioQuartoPreviewCheck(false);
|
|
20571
21611
|
renderQuartoPreviewView();
|
|
20572
21612
|
}
|
|
21613
|
+
if (rightView === "side-questions") renderSideQuestionView();
|
|
21614
|
+
syncAskAsideButton();
|
|
20573
21615
|
|
|
20574
21616
|
if (pendingRequestId) {
|
|
20575
21617
|
if (busy) {
|
|
@@ -21293,6 +22335,7 @@
|
|
|
21293
22335
|
quartoPreviewCheckRequestId = null;
|
|
21294
22336
|
quartoPreviewCheckSourcePath = "";
|
|
21295
22337
|
quartoPreviewActionRequestId = null;
|
|
22338
|
+
sideQuestionMarkdownExportRequest = null;
|
|
21296
22339
|
failAllPendingCompanionLaunches("The originating Studio connection was lost before the companion editor was ready.");
|
|
21297
22340
|
if (rightView === "editor-quarto-preview") renderQuartoPreviewView();
|
|
21298
22341
|
setBusy(true);
|
|
@@ -21785,6 +22828,7 @@
|
|
|
21785
22828
|
updateReviewNotesUi();
|
|
21786
22829
|
}
|
|
21787
22830
|
scheduleWorkspacePersistence();
|
|
22831
|
+
scheduleSideQuestionContextRefresh();
|
|
21788
22832
|
});
|
|
21789
22833
|
|
|
21790
22834
|
sourceTextEl.addEventListener("select", () => {
|
|
@@ -21798,14 +22842,17 @@
|
|
|
21798
22842
|
}
|
|
21799
22843
|
updateEditorSelectionCommentUi();
|
|
21800
22844
|
syncShowMeButton();
|
|
22845
|
+
scheduleSideQuestionContextRefresh();
|
|
21801
22846
|
});
|
|
21802
22847
|
|
|
21803
22848
|
sourceTextEl.addEventListener("keyup", () => {
|
|
21804
22849
|
updateEditorSelectionCommentUi();
|
|
22850
|
+
scheduleSideQuestionContextRefresh();
|
|
21805
22851
|
});
|
|
21806
22852
|
|
|
21807
22853
|
sourceTextEl.addEventListener("mouseup", () => {
|
|
21808
22854
|
updateEditorSelectionCommentUi();
|
|
22855
|
+
scheduleSideQuestionContextRefresh();
|
|
21809
22856
|
});
|
|
21810
22857
|
|
|
21811
22858
|
sourceTextEl.addEventListener("focus", () => {
|
|
@@ -21919,6 +22966,13 @@
|
|
|
21919
22966
|
});
|
|
21920
22967
|
}
|
|
21921
22968
|
|
|
22969
|
+
if (askAsideBtn) {
|
|
22970
|
+
askAsideBtn.addEventListener("click", () => {
|
|
22971
|
+
setRightView("side-questions");
|
|
22972
|
+
setActivePane("right");
|
|
22973
|
+
});
|
|
22974
|
+
}
|
|
22975
|
+
|
|
21922
22976
|
if (quizBtn) {
|
|
21923
22977
|
quizBtn.addEventListener("click", () => {
|
|
21924
22978
|
openQuizOverlay();
|
|
@@ -22254,9 +23308,29 @@
|
|
|
22254
23308
|
});
|
|
22255
23309
|
}
|
|
22256
23310
|
|
|
23311
|
+
if (hideStudioHeaderBtn) {
|
|
23312
|
+
hideStudioHeaderBtn.addEventListener("click", () => {
|
|
23313
|
+
setStudioHeaderHidden(true, { focusReveal: true });
|
|
23314
|
+
setStatus("Studio header hidden. Move to the top-right edge to restore it.");
|
|
23315
|
+
});
|
|
23316
|
+
}
|
|
23317
|
+
|
|
23318
|
+
if (studioHeaderRevealEl) {
|
|
23319
|
+
studioHeaderRevealEl.addEventListener("click", (event) => {
|
|
23320
|
+
if (event.target === studioHeaderRevealEl) restoreStudioHeaderFromReveal();
|
|
23321
|
+
});
|
|
23322
|
+
}
|
|
23323
|
+
|
|
23324
|
+
if (studioHeaderRevealBtn) {
|
|
23325
|
+
studioHeaderRevealBtn.addEventListener("click", restoreStudioHeaderFromReveal);
|
|
23326
|
+
}
|
|
23327
|
+
|
|
22257
23328
|
if (zenModeBtn) {
|
|
22258
23329
|
zenModeBtn.addEventListener("click", () => {
|
|
22259
|
-
setStudioZenMode(!studioZenModeEnabled);
|
|
23330
|
+
setStudioZenMode(!studioZenModeEnabled, { focusReveal: !studioZenModeEnabled });
|
|
23331
|
+
setStatus(studioZenModeEnabled
|
|
23332
|
+
? "Zen mode on. Studio header hidden; press F9 to exit."
|
|
23333
|
+
: "Zen mode off. Studio header restored.");
|
|
22260
23334
|
});
|
|
22261
23335
|
}
|
|
22262
23336
|
|