pi-studio 0.9.15 → 0.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +1 -1
- package/client/studio-client.js +240 -1
- package/client/studio.css +73 -5
- package/index.ts +185 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,11 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.16] — 2026-05-24
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Added a first-cut manual **Suggest completion** editor action beside Run/Queue that asks the active model for a short cursor/selection-aware continuation, previews it before insertion, supports Option/Alt+Tab where available plus Cmd/Ctrl+Shift+Space from the editor, and lets Tab insert a visible suggestion.
|
|
11
|
+
|
|
7
12
|
## [0.9.15] — 2026-05-23
|
|
8
13
|
|
|
9
14
|
### 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), 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), 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
|
@@ -114,6 +114,11 @@
|
|
|
114
114
|
const sendReplBtn = document.getElementById("sendReplBtn");
|
|
115
115
|
const replSendModeSelect = document.getElementById("replSendModeSelect");
|
|
116
116
|
const copyDraftBtn = document.getElementById("copyDraftBtn");
|
|
117
|
+
const suggestCompletionBtn = document.getElementById("suggestCompletionBtn");
|
|
118
|
+
const completionSuggestionPanelEl = document.getElementById("completionSuggestionPanel");
|
|
119
|
+
const completionSuggestionTextEl = document.getElementById("completionSuggestionText");
|
|
120
|
+
const completionSuggestionInsertBtn = document.getElementById("completionSuggestionInsertBtn");
|
|
121
|
+
const completionSuggestionDismissBtn = document.getElementById("completionSuggestionDismissBtn");
|
|
117
122
|
const saveAnnotatedBtn = document.getElementById("saveAnnotatedBtn");
|
|
118
123
|
const stripAnnotationsBtn = document.getElementById("stripAnnotationsBtn");
|
|
119
124
|
const highlightSelect = document.getElementById("highlightSelect");
|
|
@@ -1943,6 +1948,11 @@
|
|
|
1943
1948
|
let editorHighlightEnabled = false;
|
|
1944
1949
|
let editorLanguage = "markdown";
|
|
1945
1950
|
let responseHighlightEnabled = false;
|
|
1951
|
+
let completionSuggestionState = null;
|
|
1952
|
+
let completionSuggestionInFlight = false;
|
|
1953
|
+
let completionSuggestionRequestId = null;
|
|
1954
|
+
let completionSuggestionPendingSnapshot = null;
|
|
1955
|
+
let completionSuggestionRefocusEditorOnResult = false;
|
|
1946
1956
|
let editorHighlightRenderRaf = null;
|
|
1947
1957
|
let lineNumbersEnabled = false;
|
|
1948
1958
|
let lineNumbersRenderRaf = null;
|
|
@@ -2398,6 +2408,7 @@
|
|
|
2398
2408
|
if (!isEditorOnlyMode && replSendModeSelect) replActionLineEl.appendChild(replSendModeSelect);
|
|
2399
2409
|
const actionLineTwoEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line");
|
|
2400
2410
|
actionLineTwoEl.appendChild(copyDraftBtn);
|
|
2411
|
+
if (suggestCompletionBtn) actionLineTwoEl.appendChild(suggestCompletionBtn);
|
|
2401
2412
|
if (openCompanionBtn) actionLineTwoEl.appendChild(openCompanionBtn);
|
|
2402
2413
|
if (!isEditorOnlyMode && sendEditorBtn) actionLineTwoEl.appendChild(sendEditorBtn);
|
|
2403
2414
|
if (actionLineOneEl.childNodes.length > 0) actionsEl.appendChild(actionLineOneEl);
|
|
@@ -3680,6 +3691,8 @@
|
|
|
3680
3691
|
return;
|
|
3681
3692
|
}
|
|
3682
3693
|
|
|
3694
|
+
if (handleCompletionSuggestionAcceptKey(event)) return;
|
|
3695
|
+
|
|
3683
3696
|
if ((key === "?" || (key === "/" && event.shiftKey)) && !event.metaKey && !event.ctrlKey && !event.altKey && !isTextEntryShortcutTarget(event.target)) {
|
|
3684
3697
|
event.preventDefault();
|
|
3685
3698
|
toggleShortcuts();
|
|
@@ -8653,6 +8666,10 @@
|
|
|
8653
8666
|
if (loadGitDiffBtn) loadGitDiffBtn.disabled = uiBusy;
|
|
8654
8667
|
syncRunAndCritiqueButtons();
|
|
8655
8668
|
copyDraftBtn.disabled = uiBusy;
|
|
8669
|
+
if (suggestCompletionBtn) {
|
|
8670
|
+
suggestCompletionBtn.disabled = uiBusy || completionSuggestionInFlight || wsState !== "Ready" || !String(sourceTextEl.value || "").trim();
|
|
8671
|
+
suggestCompletionBtn.textContent = completionSuggestionInFlight ? "Suggesting…" : "Suggest";
|
|
8672
|
+
}
|
|
8656
8673
|
if (openCompanionBtn) openCompanionBtn.disabled = uiBusy || wsState !== "Ready";
|
|
8657
8674
|
if (highlightSelect) highlightSelect.disabled = uiBusy;
|
|
8658
8675
|
if (lineNumbersSelect) lineNumbersSelect.disabled = uiBusy;
|
|
@@ -8986,6 +9003,197 @@
|
|
|
8986
9003
|
}
|
|
8987
9004
|
}
|
|
8988
9005
|
|
|
9006
|
+
function hideCompletionSuggestion() {
|
|
9007
|
+
completionSuggestionState = null;
|
|
9008
|
+
if (completionSuggestionTextEl) completionSuggestionTextEl.textContent = "";
|
|
9009
|
+
if (completionSuggestionPanelEl) completionSuggestionPanelEl.hidden = true;
|
|
9010
|
+
}
|
|
9011
|
+
|
|
9012
|
+
function showCompletionSuggestion(state) {
|
|
9013
|
+
completionSuggestionState = state;
|
|
9014
|
+
if (completionSuggestionTextEl) completionSuggestionTextEl.textContent = state && state.suggestion ? state.suggestion : "";
|
|
9015
|
+
if (completionSuggestionPanelEl) completionSuggestionPanelEl.hidden = false;
|
|
9016
|
+
}
|
|
9017
|
+
|
|
9018
|
+
function focusSourceTextNoScroll() {
|
|
9019
|
+
if (!sourceTextEl || typeof sourceTextEl.focus !== "function") return;
|
|
9020
|
+
try {
|
|
9021
|
+
sourceTextEl.focus({ preventScroll: true });
|
|
9022
|
+
} catch {
|
|
9023
|
+
try { sourceTextEl.focus(); } catch {}
|
|
9024
|
+
}
|
|
9025
|
+
}
|
|
9026
|
+
|
|
9027
|
+
function focusSourceEditorForCompletion() {
|
|
9028
|
+
const snapshot = snapshotStudioScrollablePositions();
|
|
9029
|
+
if (editorView !== "markdown") {
|
|
9030
|
+
setEditorView("markdown");
|
|
9031
|
+
scheduleStudioScrollablePositionRestore(snapshot);
|
|
9032
|
+
}
|
|
9033
|
+
window.setTimeout(focusSourceTextNoScroll, 0);
|
|
9034
|
+
}
|
|
9035
|
+
|
|
9036
|
+
function isCompletionSuggestionRequestShortcut(event) {
|
|
9037
|
+
if (!event) return false;
|
|
9038
|
+
const key = typeof event.key === "string" ? event.key : "";
|
|
9039
|
+
const code = typeof event.code === "string" ? event.code : "";
|
|
9040
|
+
const commandSpace = (event.metaKey || event.ctrlKey)
|
|
9041
|
+
&& event.shiftKey
|
|
9042
|
+
&& !event.altKey
|
|
9043
|
+
&& (code === "Space" || key === " " || key === "Spacebar");
|
|
9044
|
+
const optionTab = event.altKey
|
|
9045
|
+
&& !event.metaKey
|
|
9046
|
+
&& !event.ctrlKey
|
|
9047
|
+
&& !event.shiftKey
|
|
9048
|
+
&& key === "Tab";
|
|
9049
|
+
return commandSpace || optionTab;
|
|
9050
|
+
}
|
|
9051
|
+
|
|
9052
|
+
function handleCompletionSuggestionAcceptKey(event) {
|
|
9053
|
+
if (!event || !completionSuggestionState) return false;
|
|
9054
|
+
if (event.key !== "Tab" || event.shiftKey || event.metaKey || event.ctrlKey || event.altKey) return false;
|
|
9055
|
+
const target = event.target;
|
|
9056
|
+
const focusIsUnclaimed = target === document.body || target === document.documentElement;
|
|
9057
|
+
const targetCanAccept = focusIsUnclaimed
|
|
9058
|
+
|| target === sourceTextEl
|
|
9059
|
+
|| target === suggestCompletionBtn
|
|
9060
|
+
|| Boolean(completionSuggestionPanelEl && target instanceof Element && completionSuggestionPanelEl.contains(target));
|
|
9061
|
+
if (!targetCanAccept) return false;
|
|
9062
|
+
event.preventDefault();
|
|
9063
|
+
insertCompletionSuggestion();
|
|
9064
|
+
return true;
|
|
9065
|
+
}
|
|
9066
|
+
|
|
9067
|
+
function shouldRefocusEditorForCompletionRequest() {
|
|
9068
|
+
const activeEl = document.activeElement;
|
|
9069
|
+
return activeEl === sourceTextEl
|
|
9070
|
+
|| activeEl === suggestCompletionBtn
|
|
9071
|
+
|| activeEl === document.body
|
|
9072
|
+
|| activeEl === document.documentElement
|
|
9073
|
+
|| Boolean(completionSuggestionPanelEl && activeEl instanceof Element && completionSuggestionPanelEl.contains(activeEl));
|
|
9074
|
+
}
|
|
9075
|
+
|
|
9076
|
+
function requestCompletionSuggestion() {
|
|
9077
|
+
if (isEditorOnlyMode && !sourceTextEl) return;
|
|
9078
|
+
if (completionSuggestionInFlight) {
|
|
9079
|
+
setStatus("Suggestion request already in progress.", "warning");
|
|
9080
|
+
return;
|
|
9081
|
+
}
|
|
9082
|
+
const text = String(sourceTextEl.value || "");
|
|
9083
|
+
if (!text.trim()) {
|
|
9084
|
+
setStatus("Editor is empty.", "warning");
|
|
9085
|
+
return;
|
|
9086
|
+
}
|
|
9087
|
+
const selectionStart = typeof sourceTextEl.selectionStart === "number" ? sourceTextEl.selectionStart : text.length;
|
|
9088
|
+
const selectionEnd = typeof sourceTextEl.selectionEnd === "number" ? sourceTextEl.selectionEnd : selectionStart;
|
|
9089
|
+
const requestId = makeRequestId();
|
|
9090
|
+
completionSuggestionInFlight = true;
|
|
9091
|
+
completionSuggestionRequestId = requestId;
|
|
9092
|
+
completionSuggestionPendingSnapshot = { text, selectionStart, selectionEnd };
|
|
9093
|
+
completionSuggestionRefocusEditorOnResult = shouldRefocusEditorForCompletionRequest();
|
|
9094
|
+
hideCompletionSuggestion();
|
|
9095
|
+
syncActionButtons();
|
|
9096
|
+
setStatus("Generating completion suggestion…", "warning");
|
|
9097
|
+
const sent = sendMessage({
|
|
9098
|
+
type: "completion_suggestion_request",
|
|
9099
|
+
requestId,
|
|
9100
|
+
text,
|
|
9101
|
+
selectionStart,
|
|
9102
|
+
selectionEnd,
|
|
9103
|
+
language: editorLanguage || "",
|
|
9104
|
+
label: sourceState && sourceState.label ? sourceState.label : "Studio editor",
|
|
9105
|
+
path: sourceState && sourceState.path ? sourceState.path : undefined,
|
|
9106
|
+
});
|
|
9107
|
+
if (!sent) {
|
|
9108
|
+
completionSuggestionInFlight = false;
|
|
9109
|
+
completionSuggestionRequestId = null;
|
|
9110
|
+
completionSuggestionPendingSnapshot = null;
|
|
9111
|
+
completionSuggestionRefocusEditorOnResult = false;
|
|
9112
|
+
syncActionButtons();
|
|
9113
|
+
}
|
|
9114
|
+
}
|
|
9115
|
+
|
|
9116
|
+
function insertCompletionSuggestion() {
|
|
9117
|
+
const state = completionSuggestionState;
|
|
9118
|
+
if (!state || typeof state.suggestion !== "string") {
|
|
9119
|
+
setStatus("No suggestion to insert.", "warning");
|
|
9120
|
+
return;
|
|
9121
|
+
}
|
|
9122
|
+
const currentText = String(sourceTextEl.value || "");
|
|
9123
|
+
const useOriginalRange = currentText === state.baseText;
|
|
9124
|
+
const start = useOriginalRange
|
|
9125
|
+
? Math.max(0, Math.min(state.selectionStart, currentText.length))
|
|
9126
|
+
: (typeof sourceTextEl.selectionStart === "number" ? sourceTextEl.selectionStart : currentText.length);
|
|
9127
|
+
const end = useOriginalRange
|
|
9128
|
+
? Math.max(start, Math.min(state.selectionEnd, currentText.length))
|
|
9129
|
+
: (typeof sourceTextEl.selectionEnd === "number" ? sourceTextEl.selectionEnd : start);
|
|
9130
|
+
const nextText = currentText.slice(0, start) + state.suggestion + currentText.slice(end);
|
|
9131
|
+
const caret = start + state.suggestion.length;
|
|
9132
|
+
applySourceTextEdit(nextText, caret, caret);
|
|
9133
|
+
hideCompletionSuggestion();
|
|
9134
|
+
focusSourceTextNoScroll();
|
|
9135
|
+
setStatus("Inserted completion suggestion.", "success");
|
|
9136
|
+
}
|
|
9137
|
+
|
|
9138
|
+
function handleCompletionSuggestionServerMessage(message) {
|
|
9139
|
+
if (!message || typeof message !== "object") return false;
|
|
9140
|
+
if (
|
|
9141
|
+
message.type !== "completion_suggestion_progress"
|
|
9142
|
+
&& message.type !== "completion_suggestion_result"
|
|
9143
|
+
&& message.type !== "completion_suggestion_error"
|
|
9144
|
+
) return false;
|
|
9145
|
+
if (typeof message.requestId === "string" && completionSuggestionRequestId && message.requestId !== completionSuggestionRequestId) {
|
|
9146
|
+
return true;
|
|
9147
|
+
}
|
|
9148
|
+
if (message.type === "completion_suggestion_progress") {
|
|
9149
|
+
setStatus(typeof message.message === "string" ? message.message : "Generating suggestion…", "warning");
|
|
9150
|
+
return true;
|
|
9151
|
+
}
|
|
9152
|
+
const pendingSnapshot = completionSuggestionPendingSnapshot;
|
|
9153
|
+
const shouldRefocusEditor = completionSuggestionRefocusEditorOnResult;
|
|
9154
|
+
completionSuggestionInFlight = false;
|
|
9155
|
+
completionSuggestionRequestId = null;
|
|
9156
|
+
completionSuggestionPendingSnapshot = null;
|
|
9157
|
+
completionSuggestionRefocusEditorOnResult = false;
|
|
9158
|
+
syncActionButtons();
|
|
9159
|
+
if (message.type === "completion_suggestion_error") {
|
|
9160
|
+
setStatus(typeof message.message === "string" ? message.message : "Suggestion failed.", "warning");
|
|
9161
|
+
return true;
|
|
9162
|
+
}
|
|
9163
|
+
const suggestion = typeof message.suggestion === "string" ? message.suggestion : "";
|
|
9164
|
+
if (!suggestion.trim()) {
|
|
9165
|
+
setStatus("Model returned an empty suggestion.", "warning");
|
|
9166
|
+
return true;
|
|
9167
|
+
}
|
|
9168
|
+
const text = String(sourceTextEl.value || "");
|
|
9169
|
+
if (pendingSnapshot && text !== pendingSnapshot.text) {
|
|
9170
|
+
setStatus("Editor changed while the suggestion was generating. Please request a fresh suggestion.", "warning");
|
|
9171
|
+
return true;
|
|
9172
|
+
}
|
|
9173
|
+
const baseText = pendingSnapshot ? pendingSnapshot.text : text;
|
|
9174
|
+
const start = Math.max(0, Math.min(pendingSnapshot ? pendingSnapshot.selectionStart : (Number(message.selectionStart) || 0), baseText.length));
|
|
9175
|
+
const end = Math.max(start, Math.min(pendingSnapshot ? pendingSnapshot.selectionEnd : (Number(message.selectionEnd) || start), baseText.length));
|
|
9176
|
+
showCompletionSuggestion({
|
|
9177
|
+
suggestion,
|
|
9178
|
+
baseText,
|
|
9179
|
+
selectionStart: start,
|
|
9180
|
+
selectionEnd: end,
|
|
9181
|
+
});
|
|
9182
|
+
const activeEl = document.activeElement;
|
|
9183
|
+
if (
|
|
9184
|
+
shouldRefocusEditor
|
|
9185
|
+
|| activeEl === sourceTextEl
|
|
9186
|
+
|| activeEl === suggestCompletionBtn
|
|
9187
|
+
|| activeEl === document.body
|
|
9188
|
+
|| activeEl === document.documentElement
|
|
9189
|
+
|| Boolean(completionSuggestionPanelEl && activeEl instanceof Element && completionSuggestionPanelEl.contains(activeEl))
|
|
9190
|
+
) {
|
|
9191
|
+
focusSourceEditorForCompletion();
|
|
9192
|
+
}
|
|
9193
|
+
setStatus("Suggestion ready. Press Tab to insert it, or use the Insert suggestion button.", "success");
|
|
9194
|
+
return true;
|
|
9195
|
+
}
|
|
9196
|
+
|
|
8989
9197
|
function getSourceTextLineEditBounds(text, selectionStart, selectionEnd) {
|
|
8990
9198
|
const source = String(text || "");
|
|
8991
9199
|
const safeStart = Math.max(0, Math.min(Math.floor(Number(selectionStart) || 0), source.length));
|
|
@@ -9065,7 +9273,14 @@
|
|
|
9065
9273
|
}
|
|
9066
9274
|
|
|
9067
9275
|
function handleSourceTextTabKey(event) {
|
|
9068
|
-
if (!event
|
|
9276
|
+
if (!event) return;
|
|
9277
|
+
if (isCompletionSuggestionRequestShortcut(event)) {
|
|
9278
|
+
event.preventDefault();
|
|
9279
|
+
requestCompletionSuggestion();
|
|
9280
|
+
return;
|
|
9281
|
+
}
|
|
9282
|
+
if (handleCompletionSuggestionAcceptKey(event)) return;
|
|
9283
|
+
if (event.key !== "Tab" || event.metaKey || event.ctrlKey || event.altKey) return;
|
|
9069
9284
|
event.preventDefault();
|
|
9070
9285
|
if (event.shiftKey) {
|
|
9071
9286
|
unindentSourceTextSelection();
|
|
@@ -16007,6 +16222,8 @@
|
|
|
16007
16222
|
updateFooterMeta();
|
|
16008
16223
|
}
|
|
16009
16224
|
|
|
16225
|
+
if (handleCompletionSuggestionServerMessage(message)) return;
|
|
16226
|
+
|
|
16010
16227
|
if (
|
|
16011
16228
|
message.type === "quiz_progress" ||
|
|
16012
16229
|
message.type === "quiz_generated" ||
|
|
@@ -17258,6 +17475,9 @@
|
|
|
17258
17475
|
sourceTextEl.addEventListener("keydown", handleSourceTextTabKey);
|
|
17259
17476
|
|
|
17260
17477
|
sourceTextEl.addEventListener("input", () => {
|
|
17478
|
+
if (completionSuggestionState && sourceTextEl.value !== completionSuggestionState.baseText) {
|
|
17479
|
+
hideCompletionSuggestion();
|
|
17480
|
+
}
|
|
17261
17481
|
if (activePreviewCommentSelection) {
|
|
17262
17482
|
clearPreviewCommentSelection();
|
|
17263
17483
|
}
|
|
@@ -17266,6 +17486,7 @@
|
|
|
17266
17486
|
scheduleEditorMetaUpdate();
|
|
17267
17487
|
updateEditorSelectionCommentUi();
|
|
17268
17488
|
updateOutlineUi();
|
|
17489
|
+
syncActionButtons();
|
|
17269
17490
|
if (isReviewNotesOpen() && reviewNotes.length > 0) {
|
|
17270
17491
|
renderReviewNotesList();
|
|
17271
17492
|
updateReviewNotesUi();
|
|
@@ -17721,6 +17942,24 @@
|
|
|
17721
17942
|
}
|
|
17722
17943
|
});
|
|
17723
17944
|
|
|
17945
|
+
if (suggestCompletionBtn) {
|
|
17946
|
+
suggestCompletionBtn.addEventListener("click", () => {
|
|
17947
|
+
requestCompletionSuggestion();
|
|
17948
|
+
});
|
|
17949
|
+
}
|
|
17950
|
+
if (completionSuggestionInsertBtn) {
|
|
17951
|
+
completionSuggestionInsertBtn.addEventListener("click", () => {
|
|
17952
|
+
insertCompletionSuggestion();
|
|
17953
|
+
});
|
|
17954
|
+
}
|
|
17955
|
+
if (completionSuggestionDismissBtn) {
|
|
17956
|
+
completionSuggestionDismissBtn.addEventListener("click", () => {
|
|
17957
|
+
hideCompletionSuggestion();
|
|
17958
|
+
focusSourceTextNoScroll();
|
|
17959
|
+
setStatus("Dismissed completion suggestion.");
|
|
17960
|
+
});
|
|
17961
|
+
}
|
|
17962
|
+
|
|
17724
17963
|
if (reviewNotesBtn) {
|
|
17725
17964
|
reviewNotesBtn.addEventListener("click", () => {
|
|
17726
17965
|
toggleReviewNotes();
|
package/client/studio.css
CHANGED
|
@@ -849,6 +849,63 @@
|
|
|
849
849
|
-webkit-text-fill-color: transparent;
|
|
850
850
|
}
|
|
851
851
|
|
|
852
|
+
.completion-suggestion-panel {
|
|
853
|
+
border: 1px solid var(--control-border);
|
|
854
|
+
border-radius: 10px;
|
|
855
|
+
background: var(--panel-2);
|
|
856
|
+
box-shadow: var(--panel-shadow);
|
|
857
|
+
overflow: hidden;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.completion-suggestion-panel[hidden] {
|
|
861
|
+
display: none !important;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
.completion-suggestion-header,
|
|
865
|
+
.completion-suggestion-actions {
|
|
866
|
+
display: flex;
|
|
867
|
+
align-items: center;
|
|
868
|
+
justify-content: space-between;
|
|
869
|
+
gap: 8px;
|
|
870
|
+
padding: 8px 10px;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.completion-suggestion-header {
|
|
874
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
875
|
+
color: var(--studio-info-text, var(--muted));
|
|
876
|
+
font-size: 12px;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.completion-suggestion-header strong {
|
|
880
|
+
color: var(--text);
|
|
881
|
+
font-weight: 650;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
.completion-suggestion-text {
|
|
885
|
+
margin: 0;
|
|
886
|
+
max-height: 180px;
|
|
887
|
+
overflow: auto;
|
|
888
|
+
padding: 10px;
|
|
889
|
+
white-space: pre-wrap;
|
|
890
|
+
word-break: break-word;
|
|
891
|
+
font-family: var(--font-mono);
|
|
892
|
+
font-size: var(--studio-editor-font-size);
|
|
893
|
+
line-height: 1.45;
|
|
894
|
+
color: var(--text);
|
|
895
|
+
background: var(--editor-bg);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
.completion-suggestion-actions {
|
|
899
|
+
justify-content: flex-end;
|
|
900
|
+
border-top: 1px solid var(--border-subtle);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.completion-suggestion-header button,
|
|
904
|
+
.completion-suggestion-actions button {
|
|
905
|
+
padding: 4px 8px;
|
|
906
|
+
font-size: 12px;
|
|
907
|
+
}
|
|
908
|
+
|
|
852
909
|
.editor-selection-actions {
|
|
853
910
|
position: absolute;
|
|
854
911
|
top: 12px;
|
|
@@ -3766,8 +3823,8 @@
|
|
|
3766
3823
|
|
|
3767
3824
|
.shortcuts-group dl > div {
|
|
3768
3825
|
display: grid;
|
|
3769
|
-
grid-template-columns: minmax(
|
|
3770
|
-
gap:
|
|
3826
|
+
grid-template-columns: minmax(260px, 260px) minmax(0, 1fr);
|
|
3827
|
+
gap: 24px;
|
|
3771
3828
|
align-items: baseline;
|
|
3772
3829
|
padding: 6px 10px;
|
|
3773
3830
|
border-top: 1px solid var(--border-subtle);
|
|
@@ -4188,13 +4245,18 @@
|
|
|
4188
4245
|
justify-content: stretch;
|
|
4189
4246
|
}
|
|
4190
4247
|
|
|
4191
|
-
body.studio-ui-refresh .studio-refresh-pane-tools
|
|
4192
|
-
body.studio-ui-refresh .studio-refresh-toolbar-state {
|
|
4248
|
+
body.studio-ui-refresh .studio-refresh-pane-tools {
|
|
4193
4249
|
justify-content: flex-start;
|
|
4194
4250
|
justify-items: start;
|
|
4195
4251
|
min-width: 0;
|
|
4196
4252
|
}
|
|
4197
4253
|
|
|
4254
|
+
body.studio-ui-refresh .studio-refresh-toolbar-state {
|
|
4255
|
+
justify-content: flex-end;
|
|
4256
|
+
justify-items: end;
|
|
4257
|
+
min-width: 0;
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4198
4260
|
body.studio-ui-refresh .studio-refresh-pane-tools,
|
|
4199
4261
|
body.studio-ui-refresh .studio-refresh-title-group,
|
|
4200
4262
|
body.studio-ui-refresh .studio-refresh-utility-left {
|
|
@@ -4202,7 +4264,7 @@
|
|
|
4202
4264
|
}
|
|
4203
4265
|
|
|
4204
4266
|
body.studio-ui-refresh .studio-refresh-toolbar-state .studio-refresh-action-line {
|
|
4205
|
-
justify-content: flex-
|
|
4267
|
+
justify-content: flex-end;
|
|
4206
4268
|
}
|
|
4207
4269
|
}
|
|
4208
4270
|
|
|
@@ -4664,6 +4726,12 @@
|
|
|
4664
4726
|
font-size: 12px;
|
|
4665
4727
|
}
|
|
4666
4728
|
|
|
4729
|
+
body.studio-ui-refresh .studio-refresh-toolbar-actions .studio-refresh-action-line button:not(#sendRunBtn):not(#queueSteerBtn):not(#sendReplBtn):not(.request-stop-active) {
|
|
4730
|
+
min-height: 26px;
|
|
4731
|
+
padding: 5px 8px;
|
|
4732
|
+
line-height: 1.2;
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4667
4735
|
body.studio-ui-refresh .studio-refresh-toolbar button:not(#sendRunBtn):not(#queueSteerBtn):not(#sendReplBtn):not(.request-stop-active),
|
|
4668
4736
|
body.studio-ui-refresh .studio-refresh-toolbar select {
|
|
4669
4737
|
color: color-mix(in srgb, var(--text) 72%, var(--muted));
|
package/index.ts
CHANGED
|
@@ -311,6 +311,17 @@ interface SendRunRequestMessage {
|
|
|
311
311
|
text: string;
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
interface CompletionSuggestionRequestMessage {
|
|
315
|
+
type: "completion_suggestion_request";
|
|
316
|
+
requestId: string;
|
|
317
|
+
text: string;
|
|
318
|
+
selectionStart: number;
|
|
319
|
+
selectionEnd: number;
|
|
320
|
+
language?: string;
|
|
321
|
+
label?: string;
|
|
322
|
+
path?: string;
|
|
323
|
+
}
|
|
324
|
+
|
|
314
325
|
interface QuizGenerateRequestMessage {
|
|
315
326
|
type: "quiz_generate_request";
|
|
316
327
|
requestId: string;
|
|
@@ -451,6 +462,7 @@ type IncomingStudioMessage =
|
|
|
451
462
|
| CritiqueRequestMessage
|
|
452
463
|
| AnnotationRequestMessage
|
|
453
464
|
| SendRunRequestMessage
|
|
465
|
+
| CompletionSuggestionRequestMessage
|
|
454
466
|
| QuizGenerateRequestMessage
|
|
455
467
|
| QuizAnswerRequestMessage
|
|
456
468
|
| QuizDiscussRequestMessage
|
|
@@ -472,6 +484,9 @@ type IncomingStudioMessage =
|
|
|
472
484
|
|
|
473
485
|
const REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
|
|
474
486
|
const PREVIEW_RENDER_MAX_CHARS = 400_000;
|
|
487
|
+
const STUDIO_COMPLETION_MAX_TEXT_CHARS = 250_000;
|
|
488
|
+
const STUDIO_COMPLETION_PREFIX_CHARS = 12_000;
|
|
489
|
+
const STUDIO_COMPLETION_SUFFIX_CHARS = 6_000;
|
|
475
490
|
const PDF_EXPORT_MAX_CHARS = 400_000;
|
|
476
491
|
const HTML_EXPORT_MAX_CHARS = 400_000;
|
|
477
492
|
const HTML_PREVIEW_MATH_RENDER_MAX_ITEMS = 250;
|
|
@@ -7118,7 +7133,7 @@ async function resolveStudioModelRequestAuth(ctx: StudioModelRequestContext, mod
|
|
|
7118
7133
|
if (typeof registry.getApiKey === "function") {
|
|
7119
7134
|
return { apiKey: await registry.getApiKey(model) };
|
|
7120
7135
|
}
|
|
7121
|
-
throw new Error("Current pi model registry does not expose model credentials for Studio
|
|
7136
|
+
throw new Error("Current pi model registry does not expose model credentials for Studio model requests.");
|
|
7122
7137
|
}
|
|
7123
7138
|
|
|
7124
7139
|
function getStudioQuizReasoning(model: NonNullable<ExtensionContext["model"]>, thinking: StudioQuizThinking | undefined): ThinkingLevel | undefined {
|
|
@@ -7127,33 +7142,47 @@ function getStudioQuizReasoning(model: NonNullable<ExtensionContext["model"]>, t
|
|
|
7127
7142
|
return normalized === "off" ? undefined : normalized;
|
|
7128
7143
|
}
|
|
7129
7144
|
|
|
7130
|
-
async function
|
|
7145
|
+
async function runStudioModelText(
|
|
7146
|
+
ctx: StudioModelRequestContext,
|
|
7147
|
+
prompt: string,
|
|
7148
|
+
options?: { systemPrompt?: string; maxTokens?: number; signal?: AbortSignal; reasoning?: ThinkingLevel; timeoutMs?: number; trim?: boolean },
|
|
7149
|
+
): Promise<string> {
|
|
7131
7150
|
if (!ctx.model) throw new Error("No active model selected.");
|
|
7132
7151
|
const auth = await resolveStudioModelRequestAuth(ctx, ctx.model);
|
|
7133
7152
|
const response = await completeSimple(
|
|
7134
7153
|
ctx.model,
|
|
7135
7154
|
{
|
|
7136
|
-
systemPrompt: "You are
|
|
7155
|
+
systemPrompt: options?.systemPrompt ?? "You are a concise assistant inside pi Studio. Return exactly the requested format.",
|
|
7137
7156
|
messages: [{ role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() }],
|
|
7138
7157
|
},
|
|
7139
7158
|
{
|
|
7140
7159
|
apiKey: auth.apiKey,
|
|
7141
7160
|
headers: auth.headers,
|
|
7142
|
-
reasoning:
|
|
7161
|
+
reasoning: options?.reasoning,
|
|
7143
7162
|
maxTokens: options?.maxTokens ?? 2500,
|
|
7144
7163
|
signal: options?.signal,
|
|
7145
|
-
timeoutMs: 120_000,
|
|
7164
|
+
timeoutMs: options?.timeoutMs ?? 120_000,
|
|
7146
7165
|
},
|
|
7147
7166
|
);
|
|
7148
|
-
const
|
|
7167
|
+
const rawText = response.content
|
|
7149
7168
|
.filter((part): part is { type: "text"; text: string } => part.type === "text")
|
|
7150
7169
|
.map((part) => part.text)
|
|
7151
|
-
.join("\n")
|
|
7152
|
-
|
|
7153
|
-
if (!text) throw new Error("Model returned no text response.");
|
|
7170
|
+
.join("\n");
|
|
7171
|
+
const text = options?.trim === false ? rawText : rawText.trim();
|
|
7172
|
+
if (!text.trim()) throw new Error("Model returned no text response.");
|
|
7154
7173
|
return text;
|
|
7155
7174
|
}
|
|
7156
7175
|
|
|
7176
|
+
async function runStudioQuizModelText(ctx: StudioModelRequestContext, prompt: string, options?: { maxTokens?: number; signal?: AbortSignal; thinking?: StudioQuizThinking }): Promise<string> {
|
|
7177
|
+
return runStudioModelText(ctx, prompt, {
|
|
7178
|
+
systemPrompt: "You are an active tutor inside pi Studio. Ask and mark concise, probing quiz questions. Return exactly the requested format.",
|
|
7179
|
+
reasoning: ctx.model ? getStudioQuizReasoning(ctx.model, options?.thinking) : undefined,
|
|
7180
|
+
maxTokens: options?.maxTokens ?? 2500,
|
|
7181
|
+
signal: options?.signal,
|
|
7182
|
+
timeoutMs: 120_000,
|
|
7183
|
+
});
|
|
7184
|
+
}
|
|
7185
|
+
|
|
7157
7186
|
async function runStudioQuizModelJson(
|
|
7158
7187
|
ctx: StudioModelRequestContext,
|
|
7159
7188
|
prompt: string,
|
|
@@ -7181,6 +7210,72 @@ async function runStudioQuizModelJson(
|
|
|
7181
7210
|
throw lastError ?? new Error("Model did not return valid JSON.");
|
|
7182
7211
|
}
|
|
7183
7212
|
|
|
7213
|
+
function buildStudioCompletionSuggestionPrompt(options: {
|
|
7214
|
+
text: string;
|
|
7215
|
+
selectionStart: number;
|
|
7216
|
+
selectionEnd: number;
|
|
7217
|
+
language?: string;
|
|
7218
|
+
label?: string;
|
|
7219
|
+
path?: string;
|
|
7220
|
+
}): string {
|
|
7221
|
+
const text = String(options.text || "");
|
|
7222
|
+
const start = Math.max(0, Math.min(Math.floor(options.selectionStart || 0), text.length));
|
|
7223
|
+
const end = Math.max(start, Math.min(Math.floor(options.selectionEnd || start), text.length));
|
|
7224
|
+
const prefix = text.slice(Math.max(0, start - STUDIO_COMPLETION_PREFIX_CHARS), start);
|
|
7225
|
+
const selected = text.slice(start, end);
|
|
7226
|
+
const suffix = text.slice(end, Math.min(text.length, end + STUDIO_COMPLETION_SUFFIX_CHARS));
|
|
7227
|
+
const language = String(options.language || "").trim() || "unknown";
|
|
7228
|
+
const label = String(options.label || options.path || "Studio editor").trim();
|
|
7229
|
+
return [
|
|
7230
|
+
"Generate an inline completion for the current editor cursor position.",
|
|
7231
|
+
"Return only the exact text to insert. Do not wrap it in Markdown fences. Do not explain.",
|
|
7232
|
+
"Match the surrounding language, style, indentation, and register.",
|
|
7233
|
+
"Keep the suggestion short unless the context clearly asks for a longer continuation.",
|
|
7234
|
+
selected
|
|
7235
|
+
? "The selected text will be replaced by the completion."
|
|
7236
|
+
: "The completion will be inserted at the cursor.",
|
|
7237
|
+
"",
|
|
7238
|
+
`File/context label: ${label}`,
|
|
7239
|
+
`Language mode: ${language}`,
|
|
7240
|
+
"",
|
|
7241
|
+
"<prefix>",
|
|
7242
|
+
prefix,
|
|
7243
|
+
"</prefix>",
|
|
7244
|
+
selected ? ["", "<selected>", selected, "</selected>"].join("\n") : "",
|
|
7245
|
+
"",
|
|
7246
|
+
"<suffix>",
|
|
7247
|
+
suffix,
|
|
7248
|
+
"</suffix>",
|
|
7249
|
+
].filter((part) => part !== "").join("\n");
|
|
7250
|
+
}
|
|
7251
|
+
|
|
7252
|
+
function cleanStudioCompletionSuggestion(text: string): string {
|
|
7253
|
+
let value = String(text || "").replace(/\r\n/g, "\n");
|
|
7254
|
+
value = value.replace(/^\s*(?:Here(?:'s| is) (?:the )?(?:completion|suggestion):|Completion:|Suggestion:)\s*/i, "");
|
|
7255
|
+
return value;
|
|
7256
|
+
}
|
|
7257
|
+
|
|
7258
|
+
async function runStudioCompletionSuggestion(ctx: StudioModelRequestContext, options: {
|
|
7259
|
+
text: string;
|
|
7260
|
+
selectionStart: number;
|
|
7261
|
+
selectionEnd: number;
|
|
7262
|
+
language?: string;
|
|
7263
|
+
label?: string;
|
|
7264
|
+
path?: string;
|
|
7265
|
+
}): Promise<string> {
|
|
7266
|
+
const prompt = buildStudioCompletionSuggestionPrompt(options);
|
|
7267
|
+
// Intentionally omit `reasoning`: pi-ai treats absent reasoning as off/disabled
|
|
7268
|
+
// where supported. Passing "minimal" would still enable a reasoning path and slow completions.
|
|
7269
|
+
const suggestion = cleanStudioCompletionSuggestion(await runStudioModelText(ctx, prompt, {
|
|
7270
|
+
systemPrompt: "You are an inline autocomplete engine inside pi Studio. Return only text to insert at the cursor. Never explain. Never include Markdown fences unless literal fences are the intended insertion.",
|
|
7271
|
+
maxTokens: 650,
|
|
7272
|
+
timeoutMs: 60_000,
|
|
7273
|
+
trim: false,
|
|
7274
|
+
}));
|
|
7275
|
+
if (!suggestion.trim()) throw new Error("Model returned an empty completion suggestion.");
|
|
7276
|
+
return suggestion;
|
|
7277
|
+
}
|
|
7278
|
+
|
|
7184
7279
|
function inferStudioResponseKind(markdown: string): StudioRequestKind {
|
|
7185
7280
|
const lower = markdown.toLowerCase();
|
|
7186
7281
|
if (lower.includes("## critiques") && lower.includes("## document")) return "critique";
|
|
@@ -7590,6 +7685,24 @@ function parseIncomingMessage(data: RawData): IncomingStudioMessage | null {
|
|
|
7590
7685
|
};
|
|
7591
7686
|
}
|
|
7592
7687
|
|
|
7688
|
+
if (msg.type === "completion_suggestion_request" && typeof msg.requestId === "string" && typeof msg.text === "string") {
|
|
7689
|
+
const textLength = msg.text.length;
|
|
7690
|
+
const rawStart = typeof msg.selectionStart === "number" && Number.isFinite(msg.selectionStart) ? msg.selectionStart : textLength;
|
|
7691
|
+
const rawEnd = typeof msg.selectionEnd === "number" && Number.isFinite(msg.selectionEnd) ? msg.selectionEnd : rawStart;
|
|
7692
|
+
const selectionStart = Math.max(0, Math.min(Math.floor(rawStart), textLength));
|
|
7693
|
+
const selectionEnd = Math.max(selectionStart, Math.min(Math.floor(rawEnd), textLength));
|
|
7694
|
+
return {
|
|
7695
|
+
type: "completion_suggestion_request",
|
|
7696
|
+
requestId: msg.requestId,
|
|
7697
|
+
text: msg.text,
|
|
7698
|
+
selectionStart,
|
|
7699
|
+
selectionEnd,
|
|
7700
|
+
language: typeof msg.language === "string" ? msg.language : undefined,
|
|
7701
|
+
label: typeof msg.label === "string" ? msg.label : undefined,
|
|
7702
|
+
path: typeof msg.path === "string" ? msg.path : undefined,
|
|
7703
|
+
};
|
|
7704
|
+
}
|
|
7705
|
+
|
|
7593
7706
|
if (msg.type === "quiz_generate_request" && typeof msg.requestId === "string" && typeof msg.sourceText === "string") {
|
|
7594
7707
|
const rawCount = typeof msg.questionCount === "number" && Number.isFinite(msg.questionCount) ? msg.questionCount : 5;
|
|
7595
7708
|
return {
|
|
@@ -9467,8 +9580,9 @@ ${cssVarsBlock}
|
|
|
9467
9580
|
</select>
|
|
9468
9581
|
</div>
|
|
9469
9582
|
<div class="source-actions-row">
|
|
9470
|
-
<button id="copyDraftBtn" type="button" title="Copy the current editor text to the clipboard.">Copy
|
|
9471
|
-
<button id="
|
|
9583
|
+
<button id="copyDraftBtn" type="button" title="Copy the current editor text to the clipboard.">Copy</button>
|
|
9584
|
+
<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>
|
|
9585
|
+
<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>
|
|
9472
9586
|
<button id="sendEditorBtn" type="button">Send to pi editor</button>
|
|
9473
9587
|
</div>
|
|
9474
9588
|
<div class="source-actions-row">
|
|
@@ -9550,6 +9664,16 @@ ${cssVarsBlock}
|
|
|
9550
9664
|
<button id="editorSelectionJumpBtn" type="button" class="editor-selection-action-btn" hidden title="Jump to the current editor selection in the preview.">Jump</button>
|
|
9551
9665
|
</div>
|
|
9552
9666
|
</div>
|
|
9667
|
+
<div id="completionSuggestionPanel" class="completion-suggestion-panel" hidden>
|
|
9668
|
+
<div class="completion-suggestion-header">
|
|
9669
|
+
<strong>Suggested completion</strong>
|
|
9670
|
+
<button id="completionSuggestionDismissBtn" type="button" title="Dismiss this suggestion">Dismiss</button>
|
|
9671
|
+
</div>
|
|
9672
|
+
<pre id="completionSuggestionText" class="completion-suggestion-text"></pre>
|
|
9673
|
+
<div class="completion-suggestion-actions">
|
|
9674
|
+
<button id="completionSuggestionInsertBtn" type="button" title="Insert this suggestion at the cursor or original selection. You can also press Tab while the editor is focused.">Insert suggestion (Tab)</button>
|
|
9675
|
+
</div>
|
|
9676
|
+
</div>
|
|
9553
9677
|
<div id="sourcePreview" class="panel-scroll rendered-markdown" hidden><pre class="plain-markdown"></pre></div>
|
|
9554
9678
|
</div>
|
|
9555
9679
|
<aside id="outlineOverlay" class="outline-dock-wrap" hidden>
|
|
@@ -9718,7 +9842,9 @@ ${cssVarsBlock}
|
|
|
9718
9842
|
<dl>
|
|
9719
9843
|
<div><dt>Cmd/Ctrl+S</dt><dd>Save editor</dd></div>
|
|
9720
9844
|
<div><dt>Cmd/Ctrl+Enter</dt><dd>Run editor text, or queue steering during an active run</dd></div>
|
|
9721
|
-
<div><dt>Tab /
|
|
9845
|
+
<div><dt>Option/Alt+Tab or Cmd/Ctrl+Shift+Space</dt><dd>Suggest a completion at the editor cursor</dd></div>
|
|
9846
|
+
<div><dt>Tab</dt><dd>Insert a visible completion suggestion; otherwise indent selected editor text</dd></div>
|
|
9847
|
+
<div><dt>Shift+Tab</dt><dd>Unindent selected editor text</dd></div>
|
|
9722
9848
|
</dl>
|
|
9723
9849
|
</section>
|
|
9724
9850
|
<section class="shortcuts-group">
|
|
@@ -11298,6 +11424,53 @@ export default function (pi: ExtensionAPI) {
|
|
|
11298
11424
|
return;
|
|
11299
11425
|
}
|
|
11300
11426
|
|
|
11427
|
+
if (msg.type === "completion_suggestion_request") {
|
|
11428
|
+
if (!isValidRequestId(msg.requestId)) {
|
|
11429
|
+
sendToClient(client, { type: "completion_suggestion_error", requestId: msg.requestId, message: "Invalid request ID." });
|
|
11430
|
+
return;
|
|
11431
|
+
}
|
|
11432
|
+
const ctx = latestModelRequestCtx ?? lastCommandCtx;
|
|
11433
|
+
if (!ctx) {
|
|
11434
|
+
sendToClient(client, { type: "completion_suggestion_error", requestId: msg.requestId, message: "No active pi model context is available for editor suggestions." });
|
|
11435
|
+
return;
|
|
11436
|
+
}
|
|
11437
|
+
if (!msg.text.trim()) {
|
|
11438
|
+
sendToClient(client, { type: "completion_suggestion_error", requestId: msg.requestId, message: "Editor is empty." });
|
|
11439
|
+
return;
|
|
11440
|
+
}
|
|
11441
|
+
if (msg.text.length > STUDIO_COMPLETION_MAX_TEXT_CHARS) {
|
|
11442
|
+
sendToClient(client, { type: "completion_suggestion_error", requestId: msg.requestId, message: `Editor text is too large for suggestions (${STUDIO_COMPLETION_MAX_TEXT_CHARS} character limit).` });
|
|
11443
|
+
return;
|
|
11444
|
+
}
|
|
11445
|
+
sendToClient(client, { type: "completion_suggestion_progress", requestId: msg.requestId, message: "Generating suggestion…" });
|
|
11446
|
+
void (async () => {
|
|
11447
|
+
try {
|
|
11448
|
+
const suggestion = await runStudioCompletionSuggestion(ctx, {
|
|
11449
|
+
text: msg.text,
|
|
11450
|
+
selectionStart: msg.selectionStart,
|
|
11451
|
+
selectionEnd: msg.selectionEnd,
|
|
11452
|
+
language: msg.language,
|
|
11453
|
+
label: msg.label,
|
|
11454
|
+
path: msg.path,
|
|
11455
|
+
});
|
|
11456
|
+
sendToClient(client, {
|
|
11457
|
+
type: "completion_suggestion_result",
|
|
11458
|
+
requestId: msg.requestId,
|
|
11459
|
+
suggestion,
|
|
11460
|
+
selectionStart: msg.selectionStart,
|
|
11461
|
+
selectionEnd: msg.selectionEnd,
|
|
11462
|
+
});
|
|
11463
|
+
} catch (error) {
|
|
11464
|
+
sendToClient(client, {
|
|
11465
|
+
type: "completion_suggestion_error",
|
|
11466
|
+
requestId: msg.requestId,
|
|
11467
|
+
message: `Suggestion failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
11468
|
+
});
|
|
11469
|
+
}
|
|
11470
|
+
})();
|
|
11471
|
+
return;
|
|
11472
|
+
}
|
|
11473
|
+
|
|
11301
11474
|
if (msg.type === "quiz_generate_request") {
|
|
11302
11475
|
if (!isValidRequestId(msg.requestId)) {
|
|
11303
11476
|
sendToClient(client, { type: "error", requestId: msg.requestId, message: "Invalid request ID." });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.16",
|
|
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",
|