pi-studio 0.9.16 → 0.9.17
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 +7 -0
- package/README.md +1 -1
- package/client/studio-client.js +165 -42
- package/client/studio.css +52 -10
- package/index.ts +63 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.17] — 2026-05-25
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Escape now dismisses a visible editor completion suggestion before falling through to other Studio Escape actions.
|
|
11
|
+
- Added a compact **Source & context** dropdown beside the editor-mode selector, with editor-only suggestions by default and an optional editor-plus-latest-response context mode; in-flight suggestion requests can now be stopped from the **Suggest** button.
|
|
12
|
+
- Kept Zen mode focused by hiding **Suggest** with the other secondary editor utilities while still showing **Send to REPL** controls when the REPL pane is active.
|
|
13
|
+
|
|
7
14
|
## [0.9.16] — 2026-05-24
|
|
8
15
|
|
|
9
16
|
### Added
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace
|
|
|
21
21
|
- Opens a two-pane browser workspace: **Editor** (left) + **Response/Working/Editor Preview** (right)
|
|
22
22
|
- Supports one canonical full Studio view per Pi session, plus additional editor-only companion views when you just want extra editing/preview surfaces; the editor toolbar can open a detached copy of the current editor text as a companion view
|
|
23
23
|
- Includes a global **Zen** mode for hiding secondary Studio chrome without changing the current left/right pane layout
|
|
24
|
-
- Runs editor text directly, asks for structured critique (auto/writing/code focus), offers a manual **Suggest completion** action for short cursor-aware continuations (`Option/Alt+Tab` where available or `Cmd/Ctrl+Shift+Space` from the editor, `Tab` to insert a visible suggestion), or opens **Quiz me** for a Studio-native active-recall loop over the current editor text, selection, current file, folder, or repo, with optional focus guidance for shaping question selection
|
|
24
|
+
- Runs editor text directly, asks for structured critique (auto/writing/code focus), offers a manual **Suggest completion** action for short cursor-aware continuations (`Option/Alt+Tab` where available or `Cmd/Ctrl+Shift+Space` from the editor, `Tab` to insert a visible suggestion) with an optional editor-plus-latest-response context mode, or opens **Quiz me** for a Studio-native active-recall loop over the current editor text, selection, current file, folder, or repo, with optional focus guidance for shaping question selection
|
|
25
25
|
- Includes a live **Working** view for following current model/tool activity, with `All` / `Thinking` / `Tools` filters, image previews for image-producing tool outputs, plus **Load visible into editor** and **Copy visible** actions; when cycling response history, Working follows saved working details for the selected response when available
|
|
26
26
|
- Includes a right-pane **Files** view for browsing the current Pi session/resource directory, opening folders, loading text/code documents into the editor, previewing PDFs/images, copying paths, and revealing files in the file manager
|
|
27
27
|
- Includes an optional tmux-backed **REPL** view for Shell, Python, IPython, Julia, R, GHCi, and Clojure sessions, with Raw/Literate send modes, `Cmd/Ctrl+Shift+Enter` **Send to REPL**, session start/stop/interrupt controls, a compact refresh-persistent **Studio REPL Record** of user and Pi-sent code, a secondary raw tmux mirror, agent-facing `studio_repl_status` / `studio_repl_send` tools, and Markdown/PDF/HTML export
|
package/client/studio-client.js
CHANGED
|
@@ -115,6 +115,8 @@
|
|
|
115
115
|
const replSendModeSelect = document.getElementById("replSendModeSelect");
|
|
116
116
|
const copyDraftBtn = document.getElementById("copyDraftBtn");
|
|
117
117
|
const suggestCompletionBtn = document.getElementById("suggestCompletionBtn");
|
|
118
|
+
const suggestCompletionOptionsBtn = document.getElementById("suggestCompletionOptionsBtn");
|
|
119
|
+
const completionContextSelect = document.getElementById("completionContextSelect");
|
|
118
120
|
const completionSuggestionPanelEl = document.getElementById("completionSuggestionPanel");
|
|
119
121
|
const completionSuggestionTextEl = document.getElementById("completionSuggestionText");
|
|
120
122
|
const completionSuggestionInsertBtn = document.getElementById("completionSuggestionInsertBtn");
|
|
@@ -256,6 +258,8 @@
|
|
|
256
258
|
const HTML_ARTIFACT_RESOURCE_FETCH_TIMEOUT_MS = 30_000;
|
|
257
259
|
const EDITOR_TAB_TEXT = " ";
|
|
258
260
|
const QUIZ_DEFAULT_COUNT = 5;
|
|
261
|
+
const COMPLETION_CONTEXT_STORAGE_KEY = "piStudio.completionContextMode";
|
|
262
|
+
const COMPLETION_CONTEXT_MAX_CHARS = 12000;
|
|
259
263
|
const QUIZ_SCOPES = ["editor", "selection", "file", "folder", "repo"];
|
|
260
264
|
const QUIZ_ANGLES = ["general", "scientist", "mathematician", "statistician", "developer", "reviewer"];
|
|
261
265
|
const QUIZ_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
@@ -1949,6 +1953,7 @@
|
|
|
1949
1953
|
let editorLanguage = "markdown";
|
|
1950
1954
|
let responseHighlightEnabled = false;
|
|
1951
1955
|
let completionSuggestionState = null;
|
|
1956
|
+
let completionSuggestionContextMode = readCompletionSuggestionContextMode();
|
|
1952
1957
|
let completionSuggestionInFlight = false;
|
|
1953
1958
|
let completionSuggestionRequestId = null;
|
|
1954
1959
|
let completionSuggestionPendingSnapshot = null;
|
|
@@ -2339,6 +2344,34 @@
|
|
|
2339
2344
|
appendStudioUiRefreshMenuSection(reviewMenu.menu, "Setting", [lensSelect]);
|
|
2340
2345
|
}
|
|
2341
2346
|
|
|
2347
|
+
let contextMenu = null;
|
|
2348
|
+
if (suggestCompletionOptionsBtn) {
|
|
2349
|
+
suggestCompletionOptionsBtn.hidden = false;
|
|
2350
|
+
if (completionContextSelect) completionContextSelect.hidden = true;
|
|
2351
|
+
contextMenu = makeStudioUiRefreshMenu(suggestCompletionOptionsBtn, "context", "studio-refresh-context-anchor");
|
|
2352
|
+
if (sourceBadgeEl) appendStudioUiRefreshMenuSection(contextMenu.menu, "Document", [sourceBadgeEl]);
|
|
2353
|
+
appendStudioUiRefreshMenuSection(contextMenu.menu, "Working directory", [resourceDirBtn, resourceDirLabel, resourceDirInputWrap]);
|
|
2354
|
+
const cursorContextBtn = makeStudioUiRefreshElement("button", "completion-context-option", "Editor only");
|
|
2355
|
+
cursorContextBtn.type = "button";
|
|
2356
|
+
cursorContextBtn.setAttribute("data-completion-context-mode", "cursor");
|
|
2357
|
+
const sessionContextBtn = makeStudioUiRefreshElement("button", "completion-context-option", "Editor + latest response");
|
|
2358
|
+
sessionContextBtn.type = "button";
|
|
2359
|
+
sessionContextBtn.setAttribute("data-completion-context-mode", "session");
|
|
2360
|
+
[cursorContextBtn, sessionContextBtn].forEach((button) => {
|
|
2361
|
+
button.addEventListener("click", (event) => {
|
|
2362
|
+
event.preventDefault();
|
|
2363
|
+
event.stopPropagation();
|
|
2364
|
+
setCompletionSuggestionContextMode(button.getAttribute("data-completion-context-mode") === "session" ? "session" : "cursor");
|
|
2365
|
+
syncActionButtons();
|
|
2366
|
+
});
|
|
2367
|
+
});
|
|
2368
|
+
appendStudioUiRefreshMenuSection(contextMenu.menu, "Suggestions", [cursorContextBtn, sessionContextBtn]);
|
|
2369
|
+
if (syncBadgeEl) {
|
|
2370
|
+
syncBadgeEl.hidden = false;
|
|
2371
|
+
appendStudioUiRefreshMenuSection(contextMenu.menu, "Status", [syncBadgeEl]);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2342
2375
|
const headerTopEl = makeStudioUiRefreshElement("div", "studio-refresh-header-top");
|
|
2343
2376
|
const titleGroupEl = makeStudioUiRefreshElement("div", "studio-refresh-title-group");
|
|
2344
2377
|
if (leftFocusBtn) {
|
|
@@ -2351,6 +2384,10 @@
|
|
|
2351
2384
|
} else if (editorViewSelect) {
|
|
2352
2385
|
titleGroupEl.appendChild(editorViewSelect);
|
|
2353
2386
|
}
|
|
2387
|
+
if (contextMenu) {
|
|
2388
|
+
titleGroupEl.appendChild(makeStudioUiRefreshSeparator());
|
|
2389
|
+
titleGroupEl.appendChild(contextMenu.anchor);
|
|
2390
|
+
}
|
|
2354
2391
|
headerTopEl.appendChild(titleGroupEl);
|
|
2355
2392
|
const headerToolsEl = makeStudioUiRefreshElement("div", "studio-refresh-pane-tools");
|
|
2356
2393
|
if (reviewNotesBtn) headerToolsEl.appendChild(reviewNotesBtn);
|
|
@@ -2359,18 +2396,7 @@
|
|
|
2359
2396
|
if (reviewMenu) headerToolsEl.appendChild(reviewMenu.anchor);
|
|
2360
2397
|
headerTopEl.appendChild(headerToolsEl);
|
|
2361
2398
|
|
|
2362
|
-
|
|
2363
|
-
const utilityLeftEl = makeStudioUiRefreshElement("div", "studio-refresh-utility-left");
|
|
2364
|
-
if (sourceBadgeEl) utilityLeftEl.appendChild(sourceBadgeEl);
|
|
2365
|
-
if (sourceBadgeEl && (resourceDirBtn || resourceDirLabel || resourceDirInputWrap || syncBadgeEl)) {
|
|
2366
|
-
utilityLeftEl.appendChild(makeStudioUiRefreshSeparator());
|
|
2367
|
-
}
|
|
2368
|
-
if (resourceDirBtn) utilityLeftEl.appendChild(resourceDirBtn);
|
|
2369
|
-
if (resourceDirLabel) utilityLeftEl.appendChild(resourceDirLabel);
|
|
2370
|
-
if (resourceDirInputWrap) utilityLeftEl.appendChild(resourceDirInputWrap);
|
|
2371
|
-
if (syncBadgeEl) utilityLeftEl.appendChild(syncBadgeEl);
|
|
2372
|
-
headerUtilityEl.appendChild(utilityLeftEl);
|
|
2373
|
-
leftHeaderEl.replaceChildren(headerTopEl, headerUtilityEl);
|
|
2399
|
+
leftHeaderEl.replaceChildren(headerTopEl);
|
|
2374
2400
|
|
|
2375
2401
|
const rightHeaderEl = document.getElementById("rightSectionHeader");
|
|
2376
2402
|
if (rightHeaderEl && rightViewSelect) {
|
|
@@ -2402,18 +2428,18 @@
|
|
|
2402
2428
|
const actionLineOneEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line");
|
|
2403
2429
|
if (!isEditorOnlyMode && sendRunBtn) actionLineOneEl.appendChild(sendRunBtn);
|
|
2404
2430
|
if (!isEditorOnlyMode && queueSteerBtn) actionLineOneEl.appendChild(queueSteerBtn);
|
|
2405
|
-
const
|
|
2406
|
-
replActionLineEl.hidden = true;
|
|
2407
|
-
if (!isEditorOnlyMode && sendReplBtn) replActionLineEl.appendChild(sendReplBtn);
|
|
2408
|
-
if (!isEditorOnlyMode && replSendModeSelect) replActionLineEl.appendChild(replSendModeSelect);
|
|
2409
|
-
const actionLineTwoEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line");
|
|
2431
|
+
const actionLineTwoEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line studio-refresh-utility-action-line");
|
|
2410
2432
|
actionLineTwoEl.appendChild(copyDraftBtn);
|
|
2411
2433
|
if (suggestCompletionBtn) actionLineTwoEl.appendChild(suggestCompletionBtn);
|
|
2412
2434
|
if (openCompanionBtn) actionLineTwoEl.appendChild(openCompanionBtn);
|
|
2413
2435
|
if (!isEditorOnlyMode && sendEditorBtn) actionLineTwoEl.appendChild(sendEditorBtn);
|
|
2436
|
+
const replActionLineEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line repl-action-line");
|
|
2437
|
+
replActionLineEl.hidden = true;
|
|
2438
|
+
if (!isEditorOnlyMode && sendReplBtn) replActionLineEl.appendChild(sendReplBtn);
|
|
2439
|
+
if (!isEditorOnlyMode && replSendModeSelect) replActionLineEl.appendChild(replSendModeSelect);
|
|
2414
2440
|
if (actionLineOneEl.childNodes.length > 0) actionsEl.appendChild(actionLineOneEl);
|
|
2415
|
-
if (replActionLineEl.childNodes.length > 0) actionsEl.appendChild(replActionLineEl);
|
|
2416
2441
|
actionsEl.appendChild(actionLineTwoEl);
|
|
2442
|
+
if (replActionLineEl.childNodes.length > 0) actionsEl.appendChild(replActionLineEl);
|
|
2417
2443
|
|
|
2418
2444
|
const stateEl = makeStudioUiRefreshElement("div", "studio-refresh-toolbar-state");
|
|
2419
2445
|
const annotationsButton = makeStudioUiRefreshElement("button", "", "Annotations");
|
|
@@ -2435,7 +2461,9 @@
|
|
|
2435
2461
|
annotationsButton,
|
|
2436
2462
|
viewButton,
|
|
2437
2463
|
reviewButton: reviewMenu ? reviewMenu.button : null,
|
|
2438
|
-
menus: [annotationsMenu, viewMenu]
|
|
2464
|
+
menus: [annotationsMenu, viewMenu]
|
|
2465
|
+
.concat(contextMenu ? [contextMenu] : [])
|
|
2466
|
+
.concat(reviewMenu ? [reviewMenu] : []),
|
|
2439
2467
|
};
|
|
2440
2468
|
|
|
2441
2469
|
document.addEventListener("click", (event) => {
|
|
@@ -3691,6 +3719,14 @@
|
|
|
3691
3719
|
return;
|
|
3692
3720
|
}
|
|
3693
3721
|
|
|
3722
|
+
if (plainEscape && completionSuggestionState) {
|
|
3723
|
+
event.preventDefault();
|
|
3724
|
+
hideCompletionSuggestion();
|
|
3725
|
+
focusSourceTextNoScroll();
|
|
3726
|
+
setStatus("Dismissed completion suggestion.");
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3694
3730
|
if (handleCompletionSuggestionAcceptKey(event)) return;
|
|
3695
3731
|
|
|
3696
3732
|
if ((key === "?" || (key === "/" && event.shiftKey)) && !event.metaKey && !event.ctrlKey && !event.altKey && !isTextEntryShortcutTarget(event.target)) {
|
|
@@ -4244,20 +4280,15 @@
|
|
|
4244
4280
|
|
|
4245
4281
|
if (isEditorOnlyMode) {
|
|
4246
4282
|
syncBadgeEl.hidden = true;
|
|
4247
|
-
syncBadgeEl.
|
|
4248
|
-
|
|
4249
|
-
}
|
|
4250
|
-
|
|
4251
|
-
if (rightView === "trace") {
|
|
4252
|
-
syncBadgeEl.hidden = true;
|
|
4253
|
-
syncBadgeEl.classList.remove("sync");
|
|
4283
|
+
syncBadgeEl.textContent = "Editor-only tab";
|
|
4284
|
+
syncBadgeEl.classList.remove("sync", "out-of-sync");
|
|
4254
4285
|
return;
|
|
4255
4286
|
}
|
|
4256
4287
|
|
|
4257
4288
|
if (!latestResponseHasContent) {
|
|
4258
|
-
syncBadgeEl.hidden =
|
|
4259
|
-
syncBadgeEl.textContent = "
|
|
4260
|
-
syncBadgeEl.classList.remove("sync");
|
|
4289
|
+
syncBadgeEl.hidden = false;
|
|
4290
|
+
syncBadgeEl.textContent = "No latest response";
|
|
4291
|
+
syncBadgeEl.classList.remove("sync", "out-of-sync");
|
|
4261
4292
|
return;
|
|
4262
4293
|
}
|
|
4263
4294
|
|
|
@@ -4265,15 +4296,10 @@
|
|
|
4265
4296
|
? normalizedEditorText
|
|
4266
4297
|
: normalizeForCompare(sourceTextEl.value);
|
|
4267
4298
|
const inSync = normalizedEditor === latestResponseNormalized;
|
|
4268
|
-
syncBadgeEl.hidden =
|
|
4269
|
-
syncBadgeEl.textContent = "In sync with response";
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
syncBadgeEl.classList.add("sync");
|
|
4273
|
-
return;
|
|
4274
|
-
}
|
|
4275
|
-
|
|
4276
|
-
syncBadgeEl.classList.remove("sync");
|
|
4299
|
+
syncBadgeEl.hidden = false;
|
|
4300
|
+
syncBadgeEl.textContent = inSync ? "In sync with response" : "Editor differs from latest response";
|
|
4301
|
+
syncBadgeEl.classList.toggle("sync", inSync);
|
|
4302
|
+
syncBadgeEl.classList.toggle("out-of-sync", !inSync);
|
|
4277
4303
|
}
|
|
4278
4304
|
|
|
4279
4305
|
function buildPlainMarkdownHtml(markdown, options) {
|
|
@@ -8667,9 +8693,14 @@
|
|
|
8667
8693
|
syncRunAndCritiqueButtons();
|
|
8668
8694
|
copyDraftBtn.disabled = uiBusy;
|
|
8669
8695
|
if (suggestCompletionBtn) {
|
|
8670
|
-
suggestCompletionBtn.disabled =
|
|
8671
|
-
suggestCompletionBtn.textContent = completionSuggestionInFlight ? "
|
|
8672
|
-
|
|
8696
|
+
suggestCompletionBtn.disabled = wsState !== "Ready" || (!completionSuggestionInFlight && (uiBusy || !String(sourceTextEl.value || "").trim()));
|
|
8697
|
+
suggestCompletionBtn.textContent = completionSuggestionInFlight ? "Stop" : "Suggest";
|
|
8698
|
+
suggestCompletionBtn.title = completionSuggestionInFlight
|
|
8699
|
+
? "Stop the current suggestion request."
|
|
8700
|
+
: "Ask the current model for a short completion at the editor cursor. Shortcut: Option/Alt+Tab where available, or Cmd/Ctrl+Shift+Space from the editor.";
|
|
8701
|
+
}
|
|
8702
|
+
if (suggestCompletionOptionsBtn) suggestCompletionOptionsBtn.disabled = uiBusy || completionSuggestionInFlight;
|
|
8703
|
+
syncCompletionSuggestionContextUi();
|
|
8673
8704
|
if (openCompanionBtn) openCompanionBtn.disabled = uiBusy || wsState !== "Ready";
|
|
8674
8705
|
if (highlightSelect) highlightSelect.disabled = uiBusy;
|
|
8675
8706
|
if (lineNumbersSelect) lineNumbersSelect.disabled = uiBusy;
|
|
@@ -9003,6 +9034,69 @@
|
|
|
9003
9034
|
}
|
|
9004
9035
|
}
|
|
9005
9036
|
|
|
9037
|
+
function readCompletionSuggestionContextMode() {
|
|
9038
|
+
try {
|
|
9039
|
+
const stored = window.localStorage ? String(window.localStorage.getItem(COMPLETION_CONTEXT_STORAGE_KEY) || "") : "";
|
|
9040
|
+
return stored === "session" ? "session" : "cursor";
|
|
9041
|
+
} catch {
|
|
9042
|
+
return "cursor";
|
|
9043
|
+
}
|
|
9044
|
+
}
|
|
9045
|
+
|
|
9046
|
+
function setCompletionSuggestionContextMode(mode) {
|
|
9047
|
+
completionSuggestionContextMode = mode === "session" ? "session" : "cursor";
|
|
9048
|
+
if (completionContextSelect) completionContextSelect.value = completionSuggestionContextMode;
|
|
9049
|
+
try {
|
|
9050
|
+
if (window.localStorage) window.localStorage.setItem(COMPLETION_CONTEXT_STORAGE_KEY, completionSuggestionContextMode);
|
|
9051
|
+
} catch {}
|
|
9052
|
+
setStatus(completionSuggestionContextMode === "session"
|
|
9053
|
+
? "Suggestions will include the latest response as context."
|
|
9054
|
+
: "Suggestions will use cursor-local editor context only.");
|
|
9055
|
+
}
|
|
9056
|
+
|
|
9057
|
+
function syncCompletionSuggestionContextUi() {
|
|
9058
|
+
if (completionContextSelect) completionContextSelect.value = completionSuggestionContextMode;
|
|
9059
|
+
if (suggestCompletionOptionsBtn) {
|
|
9060
|
+
suggestCompletionOptionsBtn.textContent = "Source & context";
|
|
9061
|
+
suggestCompletionOptionsBtn.title = completionSuggestionContextMode === "session"
|
|
9062
|
+
? "Document source, working directory, status, and suggestion context. Suggestions include editor plus latest response."
|
|
9063
|
+
: "Document source, working directory, status, and suggestion context. Suggestions use editor-only context.";
|
|
9064
|
+
suggestCompletionOptionsBtn.setAttribute("aria-label", suggestCompletionOptionsBtn.title);
|
|
9065
|
+
}
|
|
9066
|
+
document.querySelectorAll("[data-completion-context-mode]").forEach((button) => {
|
|
9067
|
+
if (!(button instanceof HTMLElement)) return;
|
|
9068
|
+
const mode = button.getAttribute("data-completion-context-mode") === "session" ? "session" : "cursor";
|
|
9069
|
+
const selected = mode === completionSuggestionContextMode;
|
|
9070
|
+
button.classList.toggle("is-selected", selected);
|
|
9071
|
+
button.setAttribute("aria-pressed", selected ? "true" : "false");
|
|
9072
|
+
button.textContent = (selected ? "✓ " : " ") + (mode === "session" ? "Editor + latest response" : "Editor only");
|
|
9073
|
+
});
|
|
9074
|
+
}
|
|
9075
|
+
|
|
9076
|
+
function trimCompletionContextText(text) {
|
|
9077
|
+
const value = String(text || "").trim();
|
|
9078
|
+
if (value.length <= COMPLETION_CONTEXT_MAX_CHARS) return value;
|
|
9079
|
+
return value.slice(value.length - COMPLETION_CONTEXT_MAX_CHARS);
|
|
9080
|
+
}
|
|
9081
|
+
|
|
9082
|
+
function getCompletionSuggestionContextText() {
|
|
9083
|
+
if (completionSuggestionContextMode !== "session") return "";
|
|
9084
|
+
const selected = getSelectedHistoryItem ? getSelectedHistoryItem() : null;
|
|
9085
|
+
const responseText = selected && typeof selected.markdown === "string" && selected.markdown.trim()
|
|
9086
|
+
? selected.markdown
|
|
9087
|
+
: latestResponseMarkdown;
|
|
9088
|
+
const parts = [];
|
|
9089
|
+
if (selected && typeof selected.promptTriggerText === "string" && selected.promptTriggerText.trim()) {
|
|
9090
|
+
parts.push("Latest request/steering:\n" + trimCompletionContextText(selected.promptTriggerText));
|
|
9091
|
+
} else if (selected && typeof selected.prompt === "string" && selected.prompt.trim()) {
|
|
9092
|
+
parts.push("Latest prompt:\n" + trimCompletionContextText(selected.prompt));
|
|
9093
|
+
}
|
|
9094
|
+
if (String(responseText || "").trim()) {
|
|
9095
|
+
parts.push("Latest response:\n" + trimCompletionContextText(responseText));
|
|
9096
|
+
}
|
|
9097
|
+
return trimCompletionContextText(parts.join("\n\n---\n\n"));
|
|
9098
|
+
}
|
|
9099
|
+
|
|
9006
9100
|
function hideCompletionSuggestion() {
|
|
9007
9101
|
completionSuggestionState = null;
|
|
9008
9102
|
if (completionSuggestionTextEl) completionSuggestionTextEl.textContent = "";
|
|
@@ -9073,10 +9167,29 @@
|
|
|
9073
9167
|
|| Boolean(completionSuggestionPanelEl && activeEl instanceof Element && completionSuggestionPanelEl.contains(activeEl));
|
|
9074
9168
|
}
|
|
9075
9169
|
|
|
9170
|
+
function cancelCompletionSuggestion() {
|
|
9171
|
+
if (!completionSuggestionInFlight || !completionSuggestionRequestId) {
|
|
9172
|
+
setStatus("No suggestion request is running.", "warning");
|
|
9173
|
+
return;
|
|
9174
|
+
}
|
|
9175
|
+
setStatus("Stopping suggestion…", "warning");
|
|
9176
|
+
const sent = sendMessage({
|
|
9177
|
+
type: "completion_suggestion_cancel_request",
|
|
9178
|
+
requestId: completionSuggestionRequestId,
|
|
9179
|
+
});
|
|
9180
|
+
if (!sent) {
|
|
9181
|
+
completionSuggestionInFlight = false;
|
|
9182
|
+
completionSuggestionRequestId = null;
|
|
9183
|
+
completionSuggestionPendingSnapshot = null;
|
|
9184
|
+
completionSuggestionRefocusEditorOnResult = false;
|
|
9185
|
+
syncActionButtons();
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
|
|
9076
9189
|
function requestCompletionSuggestion() {
|
|
9077
9190
|
if (isEditorOnlyMode && !sourceTextEl) return;
|
|
9078
9191
|
if (completionSuggestionInFlight) {
|
|
9079
|
-
|
|
9192
|
+
cancelCompletionSuggestion();
|
|
9080
9193
|
return;
|
|
9081
9194
|
}
|
|
9082
9195
|
const text = String(sourceTextEl.value || "");
|
|
@@ -9086,6 +9199,7 @@
|
|
|
9086
9199
|
}
|
|
9087
9200
|
const selectionStart = typeof sourceTextEl.selectionStart === "number" ? sourceTextEl.selectionStart : text.length;
|
|
9088
9201
|
const selectionEnd = typeof sourceTextEl.selectionEnd === "number" ? sourceTextEl.selectionEnd : selectionStart;
|
|
9202
|
+
const contextText = getCompletionSuggestionContextText();
|
|
9089
9203
|
const requestId = makeRequestId();
|
|
9090
9204
|
completionSuggestionInFlight = true;
|
|
9091
9205
|
completionSuggestionRequestId = requestId;
|
|
@@ -9103,6 +9217,8 @@
|
|
|
9103
9217
|
language: editorLanguage || "",
|
|
9104
9218
|
label: sourceState && sourceState.label ? sourceState.label : "Studio editor",
|
|
9105
9219
|
path: sourceState && sourceState.path ? sourceState.path : undefined,
|
|
9220
|
+
contextMode: completionSuggestionContextMode,
|
|
9221
|
+
contextText: contextText || undefined,
|
|
9106
9222
|
});
|
|
9107
9223
|
if (!sent) {
|
|
9108
9224
|
completionSuggestionInFlight = false;
|
|
@@ -17947,6 +18063,13 @@
|
|
|
17947
18063
|
requestCompletionSuggestion();
|
|
17948
18064
|
});
|
|
17949
18065
|
}
|
|
18066
|
+
if (completionContextSelect) {
|
|
18067
|
+
completionContextSelect.value = completionSuggestionContextMode;
|
|
18068
|
+
completionContextSelect.addEventListener("change", () => {
|
|
18069
|
+
setCompletionSuggestionContextMode(completionContextSelect.value);
|
|
18070
|
+
syncActionButtons();
|
|
18071
|
+
});
|
|
18072
|
+
}
|
|
17950
18073
|
if (completionSuggestionInsertBtn) {
|
|
17951
18074
|
completionSuggestionInsertBtn.addEventListener("click", () => {
|
|
17952
18075
|
insertCompletionSuggestion();
|
package/client/studio.css
CHANGED
|
@@ -4336,7 +4336,8 @@
|
|
|
4336
4336
|
}
|
|
4337
4337
|
|
|
4338
4338
|
body.studio-ui-refresh .studio-refresh-header-utility {
|
|
4339
|
-
grid-template-columns: minmax(0, 1fr)
|
|
4339
|
+
grid-template-columns: minmax(0, 1fr);
|
|
4340
|
+
justify-items: start;
|
|
4340
4341
|
}
|
|
4341
4342
|
|
|
4342
4343
|
body.studio-ui-refresh .studio-refresh-pane-identity {
|
|
@@ -4353,12 +4354,18 @@
|
|
|
4353
4354
|
flex-wrap: nowrap;
|
|
4354
4355
|
}
|
|
4355
4356
|
|
|
4356
|
-
body.studio-ui-refresh .studio-refresh-context-group
|
|
4357
|
-
body.studio-ui-refresh .studio-refresh-utility-left {
|
|
4357
|
+
body.studio-ui-refresh .studio-refresh-context-group {
|
|
4358
4358
|
overflow: hidden;
|
|
4359
4359
|
white-space: nowrap;
|
|
4360
4360
|
}
|
|
4361
4361
|
|
|
4362
|
+
body.studio-ui-refresh .studio-refresh-utility-left {
|
|
4363
|
+
overflow: visible;
|
|
4364
|
+
white-space: nowrap;
|
|
4365
|
+
justify-content: flex-start;
|
|
4366
|
+
justify-self: start;
|
|
4367
|
+
}
|
|
4368
|
+
|
|
4362
4369
|
body.studio-ui-refresh .studio-refresh-title-group {
|
|
4363
4370
|
position: relative;
|
|
4364
4371
|
z-index: 2;
|
|
@@ -4462,12 +4469,14 @@
|
|
|
4462
4469
|
border-color: transparent;
|
|
4463
4470
|
background: transparent;
|
|
4464
4471
|
padding: 3px 5px;
|
|
4465
|
-
font-size:
|
|
4472
|
+
font-size: 12px;
|
|
4473
|
+
font-weight: 400;
|
|
4474
|
+
line-height: 1.25;
|
|
4466
4475
|
border-radius: 8px;
|
|
4467
4476
|
}
|
|
4468
4477
|
|
|
4469
4478
|
body.studio-ui-refresh #sourceBadge {
|
|
4470
|
-
color: var(--text);
|
|
4479
|
+
color: var(--studio-info-text, var(--muted));
|
|
4471
4480
|
max-width: min(34rem, 54vw);
|
|
4472
4481
|
min-width: 0;
|
|
4473
4482
|
overflow: hidden;
|
|
@@ -4475,7 +4484,6 @@
|
|
|
4475
4484
|
white-space: nowrap;
|
|
4476
4485
|
}
|
|
4477
4486
|
|
|
4478
|
-
body.studio-ui-refresh #resourceDirBtn,
|
|
4479
4487
|
body.studio-ui-refresh #reviewNotesBtn,
|
|
4480
4488
|
body.studio-ui-refresh #outlineBtn,
|
|
4481
4489
|
body.studio-ui-refresh #scratchpadBtn,
|
|
@@ -4483,11 +4491,19 @@
|
|
|
4483
4491
|
color: var(--text);
|
|
4484
4492
|
}
|
|
4485
4493
|
|
|
4494
|
+
body.studio-ui-refresh #resourceDirBtn,
|
|
4486
4495
|
body.studio-ui-refresh #resourceDirLabel {
|
|
4487
4496
|
color: var(--studio-info-text, var(--muted));
|
|
4488
4497
|
font-weight: 400;
|
|
4489
4498
|
}
|
|
4490
4499
|
|
|
4500
|
+
body.studio-ui-refresh #resourceDirBtn:hover,
|
|
4501
|
+
body.studio-ui-refresh #resourceDirLabel:hover {
|
|
4502
|
+
color: var(--text);
|
|
4503
|
+
background: var(--studio-header-action-hover-bg, var(--panel-2));
|
|
4504
|
+
border-color: transparent;
|
|
4505
|
+
}
|
|
4506
|
+
|
|
4491
4507
|
body.studio-ui-refresh #resourceDirInputWrap.visible {
|
|
4492
4508
|
display: inline-flex;
|
|
4493
4509
|
}
|
|
@@ -4501,9 +4517,11 @@
|
|
|
4501
4517
|
border-radius: 999px;
|
|
4502
4518
|
padding: 2px 7px;
|
|
4503
4519
|
background: transparent;
|
|
4504
|
-
color: var(--muted);
|
|
4505
|
-
opacity:
|
|
4506
|
-
font-
|
|
4520
|
+
color: var(--studio-info-text, var(--muted));
|
|
4521
|
+
opacity: 1;
|
|
4522
|
+
font-size: 12px;
|
|
4523
|
+
font-weight: 400;
|
|
4524
|
+
line-height: 1.25;
|
|
4507
4525
|
}
|
|
4508
4526
|
|
|
4509
4527
|
body.studio-ui-refresh #syncBadge[hidden] {
|
|
@@ -4519,6 +4537,11 @@
|
|
|
4519
4537
|
opacity: 0.72;
|
|
4520
4538
|
}
|
|
4521
4539
|
|
|
4540
|
+
body.studio-ui-refresh #syncBadge.out-of-sync::before {
|
|
4541
|
+
background: var(--warning, var(--accent));
|
|
4542
|
+
opacity: 0.95;
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4522
4545
|
body.studio-ui-refresh #reviewNotesBtn,
|
|
4523
4546
|
body.studio-ui-refresh #outlineBtn,
|
|
4524
4547
|
body.studio-ui-refresh #scratchpadBtn,
|
|
@@ -4536,9 +4559,10 @@
|
|
|
4536
4559
|
body.studio-zen-mode #exportPdfBtn,
|
|
4537
4560
|
body.studio-zen-mode .studio-refresh-tool-tab,
|
|
4538
4561
|
body.studio-zen-mode .studio-refresh-toolbar-state,
|
|
4539
|
-
body.studio-zen-mode .studio-refresh-toolbar-actions .studio-refresh-action-line
|
|
4562
|
+
body.studio-zen-mode .studio-refresh-toolbar-actions .studio-refresh-utility-action-line,
|
|
4540
4563
|
body.studio-zen-mode .source-actions-row:nth-child(n+3),
|
|
4541
4564
|
body.studio-zen-mode #copyDraftBtn,
|
|
4565
|
+
body.studio-zen-mode #suggestCompletionBtn,
|
|
4542
4566
|
body.studio-zen-mode #openCompanionBtn,
|
|
4543
4567
|
body.studio-zen-mode #sendEditorBtn {
|
|
4544
4568
|
display: none !important;
|
|
@@ -4805,6 +4829,19 @@
|
|
|
4805
4829
|
width: min(320px, calc(100vw - 48px));
|
|
4806
4830
|
}
|
|
4807
4831
|
|
|
4832
|
+
body.studio-ui-refresh .studio-refresh-title-group .studio-refresh-context-anchor .studio-refresh-chip {
|
|
4833
|
+
color: var(--studio-info-text, var(--muted));
|
|
4834
|
+
font-size: 13px;
|
|
4835
|
+
font-weight: 500;
|
|
4836
|
+
padding: 3px 5px;
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4839
|
+
body.studio-ui-refresh .studio-refresh-context-anchor .studio-refresh-menu {
|
|
4840
|
+
left: 0;
|
|
4841
|
+
right: auto;
|
|
4842
|
+
width: min(360px, calc(100vw - 48px));
|
|
4843
|
+
}
|
|
4844
|
+
|
|
4808
4845
|
body.studio-ui-refresh .studio-refresh-menu[hidden] {
|
|
4809
4846
|
display: none !important;
|
|
4810
4847
|
}
|
|
@@ -4852,6 +4889,11 @@
|
|
|
4852
4889
|
text-align: left;
|
|
4853
4890
|
}
|
|
4854
4891
|
|
|
4892
|
+
body.studio-ui-refresh .completion-context-option.is-selected {
|
|
4893
|
+
color: var(--text);
|
|
4894
|
+
font-weight: 650;
|
|
4895
|
+
}
|
|
4896
|
+
|
|
4855
4897
|
body.studio-ui-refresh .studio-refresh-menu-item > button:not(:disabled):hover,
|
|
4856
4898
|
body.studio-ui-refresh .studio-refresh-menu-item > select:not(:disabled):hover {
|
|
4857
4899
|
background: var(--studio-header-action-hover-bg, var(--panel-2));
|
package/index.ts
CHANGED
|
@@ -320,6 +320,13 @@ interface CompletionSuggestionRequestMessage {
|
|
|
320
320
|
language?: string;
|
|
321
321
|
label?: string;
|
|
322
322
|
path?: string;
|
|
323
|
+
contextMode?: "cursor" | "session";
|
|
324
|
+
contextText?: string;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
interface CompletionSuggestionCancelRequestMessage {
|
|
328
|
+
type: "completion_suggestion_cancel_request";
|
|
329
|
+
requestId: string;
|
|
323
330
|
}
|
|
324
331
|
|
|
325
332
|
interface QuizGenerateRequestMessage {
|
|
@@ -463,6 +470,7 @@ type IncomingStudioMessage =
|
|
|
463
470
|
| AnnotationRequestMessage
|
|
464
471
|
| SendRunRequestMessage
|
|
465
472
|
| CompletionSuggestionRequestMessage
|
|
473
|
+
| CompletionSuggestionCancelRequestMessage
|
|
466
474
|
| QuizGenerateRequestMessage
|
|
467
475
|
| QuizAnswerRequestMessage
|
|
468
476
|
| QuizDiscussRequestMessage
|
|
@@ -485,6 +493,7 @@ type IncomingStudioMessage =
|
|
|
485
493
|
const REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
|
|
486
494
|
const PREVIEW_RENDER_MAX_CHARS = 400_000;
|
|
487
495
|
const STUDIO_COMPLETION_MAX_TEXT_CHARS = 250_000;
|
|
496
|
+
const STUDIO_COMPLETION_MAX_CONTEXT_CHARS = 12_000;
|
|
488
497
|
const STUDIO_COMPLETION_PREFIX_CHARS = 12_000;
|
|
489
498
|
const STUDIO_COMPLETION_SUFFIX_CHARS = 6_000;
|
|
490
499
|
const PDF_EXPORT_MAX_CHARS = 400_000;
|
|
@@ -7217,6 +7226,8 @@ function buildStudioCompletionSuggestionPrompt(options: {
|
|
|
7217
7226
|
language?: string;
|
|
7218
7227
|
label?: string;
|
|
7219
7228
|
path?: string;
|
|
7229
|
+
contextMode?: "cursor" | "session";
|
|
7230
|
+
contextText?: string;
|
|
7220
7231
|
}): string {
|
|
7221
7232
|
const text = String(options.text || "");
|
|
7222
7233
|
const start = Math.max(0, Math.min(Math.floor(options.selectionStart || 0), text.length));
|
|
@@ -7226,17 +7237,23 @@ function buildStudioCompletionSuggestionPrompt(options: {
|
|
|
7226
7237
|
const suffix = text.slice(end, Math.min(text.length, end + STUDIO_COMPLETION_SUFFIX_CHARS));
|
|
7227
7238
|
const language = String(options.language || "").trim() || "unknown";
|
|
7228
7239
|
const label = String(options.label || options.path || "Studio editor").trim();
|
|
7240
|
+
const contextText = String(options.contextText || "").trim().slice(-STUDIO_COMPLETION_MAX_CONTEXT_CHARS);
|
|
7229
7241
|
return [
|
|
7230
7242
|
"Generate an inline completion for the current editor cursor position.",
|
|
7231
7243
|
"Return only the exact text to insert. Do not wrap it in Markdown fences. Do not explain.",
|
|
7232
7244
|
"Match the surrounding language, style, indentation, and register.",
|
|
7233
7245
|
"Keep the suggestion short unless the context clearly asks for a longer continuation.",
|
|
7246
|
+
contextText
|
|
7247
|
+
? "Use the extra session context only as background. Do not continue the extra context directly unless the editor cursor calls for it."
|
|
7248
|
+
: "Use only the cursor-local editor context below.",
|
|
7234
7249
|
selected
|
|
7235
7250
|
? "The selected text will be replaced by the completion."
|
|
7236
7251
|
: "The completion will be inserted at the cursor.",
|
|
7237
7252
|
"",
|
|
7238
7253
|
`File/context label: ${label}`,
|
|
7239
7254
|
`Language mode: ${language}`,
|
|
7255
|
+
`Suggestion context mode: ${contextText ? "editor plus latest response" : "editor only"}`,
|
|
7256
|
+
contextText ? ["", "<extra_context>", contextText, "</extra_context>"].join("\n") : "",
|
|
7240
7257
|
"",
|
|
7241
7258
|
"<prefix>",
|
|
7242
7259
|
prefix,
|
|
@@ -7262,6 +7279,9 @@ async function runStudioCompletionSuggestion(ctx: StudioModelRequestContext, opt
|
|
|
7262
7279
|
language?: string;
|
|
7263
7280
|
label?: string;
|
|
7264
7281
|
path?: string;
|
|
7282
|
+
contextMode?: "cursor" | "session";
|
|
7283
|
+
contextText?: string;
|
|
7284
|
+
signal?: AbortSignal;
|
|
7265
7285
|
}): Promise<string> {
|
|
7266
7286
|
const prompt = buildStudioCompletionSuggestionPrompt(options);
|
|
7267
7287
|
// Intentionally omit `reasoning`: pi-ai treats absent reasoning as off/disabled
|
|
@@ -7271,6 +7291,7 @@ async function runStudioCompletionSuggestion(ctx: StudioModelRequestContext, opt
|
|
|
7271
7291
|
maxTokens: 650,
|
|
7272
7292
|
timeoutMs: 60_000,
|
|
7273
7293
|
trim: false,
|
|
7294
|
+
signal: options.signal,
|
|
7274
7295
|
}));
|
|
7275
7296
|
if (!suggestion.trim()) throw new Error("Model returned an empty completion suggestion.");
|
|
7276
7297
|
return suggestion;
|
|
@@ -7685,12 +7706,20 @@ function parseIncomingMessage(data: RawData): IncomingStudioMessage | null {
|
|
|
7685
7706
|
};
|
|
7686
7707
|
}
|
|
7687
7708
|
|
|
7709
|
+
if (msg.type === "completion_suggestion_cancel_request" && typeof msg.requestId === "string") {
|
|
7710
|
+
return {
|
|
7711
|
+
type: "completion_suggestion_cancel_request",
|
|
7712
|
+
requestId: msg.requestId,
|
|
7713
|
+
};
|
|
7714
|
+
}
|
|
7715
|
+
|
|
7688
7716
|
if (msg.type === "completion_suggestion_request" && typeof msg.requestId === "string" && typeof msg.text === "string") {
|
|
7689
7717
|
const textLength = msg.text.length;
|
|
7690
7718
|
const rawStart = typeof msg.selectionStart === "number" && Number.isFinite(msg.selectionStart) ? msg.selectionStart : textLength;
|
|
7691
7719
|
const rawEnd = typeof msg.selectionEnd === "number" && Number.isFinite(msg.selectionEnd) ? msg.selectionEnd : rawStart;
|
|
7692
7720
|
const selectionStart = Math.max(0, Math.min(Math.floor(rawStart), textLength));
|
|
7693
7721
|
const selectionEnd = Math.max(selectionStart, Math.min(Math.floor(rawEnd), textLength));
|
|
7722
|
+
const contextMode = msg.contextMode === "session" ? "session" : "cursor";
|
|
7694
7723
|
return {
|
|
7695
7724
|
type: "completion_suggestion_request",
|
|
7696
7725
|
requestId: msg.requestId,
|
|
@@ -7700,6 +7729,8 @@ function parseIncomingMessage(data: RawData): IncomingStudioMessage | null {
|
|
|
7700
7729
|
language: typeof msg.language === "string" ? msg.language : undefined,
|
|
7701
7730
|
label: typeof msg.label === "string" ? msg.label : undefined,
|
|
7702
7731
|
path: typeof msg.path === "string" ? msg.path : undefined,
|
|
7732
|
+
contextMode,
|
|
7733
|
+
contextText: contextMode === "session" && typeof msg.contextText === "string" ? msg.contextText.slice(-STUDIO_COMPLETION_MAX_CONTEXT_CHARS) : undefined,
|
|
7703
7734
|
};
|
|
7704
7735
|
}
|
|
7705
7736
|
|
|
@@ -9582,6 +9613,11 @@ ${cssVarsBlock}
|
|
|
9582
9613
|
<div class="source-actions-row">
|
|
9583
9614
|
<button id="copyDraftBtn" type="button" title="Copy the current editor text to the clipboard.">Copy</button>
|
|
9584
9615
|
<button id="suggestCompletionBtn" type="button" title="Ask the current model for a short completion at the editor cursor. Shortcut: Option/Alt+Tab where available, or Cmd/Ctrl+Shift+Space from the editor.">Suggest</button>
|
|
9616
|
+
<button id="suggestCompletionOptionsBtn" type="button" hidden title="Suggestion context options">▾</button>
|
|
9617
|
+
<select id="completionContextSelect" hidden aria-label="Suggestion context mode" title="Choose how much context Suggest includes.">
|
|
9618
|
+
<option value="cursor" selected>Context: editor only</option>
|
|
9619
|
+
<option value="session">Context: editor + latest response</option>
|
|
9620
|
+
</select>
|
|
9585
9621
|
<button id="openCompanionBtn" type="button" title="Open a detached copy of the current editor text in a new editor-only Studio tab.">New editor</button>
|
|
9586
9622
|
<button id="sendEditorBtn" type="button">Send to pi editor</button>
|
|
9587
9623
|
</div>
|
|
@@ -9844,6 +9880,7 @@ ${cssVarsBlock}
|
|
|
9844
9880
|
<div><dt>Cmd/Ctrl+Enter</dt><dd>Run editor text, or queue steering during an active run</dd></div>
|
|
9845
9881
|
<div><dt>Option/Alt+Tab or Cmd/Ctrl+Shift+Space</dt><dd>Suggest a completion at the editor cursor</dd></div>
|
|
9846
9882
|
<div><dt>Tab</dt><dd>Insert a visible completion suggestion; otherwise indent selected editor text</dd></div>
|
|
9883
|
+
<div><dt>Esc</dt><dd>Dismiss a visible completion suggestion, close overlays, exit pane focus, or stop an active request</dd></div>
|
|
9847
9884
|
<div><dt>Shift+Tab</dt><dd>Unindent selected editor text</dd></div>
|
|
9848
9885
|
</dl>
|
|
9849
9886
|
</section>
|
|
@@ -9938,6 +9975,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
9938
9975
|
let studioReplActiveSessionName: string | null = null;
|
|
9939
9976
|
let compactInProgress = false;
|
|
9940
9977
|
let compactRequestId: string | null = null;
|
|
9978
|
+
const activeCompletionSuggestions = new Map<string, AbortController>();
|
|
9941
9979
|
|
|
9942
9980
|
const selectStudioReplSessionForTool = (params: { sessionName?: string; target?: string }): { session: StudioReplSessionInfo | null; error?: string; sessions: StudioReplSessionInfo[] } => {
|
|
9943
9981
|
const state = listStudioReplSessions();
|
|
@@ -11424,6 +11462,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
11424
11462
|
return;
|
|
11425
11463
|
}
|
|
11426
11464
|
|
|
11465
|
+
if (msg.type === "completion_suggestion_cancel_request") {
|
|
11466
|
+
if (!isValidRequestId(msg.requestId)) {
|
|
11467
|
+
sendToClient(client, { type: "completion_suggestion_error", requestId: msg.requestId, message: "Invalid request ID." });
|
|
11468
|
+
return;
|
|
11469
|
+
}
|
|
11470
|
+
const controller = activeCompletionSuggestions.get(msg.requestId);
|
|
11471
|
+
if (!controller) {
|
|
11472
|
+
sendToClient(client, { type: "completion_suggestion_error", requestId: msg.requestId, message: "No matching suggestion request is running." });
|
|
11473
|
+
return;
|
|
11474
|
+
}
|
|
11475
|
+
controller.abort();
|
|
11476
|
+
sendToClient(client, { type: "completion_suggestion_progress", requestId: msg.requestId, message: "Stopping suggestion…" });
|
|
11477
|
+
return;
|
|
11478
|
+
}
|
|
11479
|
+
|
|
11427
11480
|
if (msg.type === "completion_suggestion_request") {
|
|
11428
11481
|
if (!isValidRequestId(msg.requestId)) {
|
|
11429
11482
|
sendToClient(client, { type: "completion_suggestion_error", requestId: msg.requestId, message: "Invalid request ID." });
|
|
@@ -11443,6 +11496,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
11443
11496
|
return;
|
|
11444
11497
|
}
|
|
11445
11498
|
sendToClient(client, { type: "completion_suggestion_progress", requestId: msg.requestId, message: "Generating suggestion…" });
|
|
11499
|
+
const completionController = new AbortController();
|
|
11500
|
+
activeCompletionSuggestions.set(msg.requestId, completionController);
|
|
11446
11501
|
void (async () => {
|
|
11447
11502
|
try {
|
|
11448
11503
|
const suggestion = await runStudioCompletionSuggestion(ctx, {
|
|
@@ -11452,6 +11507,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
11452
11507
|
language: msg.language,
|
|
11453
11508
|
label: msg.label,
|
|
11454
11509
|
path: msg.path,
|
|
11510
|
+
contextMode: msg.contextMode,
|
|
11511
|
+
contextText: msg.contextText,
|
|
11512
|
+
signal: completionController.signal,
|
|
11455
11513
|
});
|
|
11456
11514
|
sendToClient(client, {
|
|
11457
11515
|
type: "completion_suggestion_result",
|
|
@@ -11464,8 +11522,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
11464
11522
|
sendToClient(client, {
|
|
11465
11523
|
type: "completion_suggestion_error",
|
|
11466
11524
|
requestId: msg.requestId,
|
|
11467
|
-
message:
|
|
11525
|
+
message: completionController.signal.aborted
|
|
11526
|
+
? "Suggestion stopped."
|
|
11527
|
+
: `Suggestion failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
11468
11528
|
});
|
|
11529
|
+
} finally {
|
|
11530
|
+
activeCompletionSuggestions.delete(msg.requestId);
|
|
11469
11531
|
}
|
|
11470
11532
|
})();
|
|
11471
11533
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.17",
|
|
4
4
|
"description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, active quiz, prompt/response history, live previews, and tmux-backed REPL/literate REPL workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|