pi-studio 0.9.42 → 0.9.44
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 +14 -0
- package/README.md +3 -3
- package/client/studio-client.js +297 -59
- package/client/studio-navigation-helpers.js +106 -0
- package/client/studio-show-me-helpers.js +93 -0
- package/client/studio.css +61 -52
- package/index.ts +167 -8
- package/package.json +1 -1
- package/shared/studio-show-me.js +69 -0
- package/shared/studio-workspace-state.js +118 -0
package/client/studio-client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(() => {
|
|
1
|
+
(async () => {
|
|
2
2
|
const statusLineEl = document.getElementById("statusLine");
|
|
3
3
|
const statusEl = document.getElementById("status");
|
|
4
4
|
const statusSpinnerEl = document.getElementById("statusSpinner");
|
|
@@ -81,6 +81,8 @@
|
|
|
81
81
|
const pullLatestBtn = document.getElementById("pullLatestBtn");
|
|
82
82
|
const insertHeaderBtn = document.getElementById("insertHeaderBtn");
|
|
83
83
|
const critiqueBtn = document.getElementById("critiqueBtn");
|
|
84
|
+
const showMeBtn = document.getElementById("showMeBtn");
|
|
85
|
+
const showMeResponseBtn = document.getElementById("showMeResponseBtn");
|
|
84
86
|
const quizBtn = document.getElementById("quizBtn");
|
|
85
87
|
const lensSelect = document.getElementById("lensSelect");
|
|
86
88
|
const fileInput = document.getElementById("fileInput");
|
|
@@ -90,6 +92,7 @@
|
|
|
90
92
|
const resourceDirInput = document.getElementById("resourceDirInput");
|
|
91
93
|
const resourceDirClearBtn = document.getElementById("resourceDirClearBtn");
|
|
92
94
|
const loadResponseBtn = document.getElementById("loadResponseBtn");
|
|
95
|
+
const annotateResponseBtn = document.getElementById("annotateResponseBtn");
|
|
93
96
|
const loadCritiqueNotesBtn = document.getElementById("loadCritiqueNotesBtn");
|
|
94
97
|
const loadCritiqueFullBtn = document.getElementById("loadCritiqueFullBtn");
|
|
95
98
|
const copyResponseBtn = document.getElementById("copyResponseBtn");
|
|
@@ -209,9 +212,21 @@
|
|
|
209
212
|
};
|
|
210
213
|
|
|
211
214
|
const navigationHelpers = globalThis.PiStudioNavigationHelpers;
|
|
212
|
-
if (
|
|
215
|
+
if (
|
|
216
|
+
!navigationHelpers
|
|
217
|
+
|| typeof navigationHelpers.readPaneFocusTarget !== "function"
|
|
218
|
+
|| typeof navigationHelpers.ensureStudioTabStateId !== "function"
|
|
219
|
+
|| typeof navigationHelpers.readStudioWorkspaceState !== "function"
|
|
220
|
+
|| typeof navigationHelpers.persistStudioWorkspaceState !== "function"
|
|
221
|
+
|| typeof navigationHelpers.clearStudioWorkspaceState !== "function"
|
|
222
|
+
) {
|
|
213
223
|
throw new Error("Studio navigation helpers failed to load.");
|
|
214
224
|
}
|
|
225
|
+
const showMeHelpers = globalThis.PiStudioShowMeHelpers;
|
|
226
|
+
if (!showMeHelpers || typeof showMeHelpers.chooseStudioShowMeFocus !== "function") {
|
|
227
|
+
throw new Error("Studio Show me helpers failed to load.");
|
|
228
|
+
}
|
|
229
|
+
const studioTabStateId = navigationHelpers.ensureStudioTabStateId(window);
|
|
215
230
|
const initialQueryParams = new URLSearchParams(window.location.search || "");
|
|
216
231
|
const initialPaneFocusTarget = navigationHelpers.readPaneFocusTarget(window.location);
|
|
217
232
|
const skipInitialWorkspaceRestore = initialQueryParams.get("skipWorkspaceRestore") === "1";
|
|
@@ -2059,8 +2074,9 @@
|
|
|
2059
2074
|
const PANE_SPLIT_MIN_PERCENT = 20;
|
|
2060
2075
|
const PANE_SPLIT_MAX_PERCENT = 80;
|
|
2061
2076
|
const PANE_SPLIT_SNAP_TO_CENTER_PERCENT = 1;
|
|
2062
|
-
const STUDIO_WORKSPACE_STORAGE_KEY = "piStudio.workspaceState.v1";
|
|
2063
2077
|
const STUDIO_WORKSPACE_MAX_TEXT_CHARS = 900_000;
|
|
2078
|
+
const STUDIO_WORKSPACE_PERSIST_INTERVAL_MS = 300;
|
|
2079
|
+
const STUDIO_WORKSPACE_RECOVERY_FETCH_TIMEOUT_MS = 1_500;
|
|
2064
2080
|
const EDITOR_HIGHLIGHT_MAX_CHARS = 100_000;
|
|
2065
2081
|
const EDITOR_HIGHLIGHT_STORAGE_KEY = "piStudio.editorHighlightEnabled";
|
|
2066
2082
|
const EDITOR_LANGUAGE_STORAGE_KEY = "piStudio.editorLanguage";
|
|
@@ -2217,6 +2233,8 @@
|
|
|
2217
2233
|
let workspacePersistenceReady = false;
|
|
2218
2234
|
let workspacePersistTimer = null;
|
|
2219
2235
|
let workspaceRestoredFromBrowser = false;
|
|
2236
|
+
let workspaceServerPersistenceBlocked = false;
|
|
2237
|
+
let lastWorkspacePersistenceSavedAt = 0;
|
|
2220
2238
|
let suppressedEditorSelectionStart = null;
|
|
2221
2239
|
let suppressedEditorSelectionEnd = null;
|
|
2222
2240
|
const previewJumpHighlightState = new WeakMap();
|
|
@@ -2528,6 +2546,7 @@
|
|
|
2528
2546
|
buttonEl.setAttribute("aria-expanded", "false");
|
|
2529
2547
|
buttonEl.addEventListener("click", (event) => {
|
|
2530
2548
|
event.stopPropagation();
|
|
2549
|
+
if (name === "review") syncShowMeButton();
|
|
2531
2550
|
if (name === "review" && getAbortablePendingKind() === "critique") {
|
|
2532
2551
|
requestCancelForPendingRequest("critique");
|
|
2533
2552
|
return;
|
|
@@ -2549,7 +2568,7 @@
|
|
|
2549
2568
|
if (!isEditorOnlyMode && critiqueBtn && lensSelect) {
|
|
2550
2569
|
const reviewButton = makeStudioUiRefreshElement("button", "studio-refresh-tool-tab studio-refresh-review-btn", "Review");
|
|
2551
2570
|
reviewMenu = makeStudioUiRefreshMenu(reviewButton, "review", "studio-refresh-review-anchor");
|
|
2552
|
-
appendStudioUiRefreshMenuSection(reviewMenu.menu, "Action", [critiqueBtn, quizBtn]);
|
|
2571
|
+
appendStudioUiRefreshMenuSection(reviewMenu.menu, "Action", [critiqueBtn, showMeBtn, showMeResponseBtn, quizBtn]);
|
|
2553
2572
|
appendStudioUiRefreshMenuSection(reviewMenu.menu, "Setting", [lensSelect]);
|
|
2554
2573
|
}
|
|
2555
2574
|
|
|
@@ -2875,7 +2894,7 @@
|
|
|
2875
2894
|
if (isEditorOnlyMode) {
|
|
2876
2895
|
return "Editor-only mode: edit, browse files, annotate, preview, save, suggest, refresh file-backed text, or send to a REPL.";
|
|
2877
2896
|
}
|
|
2878
|
-
return "Edit, load, or annotate text, then run, save,
|
|
2897
|
+
return "Edit, load, or annotate text, then run, save, review, or ask Studio to show the current structure.";
|
|
2879
2898
|
}
|
|
2880
2899
|
|
|
2881
2900
|
function normalizeTerminalPhase(phase) {
|
|
@@ -2943,6 +2962,7 @@
|
|
|
2943
2962
|
function getStudioActionLabel(kind) {
|
|
2944
2963
|
if (kind === "annotation") return "sending annotated reply";
|
|
2945
2964
|
if (kind === "critique") return "running critique";
|
|
2965
|
+
if (kind === "show-me") return "building a compact explanation";
|
|
2946
2966
|
if (kind === "direct") return "running editor text";
|
|
2947
2967
|
if (kind === "compact") return "compacting context";
|
|
2948
2968
|
if (kind === "send_to_editor") return "sending to pi editor";
|
|
@@ -3128,7 +3148,7 @@
|
|
|
3128
3148
|
}
|
|
3129
3149
|
|
|
3130
3150
|
function isTitleAttentionRequestKind(kind) {
|
|
3131
|
-
return kind === "annotation" || kind === "critique" || kind === "direct";
|
|
3151
|
+
return kind === "annotation" || kind === "critique" || kind === "show-me" || kind === "direct";
|
|
3132
3152
|
}
|
|
3133
3153
|
|
|
3134
3154
|
function armTitleAttentionForRequest(requestId, kind) {
|
|
@@ -3162,6 +3182,7 @@
|
|
|
3162
3182
|
|
|
3163
3183
|
function getTitleAttentionMessage(kind) {
|
|
3164
3184
|
if (kind === "critique") return "● Critique ready";
|
|
3185
|
+
if (kind === "show-me") return "● Visual explanation ready";
|
|
3165
3186
|
if (kind === "direct") return "● Response ready";
|
|
3166
3187
|
return "● Reply ready";
|
|
3167
3188
|
}
|
|
@@ -3199,6 +3220,7 @@
|
|
|
3199
3220
|
function getTitleActionMessage(kind) {
|
|
3200
3221
|
if (kind === "annotation") return "Replying…";
|
|
3201
3222
|
if (kind === "critique") return "Critiquing…";
|
|
3223
|
+
if (kind === "show-me") return "Showing…";
|
|
3202
3224
|
if (kind === "direct") return "Running…";
|
|
3203
3225
|
if (kind === "compact") return "Compacting…";
|
|
3204
3226
|
if (kind === "send_to_editor") return "Sending to editor…";
|
|
@@ -3239,6 +3261,7 @@
|
|
|
3239
3261
|
|
|
3240
3262
|
if (terminalActivityPhase === "responding") {
|
|
3241
3263
|
if (activeKind === "critique") return "Critiquing…";
|
|
3264
|
+
if (activeKind === "show-me") return "Showing…";
|
|
3242
3265
|
if (activeKind === "annotation") return "Replying…";
|
|
3243
3266
|
if (activeKind === "direct") return "Thinking…";
|
|
3244
3267
|
return "Working…";
|
|
@@ -3605,11 +3628,13 @@
|
|
|
3605
3628
|
});
|
|
3606
3629
|
|
|
3607
3630
|
document.addEventListener("visibilitychange", () => {
|
|
3608
|
-
if (
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3631
|
+
if (document.hidden) {
|
|
3632
|
+
flushWorkspacePersistence({ beacon: false });
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
windowHasFocus = typeof document.hasFocus === "function" ? document.hasFocus() : windowHasFocus;
|
|
3636
|
+
if (windowHasFocus) {
|
|
3637
|
+
clearTitleAttention();
|
|
3613
3638
|
}
|
|
3614
3639
|
});
|
|
3615
3640
|
|
|
@@ -3863,6 +3888,7 @@
|
|
|
3863
3888
|
document.body.classList.add("pane-focus-right");
|
|
3864
3889
|
}
|
|
3865
3890
|
updatePaneFocusButtons();
|
|
3891
|
+
updateResultActionButtons();
|
|
3866
3892
|
}
|
|
3867
3893
|
|
|
3868
3894
|
function persistPaneFocusUrlState() {
|
|
@@ -4416,7 +4442,7 @@
|
|
|
4416
4442
|
|
|
4417
4443
|
if (plainEscape) {
|
|
4418
4444
|
const activeKind = getAbortablePendingKind();
|
|
4419
|
-
if (activeKind === "direct" || activeKind === "critique") {
|
|
4445
|
+
if (activeKind === "direct" || activeKind === "critique" || activeKind === "show-me") {
|
|
4420
4446
|
event.preventDefault();
|
|
4421
4447
|
requestCancelForPendingRequest(activeKind);
|
|
4422
4448
|
return;
|
|
@@ -4494,7 +4520,10 @@
|
|
|
4494
4520
|
}
|
|
4495
4521
|
|
|
4496
4522
|
function normalizeHistoryKind(kind) {
|
|
4497
|
-
|
|
4523
|
+
if (kind === "critique") return "critique";
|
|
4524
|
+
if (kind === "show-me") return "show-me";
|
|
4525
|
+
if (kind === "direct") return "direct";
|
|
4526
|
+
return "annotation";
|
|
4498
4527
|
}
|
|
4499
4528
|
|
|
4500
4529
|
function normalizeTraceSummary(summary) {
|
|
@@ -4564,6 +4593,61 @@
|
|
|
4564
4593
|
return responseHistory[responseHistoryIndex] || null;
|
|
4565
4594
|
}
|
|
4566
4595
|
|
|
4596
|
+
function getStudioShowMeFocus(target) {
|
|
4597
|
+
const selection = getEditorSelectionRange();
|
|
4598
|
+
const selectedResponse = getSelectedHistoryItem();
|
|
4599
|
+
const responseText = selectedResponse && typeof selectedResponse.markdown === "string"
|
|
4600
|
+
? selectedResponse.markdown
|
|
4601
|
+
: latestResponseMarkdown;
|
|
4602
|
+
const total = Array.isArray(responseHistory) ? responseHistory.length : 0;
|
|
4603
|
+
const selected = total > 0 && responseHistoryIndex >= 0 && responseHistoryIndex < total
|
|
4604
|
+
? responseHistoryIndex + 1
|
|
4605
|
+
: 0;
|
|
4606
|
+
return showMeHelpers.chooseStudioShowMeFocus({
|
|
4607
|
+
target: target === "response" ? "response" : "editor",
|
|
4608
|
+
selectionText: selection.selected,
|
|
4609
|
+
responseText,
|
|
4610
|
+
responseVisible: (rightView === "markdown" || rightView === "preview") && paneFocusTarget !== "left",
|
|
4611
|
+
editorText: sourceTextEl.value,
|
|
4612
|
+
responseIndex: selected,
|
|
4613
|
+
responseTotal: total,
|
|
4614
|
+
});
|
|
4615
|
+
}
|
|
4616
|
+
|
|
4617
|
+
function syncShowMeButton() {
|
|
4618
|
+
if (!showMeBtn) return;
|
|
4619
|
+
const showMeIsStop = getAbortablePendingKind() === "show-me";
|
|
4620
|
+
const unavailableForRunChain = studioRunChainActive && !showMeIsStop;
|
|
4621
|
+
const editorFocus = getStudioShowMeFocus("editor");
|
|
4622
|
+
const responseFocus = getStudioShowMeFocus("response");
|
|
4623
|
+
const showEditorAction = showMeIsStop || editorFocus.sourceKind !== "context" || !responseFocus;
|
|
4624
|
+
showMeBtn.hidden = !showEditorAction;
|
|
4625
|
+
const editorMenuItem = showMeBtn.closest(".studio-refresh-menu-item");
|
|
4626
|
+
if (editorMenuItem) editorMenuItem.hidden = !showEditorAction;
|
|
4627
|
+
showMeBtn.textContent = showMeIsStop ? "Stop showing" : editorFocus.actionLabel;
|
|
4628
|
+
showMeBtn.classList.toggle("request-stop-active", showMeIsStop);
|
|
4629
|
+
showMeBtn.disabled = showMeIsStop
|
|
4630
|
+
? wsState === "Disconnected"
|
|
4631
|
+
: isEditorOnlyMode || wsState === "Disconnected" || uiBusy || unavailableForRunChain;
|
|
4632
|
+
showMeBtn.title = showMeIsStop
|
|
4633
|
+
? "Stop the running Show me request. Shortcut: Esc."
|
|
4634
|
+
: (isEditorOnlyMode
|
|
4635
|
+
? "Show me is unavailable in editor-only mode."
|
|
4636
|
+
: (unavailableForRunChain
|
|
4637
|
+
? "Show me is unavailable while Run editor text is active."
|
|
4638
|
+
: "Explain the " + editorFocus.sourceLabel + " using the smallest useful visual or structural representation."));
|
|
4639
|
+
if (showMeResponseBtn) {
|
|
4640
|
+
showMeResponseBtn.hidden = !responseFocus;
|
|
4641
|
+
const responseMenuItem = showMeResponseBtn.closest(".studio-refresh-menu-item");
|
|
4642
|
+
if (responseMenuItem) responseMenuItem.hidden = !responseFocus;
|
|
4643
|
+
showMeResponseBtn.disabled = !responseFocus || showMeIsStop || isEditorOnlyMode || wsState === "Disconnected" || uiBusy || unavailableForRunChain;
|
|
4644
|
+
showMeResponseBtn.textContent = "Explain displayed response";
|
|
4645
|
+
showMeResponseBtn.title = responseFocus
|
|
4646
|
+
? "Explain the " + responseFocus.sourceLabel + " using the smallest useful visual or structural representation."
|
|
4647
|
+
: "Available when a response is displayed in the right pane.";
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4567
4651
|
function syncTraceForSelectedHistoryItem() {
|
|
4568
4652
|
const item = getSelectedHistoryItem();
|
|
4569
4653
|
const total = Array.isArray(responseHistory) ? responseHistory.length : 0;
|
|
@@ -4651,6 +4735,7 @@
|
|
|
4651
4735
|
: "Load the prompt that generated the selected response into the editor.")
|
|
4652
4736
|
: "Prompt unavailable for the selected response.";
|
|
4653
4737
|
}
|
|
4738
|
+
syncShowMeButton();
|
|
4654
4739
|
}
|
|
4655
4740
|
|
|
4656
4741
|
function applySelectedHistoryItem(options) {
|
|
@@ -4685,7 +4770,9 @@
|
|
|
4685
4770
|
if (applied && !(options && options.silent)) {
|
|
4686
4771
|
const item = getSelectedHistoryItem();
|
|
4687
4772
|
if (item) {
|
|
4688
|
-
const responseLabel = item.kind === "critique"
|
|
4773
|
+
const responseLabel = item.kind === "critique"
|
|
4774
|
+
? "critique"
|
|
4775
|
+
: (item.kind === "show-me" ? "visual explanation" : "response");
|
|
4689
4776
|
setStatus("Viewing " + responseLabel + " in current branch history " + (nextIndex + 1) + "/" + total + ".");
|
|
4690
4777
|
}
|
|
4691
4778
|
}
|
|
@@ -4827,7 +4914,9 @@
|
|
|
4827
4914
|
}
|
|
4828
4915
|
|
|
4829
4916
|
const time = formatReferenceTime(latestResponseTimestamp);
|
|
4830
|
-
const responseLabel = latestResponseKind === "critique"
|
|
4917
|
+
const responseLabel = latestResponseKind === "critique"
|
|
4918
|
+
? "assistant critique"
|
|
4919
|
+
: (latestResponseKind === "show-me" ? "visual explanation" : "assistant response");
|
|
4831
4920
|
const total = Array.isArray(responseHistory) ? responseHistory.length : 0;
|
|
4832
4921
|
const selected = total > 0 && responseHistoryIndex >= 0 && responseHistoryIndex < total
|
|
4833
4922
|
? responseHistoryIndex + 1
|
|
@@ -10971,12 +11060,23 @@
|
|
|
10971
11060
|
const critiqueNotesLoaded = Boolean(critiqueNotes) && normalizedEditor === latestCritiqueNotesNormalized;
|
|
10972
11061
|
|
|
10973
11062
|
loadResponseBtn.hidden = isCritiqueResponse;
|
|
11063
|
+
annotateResponseBtn.hidden = isCritiqueResponse;
|
|
10974
11064
|
loadCritiqueNotesBtn.hidden = !isCritiqueResponse;
|
|
10975
11065
|
loadCritiqueFullBtn.hidden = !isCritiqueResponse;
|
|
10976
11066
|
|
|
10977
11067
|
loadResponseBtn.disabled = uiBusy || !hasResponse || responseLoaded || isCritiqueResponse;
|
|
10978
11068
|
loadResponseBtn.textContent = responseLoaded ? "Response already in editor" : "Load response into editor";
|
|
10979
11069
|
|
|
11070
|
+
const annotationWorkspaceReady = responseLoaded
|
|
11071
|
+
&& editorView === "markdown"
|
|
11072
|
+
&& rightView === "editor-preview"
|
|
11073
|
+
&& paneFocusTarget === "off";
|
|
11074
|
+
annotateResponseBtn.disabled = uiBusy || !hasResponse || isCritiqueResponse || annotationWorkspaceReady;
|
|
11075
|
+
annotateResponseBtn.textContent = annotationWorkspaceReady ? "Response ready to annotate" : "Annotate response";
|
|
11076
|
+
annotateResponseBtn.title = annotationWorkspaceReady
|
|
11077
|
+
? "The selected response is exactly in the raw editor with Editor Preview open."
|
|
11078
|
+
: "Load the selected response into the raw editor and show Editor Preview. This replaces the current editor text.";
|
|
11079
|
+
|
|
10980
11080
|
loadCritiqueNotesBtn.disabled = uiBusy || !isCritiqueResponse || !critiqueNotes || critiqueNotesLoaded;
|
|
10981
11081
|
loadCritiqueNotesBtn.textContent = critiqueNotesLoaded ? "Critique notes already in editor" : "Load critique notes into editor";
|
|
10982
11082
|
|
|
@@ -11061,6 +11161,7 @@
|
|
|
11061
11161
|
|
|
11062
11162
|
updateSyncBadge(normalizedEditor);
|
|
11063
11163
|
syncStudioQuartoDirtyUi();
|
|
11164
|
+
syncShowMeButton();
|
|
11064
11165
|
}
|
|
11065
11166
|
|
|
11066
11167
|
function refreshResponseUi() {
|
|
@@ -11377,34 +11478,46 @@
|
|
|
11377
11478
|
return "source:" + normalized.source + ":" + normalized.label;
|
|
11378
11479
|
}
|
|
11379
11480
|
|
|
11380
|
-
function
|
|
11481
|
+
function readPersistedWorkspaceState() {
|
|
11381
11482
|
try {
|
|
11382
|
-
return window
|
|
11483
|
+
return navigationHelpers.readStudioWorkspaceState(window, studioTabStateId);
|
|
11383
11484
|
} catch {
|
|
11384
11485
|
return null;
|
|
11385
11486
|
}
|
|
11386
11487
|
}
|
|
11387
11488
|
|
|
11388
|
-
function
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
|
|
11392
|
-
|
|
11393
|
-
|
|
11394
|
-
function readPersistedWorkspaceState() {
|
|
11489
|
+
async function readServerWorkspaceRecoveryState() {
|
|
11490
|
+
if (skipInitialWorkspaceRestore) return { status: "skipped", state: null };
|
|
11491
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
11492
|
+
const timer = controller
|
|
11493
|
+
? window.setTimeout(() => controller.abort(), STUDIO_WORKSPACE_RECOVERY_FETCH_TIMEOUT_MS)
|
|
11494
|
+
: null;
|
|
11395
11495
|
try {
|
|
11396
|
-
const
|
|
11397
|
-
|
|
11398
|
-
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
return parsed;
|
|
11496
|
+
const payload = await fetchStudioJson("/tab-workspace-state", {
|
|
11497
|
+
query: { tabStateId: studioTabStateId },
|
|
11498
|
+
signal: controller ? controller.signal : undefined,
|
|
11499
|
+
});
|
|
11500
|
+
const state = payload && payload.state && typeof payload.state === "object" ? payload.state : null;
|
|
11501
|
+
return { status: state ? "found" : "empty", state };
|
|
11403
11502
|
} catch {
|
|
11404
|
-
return null;
|
|
11503
|
+
return { status: "unavailable", state: null };
|
|
11504
|
+
} finally {
|
|
11505
|
+
if (timer !== null) window.clearTimeout(timer);
|
|
11405
11506
|
}
|
|
11406
11507
|
}
|
|
11407
11508
|
|
|
11509
|
+
function getWorkspaceStateSavedAt(state) {
|
|
11510
|
+
return state && typeof state.savedAt === "number" && Number.isFinite(state.savedAt)
|
|
11511
|
+
? state.savedAt
|
|
11512
|
+
: 0;
|
|
11513
|
+
}
|
|
11514
|
+
|
|
11515
|
+
function chooseNewestRestorableWorkspaceState(states) {
|
|
11516
|
+
return states
|
|
11517
|
+
.filter((state) => shouldRestorePersistedWorkspaceState(state))
|
|
11518
|
+
.sort((a, b) => getWorkspaceStateSavedAt(b) - getWorkspaceStateSavedAt(a))[0] || null;
|
|
11519
|
+
}
|
|
11520
|
+
|
|
11408
11521
|
function shouldRestorePersistedWorkspaceState(state) {
|
|
11409
11522
|
if (skipInitialWorkspaceRestore) return false;
|
|
11410
11523
|
if (!state || typeof state.text !== "string") return false;
|
|
@@ -11417,9 +11530,10 @@
|
|
|
11417
11530
|
}
|
|
11418
11531
|
|
|
11419
11532
|
function buildWorkspacePersistencePayload() {
|
|
11533
|
+
lastWorkspacePersistenceSavedAt = Math.max(Date.now(), lastWorkspacePersistenceSavedAt + 1);
|
|
11420
11534
|
return {
|
|
11421
11535
|
version: 1,
|
|
11422
|
-
savedAt:
|
|
11536
|
+
savedAt: lastWorkspacePersistenceSavedAt,
|
|
11423
11537
|
sourceState: normalizeWorkspaceSourceState(sourceState),
|
|
11424
11538
|
resourceDir: getCurrentResourceDirValue(),
|
|
11425
11539
|
editorView,
|
|
@@ -11434,38 +11548,63 @@
|
|
|
11434
11548
|
};
|
|
11435
11549
|
}
|
|
11436
11550
|
|
|
11437
|
-
function
|
|
11551
|
+
function sendServerWorkspaceRecoveryState(payload, options) {
|
|
11552
|
+
if ((options && options.skipServer) || (workspaceServerPersistenceBlocked && !(options && options.forceServer))) return;
|
|
11553
|
+
const body = { tabStateId: studioTabStateId, state: payload };
|
|
11554
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
11555
|
+
try {
|
|
11556
|
+
ws.send(JSON.stringify({ type: "workspace_state_update", ...body }));
|
|
11557
|
+
return;
|
|
11558
|
+
} catch {
|
|
11559
|
+
// Fall through to the authenticated HTTP path.
|
|
11560
|
+
}
|
|
11561
|
+
}
|
|
11562
|
+
if (options && options.beacon && trySendStudioJsonBeacon("/tab-workspace-state", body)) return;
|
|
11563
|
+
void fetchStudioJson("/tab-workspace-state", {
|
|
11564
|
+
method: "POST",
|
|
11565
|
+
body: JSON.stringify(body),
|
|
11566
|
+
keepalive: Boolean(options && options.beacon),
|
|
11567
|
+
}).catch(() => {
|
|
11568
|
+
// Session storage remains the in-page fallback if server recovery fails.
|
|
11569
|
+
});
|
|
11570
|
+
}
|
|
11571
|
+
|
|
11572
|
+
function persistWorkspaceStateNow(options) {
|
|
11438
11573
|
if (!workspacePersistenceReady) return;
|
|
11439
11574
|
try {
|
|
11440
|
-
const storage = getWorkspacePersistenceStorage();
|
|
11441
|
-
if (!storage) return;
|
|
11442
|
-
clearLegacyWorkspacePersistenceStorage();
|
|
11443
11575
|
const payload = buildWorkspacePersistencePayload();
|
|
11444
11576
|
if (payload.text.length > STUDIO_WORKSPACE_MAX_TEXT_CHARS) {
|
|
11445
|
-
|
|
11577
|
+
navigationHelpers.clearStudioWorkspaceState(window, studioTabStateId);
|
|
11578
|
+
sendServerWorkspaceRecoveryState({
|
|
11579
|
+
...payload,
|
|
11580
|
+
sourceState: { source: "recovery-omitted", label: "oversized editor", path: null, draftId: null },
|
|
11581
|
+
resourceDir: "",
|
|
11582
|
+
rightView: "editor-preview",
|
|
11583
|
+
text: "",
|
|
11584
|
+
}, { ...(options || {}), forceServer: true });
|
|
11446
11585
|
return;
|
|
11447
11586
|
}
|
|
11448
|
-
|
|
11587
|
+
navigationHelpers.persistStudioWorkspaceState(window, studioTabStateId, payload);
|
|
11588
|
+
sendServerWorkspaceRecoveryState(payload, options);
|
|
11449
11589
|
} catch {
|
|
11450
|
-
// Ignore browser storage
|
|
11590
|
+
// Ignore browser storage, serialization, and recovery-request failures.
|
|
11451
11591
|
}
|
|
11452
11592
|
}
|
|
11453
11593
|
|
|
11454
11594
|
function scheduleWorkspacePersistence() {
|
|
11455
|
-
if (!workspacePersistenceReady) return;
|
|
11456
|
-
if (workspacePersistTimer !== null) window.clearTimeout(workspacePersistTimer);
|
|
11595
|
+
if (!workspacePersistenceReady || workspacePersistTimer !== null) return;
|
|
11457
11596
|
workspacePersistTimer = window.setTimeout(() => {
|
|
11458
11597
|
workspacePersistTimer = null;
|
|
11459
11598
|
persistWorkspaceStateNow();
|
|
11460
|
-
},
|
|
11599
|
+
}, STUDIO_WORKSPACE_PERSIST_INTERVAL_MS);
|
|
11461
11600
|
}
|
|
11462
11601
|
|
|
11463
|
-
function flushWorkspacePersistence() {
|
|
11602
|
+
function flushWorkspacePersistence(options) {
|
|
11464
11603
|
if (workspacePersistTimer !== null) {
|
|
11465
11604
|
window.clearTimeout(workspacePersistTimer);
|
|
11466
11605
|
workspacePersistTimer = null;
|
|
11467
11606
|
}
|
|
11468
|
-
persistWorkspaceStateNow();
|
|
11607
|
+
persistWorkspaceStateNow(options || { beacon: true });
|
|
11469
11608
|
}
|
|
11470
11609
|
|
|
11471
11610
|
function clearPersistedWorkspaceState() {
|
|
@@ -11474,10 +11613,16 @@
|
|
|
11474
11613
|
workspacePersistTimer = null;
|
|
11475
11614
|
}
|
|
11476
11615
|
try {
|
|
11477
|
-
|
|
11478
|
-
|
|
11616
|
+
navigationHelpers.clearStudioWorkspaceState(window, studioTabStateId);
|
|
11617
|
+
const marker = buildWorkspacePersistencePayload();
|
|
11618
|
+
sendServerWorkspaceRecoveryState({
|
|
11619
|
+
...marker,
|
|
11620
|
+
sourceState: { source: "recovery-cleared", label: "cleared editor", path: null, draftId: null },
|
|
11621
|
+
resourceDir: "",
|
|
11622
|
+
rightView: "editor-preview",
|
|
11623
|
+
text: "",
|
|
11624
|
+
}, { beacon: true, forceServer: true });
|
|
11479
11625
|
} catch {}
|
|
11480
|
-
clearLegacyWorkspacePersistenceStorage();
|
|
11481
11626
|
}
|
|
11482
11627
|
|
|
11483
11628
|
function applyPersistedWorkspaceState(state) {
|
|
@@ -12555,6 +12700,8 @@
|
|
|
12555
12700
|
headers,
|
|
12556
12701
|
body: init.body,
|
|
12557
12702
|
cache: "no-store",
|
|
12703
|
+
signal: init.signal,
|
|
12704
|
+
keepalive: init.keepalive === true,
|
|
12558
12705
|
});
|
|
12559
12706
|
let payload = null;
|
|
12560
12707
|
try {
|
|
@@ -19202,7 +19349,7 @@
|
|
|
19202
19349
|
|
|
19203
19350
|
function getAbortablePendingKind() {
|
|
19204
19351
|
if (!pendingRequestId) return null;
|
|
19205
|
-
return pendingKind === "direct" || pendingKind === "critique" ? pendingKind : null;
|
|
19352
|
+
return pendingKind === "direct" || pendingKind === "critique" || pendingKind === "show-me" ? pendingKind : null;
|
|
19206
19353
|
}
|
|
19207
19354
|
|
|
19208
19355
|
function requestCancelForPendingRequest(expectedKind) {
|
|
@@ -19345,6 +19492,7 @@
|
|
|
19345
19492
|
? "Resume the current Studio quiz."
|
|
19346
19493
|
: "Open an active quiz for the current editor selection or document.");
|
|
19347
19494
|
}
|
|
19495
|
+
syncShowMeButton();
|
|
19348
19496
|
syncStudioUiRefreshReviewTrigger();
|
|
19349
19497
|
}
|
|
19350
19498
|
|
|
@@ -19427,7 +19575,7 @@
|
|
|
19427
19575
|
? timestamp
|
|
19428
19576
|
: Date.now();
|
|
19429
19577
|
const responseThinking = typeof thinking === "string" ? thinking : "";
|
|
19430
|
-
const responseKind = kind
|
|
19578
|
+
const responseKind = normalizeHistoryKind(kind);
|
|
19431
19579
|
const resetScroll = options && Object.prototype.hasOwnProperty.call(options, "resetScroll")
|
|
19432
19580
|
? Boolean(options.resetScroll)
|
|
19433
19581
|
: (
|
|
@@ -19445,7 +19593,7 @@
|
|
|
19445
19593
|
latestResponseThinking = responseThinking;
|
|
19446
19594
|
latestResponseKind = responseKind;
|
|
19447
19595
|
latestResponseTimestamp = responseTimestamp;
|
|
19448
|
-
latestResponseIsStructuredCritique = isStructuredCritique(markdown);
|
|
19596
|
+
latestResponseIsStructuredCritique = responseKind === "critique" && isStructuredCritique(markdown);
|
|
19449
19597
|
latestResponseHasContent = Boolean(markdown && markdown.trim());
|
|
19450
19598
|
latestResponseNormalized = normalizeForCompare(markdown);
|
|
19451
19599
|
latestResponseThinkingNormalized = normalizeForCompare(latestResponseThinking);
|
|
@@ -19463,7 +19611,7 @@
|
|
|
19463
19611
|
|
|
19464
19612
|
function applyLatestPayload(payload, options) {
|
|
19465
19613
|
if (!payload || typeof payload.markdown !== "string") return false;
|
|
19466
|
-
const responseKind = payload.kind
|
|
19614
|
+
const responseKind = normalizeHistoryKind(payload.kind);
|
|
19467
19615
|
handleIncomingResponse(payload.markdown, responseKind, payload.timestamp, payload.thinking, options);
|
|
19468
19616
|
return true;
|
|
19469
19617
|
}
|
|
@@ -19674,8 +19822,8 @@
|
|
|
19674
19822
|
if (!appliedHistory && message.lastResponse && typeof message.lastResponse.markdown === "string") {
|
|
19675
19823
|
const lastMarkdown = message.lastResponse.markdown;
|
|
19676
19824
|
const lastResponseKind =
|
|
19677
|
-
message.lastResponse.kind === "
|
|
19678
|
-
?
|
|
19825
|
+
typeof message.lastResponse.kind === "string"
|
|
19826
|
+
? normalizeHistoryKind(message.lastResponse.kind)
|
|
19679
19827
|
: (isStructuredCritique(lastMarkdown) ? "critique" : "annotation");
|
|
19680
19828
|
handleIncomingResponse(lastMarkdown, lastResponseKind, message.lastResponse.timestamp, message.lastResponse.thinking);
|
|
19681
19829
|
}
|
|
@@ -19919,7 +20067,7 @@
|
|
|
19919
20067
|
const responseKind =
|
|
19920
20068
|
typeof message.kind === "string"
|
|
19921
20069
|
? message.kind
|
|
19922
|
-
: (pendingKind
|
|
20070
|
+
: normalizeHistoryKind(pendingKind);
|
|
19923
20071
|
|
|
19924
20072
|
stickyStudioKind = responseKind;
|
|
19925
20073
|
pendingRequestId = null;
|
|
@@ -19944,6 +20092,8 @@
|
|
|
19944
20092
|
|
|
19945
20093
|
if (responseKind === "critique") {
|
|
19946
20094
|
setStatus("Critique ready.", "success");
|
|
20095
|
+
} else if (responseKind === "show-me") {
|
|
20096
|
+
setStatus("Visual explanation ready.", "success");
|
|
19947
20097
|
} else if (responseKind === "direct") {
|
|
19948
20098
|
setStatus("Model response ready.", "success");
|
|
19949
20099
|
} else {
|
|
@@ -19970,7 +20120,7 @@
|
|
|
19970
20120
|
|
|
19971
20121
|
if (typeof message.markdown === "string") {
|
|
19972
20122
|
const payload = {
|
|
19973
|
-
kind: message.kind
|
|
20123
|
+
kind: normalizeHistoryKind(message.kind),
|
|
19974
20124
|
markdown: message.markdown,
|
|
19975
20125
|
thinking: typeof message.thinking === "string" ? message.thinking : null,
|
|
19976
20126
|
timestamp: message.timestamp,
|
|
@@ -20909,6 +21059,7 @@
|
|
|
20909
21059
|
}
|
|
20910
21060
|
}
|
|
20911
21061
|
updateEditorSelectionCommentUi();
|
|
21062
|
+
syncShowMeButton();
|
|
20912
21063
|
});
|
|
20913
21064
|
|
|
20914
21065
|
sourceTextEl.addEventListener("keyup", () => {
|
|
@@ -20987,20 +21138,94 @@
|
|
|
20987
21138
|
}
|
|
20988
21139
|
});
|
|
20989
21140
|
|
|
21141
|
+
function submitStudioShowMe(target) {
|
|
21142
|
+
const focus = getStudioShowMeFocus(target);
|
|
21143
|
+
if (!focus) {
|
|
21144
|
+
setStatus("That explanation source is no longer displayed.", "warning");
|
|
21145
|
+
syncShowMeButton();
|
|
21146
|
+
return;
|
|
21147
|
+
}
|
|
21148
|
+
const requestId = beginUiAction("show-me");
|
|
21149
|
+
if (!requestId) return;
|
|
21150
|
+
closeStudioUiRefreshMenus();
|
|
21151
|
+
|
|
21152
|
+
const sent = sendMessage({
|
|
21153
|
+
type: "show_me_request",
|
|
21154
|
+
requestId,
|
|
21155
|
+
sourceKind: focus.sourceKind,
|
|
21156
|
+
sourceLabel: focus.sourceLabel,
|
|
21157
|
+
sourceText: focus.sourceText,
|
|
21158
|
+
});
|
|
21159
|
+
|
|
21160
|
+
if (!sent) {
|
|
21161
|
+
pendingRequestId = null;
|
|
21162
|
+
pendingKind = null;
|
|
21163
|
+
setBusy(false);
|
|
21164
|
+
}
|
|
21165
|
+
}
|
|
21166
|
+
|
|
21167
|
+
if (showMeBtn) {
|
|
21168
|
+
showMeBtn.addEventListener("click", () => {
|
|
21169
|
+
if (getAbortablePendingKind() === "show-me") {
|
|
21170
|
+
requestCancelForPendingRequest("show-me");
|
|
21171
|
+
return;
|
|
21172
|
+
}
|
|
21173
|
+
submitStudioShowMe("editor");
|
|
21174
|
+
});
|
|
21175
|
+
}
|
|
21176
|
+
|
|
21177
|
+
if (showMeResponseBtn) {
|
|
21178
|
+
showMeResponseBtn.addEventListener("click", () => {
|
|
21179
|
+
if (getAbortablePendingKind() === "show-me") return;
|
|
21180
|
+
submitStudioShowMe("response");
|
|
21181
|
+
});
|
|
21182
|
+
}
|
|
21183
|
+
|
|
20990
21184
|
if (quizBtn) {
|
|
20991
21185
|
quizBtn.addEventListener("click", () => {
|
|
20992
21186
|
openQuizOverlay();
|
|
20993
21187
|
});
|
|
20994
21188
|
}
|
|
20995
21189
|
|
|
20996
|
-
|
|
21190
|
+
function loadSelectedResponseIntoEditor(options) {
|
|
20997
21191
|
if (!latestResponseMarkdown.trim()) {
|
|
20998
21192
|
setStatus("No response available yet.", "warning");
|
|
20999
|
-
return;
|
|
21193
|
+
return false;
|
|
21194
|
+
}
|
|
21195
|
+
const prepareForAnnotation = Boolean(options && options.annotate);
|
|
21196
|
+
const currentEditorText = String(sourceTextEl.value || "");
|
|
21197
|
+
const replacingEditedResponse = prepareForAnnotation
|
|
21198
|
+
&& sourceState.source === "last-response"
|
|
21199
|
+
&& Boolean(currentEditorText.trim())
|
|
21200
|
+
&& normalizeForCompare(currentEditorText) !== latestResponseNormalized;
|
|
21201
|
+
if (
|
|
21202
|
+
replacingEditedResponse
|
|
21203
|
+
&& !window.confirm("Replace your edited response with a fresh copy? Existing edits and annotations will be lost.")
|
|
21204
|
+
) {
|
|
21205
|
+
setStatus("Kept the current editor text.");
|
|
21206
|
+
return false;
|
|
21000
21207
|
}
|
|
21001
21208
|
setEditorText(latestResponseMarkdown, { preserveScroll: false, preserveSelection: false });
|
|
21002
21209
|
setSourceState({ source: "last-response", label: "last model response", path: null });
|
|
21210
|
+
if (prepareForAnnotation) {
|
|
21211
|
+
if (editorView !== "markdown") setEditorView("markdown");
|
|
21212
|
+
exitPaneFocus();
|
|
21213
|
+
setRightView("editor-preview");
|
|
21214
|
+
setActivePane("left");
|
|
21215
|
+
window.setTimeout(focusSourceTextNoScroll, 0);
|
|
21216
|
+
setStatus("Response loaded and Editor Preview opened for annotation.", "success");
|
|
21217
|
+
return true;
|
|
21218
|
+
}
|
|
21003
21219
|
setStatus("Loaded response into editor.", "success");
|
|
21220
|
+
return true;
|
|
21221
|
+
}
|
|
21222
|
+
|
|
21223
|
+
loadResponseBtn.addEventListener("click", () => {
|
|
21224
|
+
loadSelectedResponseIntoEditor();
|
|
21225
|
+
});
|
|
21226
|
+
|
|
21227
|
+
annotateResponseBtn.addEventListener("click", () => {
|
|
21228
|
+
loadSelectedResponseIntoEditor({ annotate: true });
|
|
21004
21229
|
});
|
|
21005
21230
|
|
|
21006
21231
|
loadCritiqueNotesBtn.addEventListener("click", () => {
|
|
@@ -21892,13 +22117,26 @@
|
|
|
21892
22117
|
setAnnotationsEnabled(initialAnnotationsEnabled, { silent: true });
|
|
21893
22118
|
setReplSendMode(replSendMode);
|
|
21894
22119
|
|
|
21895
|
-
const
|
|
22120
|
+
const sessionWorkspaceState = readPersistedWorkspaceState();
|
|
22121
|
+
const serverWorkspaceRecovery = await readServerWorkspaceRecoveryState();
|
|
22122
|
+
workspaceServerPersistenceBlocked = serverWorkspaceRecovery.status === "unavailable";
|
|
22123
|
+
const serverWorkspaceState = serverWorkspaceRecovery.state;
|
|
22124
|
+
lastWorkspacePersistenceSavedAt = Math.max(
|
|
22125
|
+
lastWorkspacePersistenceSavedAt,
|
|
22126
|
+
getWorkspaceStateSavedAt(sessionWorkspaceState),
|
|
22127
|
+
getWorkspaceStateSavedAt(serverWorkspaceState),
|
|
22128
|
+
);
|
|
22129
|
+
const persistedWorkspaceState = chooseNewestRestorableWorkspaceState([
|
|
22130
|
+
sessionWorkspaceState,
|
|
22131
|
+
serverWorkspaceState,
|
|
22132
|
+
]);
|
|
21896
22133
|
applyPersistedWorkspaceState(persistedWorkspaceState);
|
|
21897
22134
|
|
|
21898
22135
|
setEditorView(editorView);
|
|
21899
22136
|
setRightView(rightView);
|
|
21900
22137
|
renderSourcePreview();
|
|
21901
22138
|
workspacePersistenceReady = true;
|
|
22139
|
+
persistWorkspaceStateNow({ skipServer: serverWorkspaceRecovery.status === "unavailable" });
|
|
21902
22140
|
if (workspaceRestoredFromBrowser) {
|
|
21903
22141
|
setStatus("Restored editor workspace from this browser tab. Use Reset editor to discard it.", "success");
|
|
21904
22142
|
}
|