pi-studio 0.9.54 → 0.9.55
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 +9 -0
- package/README.md +2 -1
- package/ROADMAP.md +8 -0
- package/client/studio-client.js +96 -22
- package/client/studio.css +141 -5
- package/index.ts +72 -50
- package/package.json +1 -1
- package/shared/studio-side-question.js +32 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.55] — 2026-09-02
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Let side questions use **X-high** and **Max** thinking when the active model supports them, while retaining **Low** as the default.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Open watched previews launched from Files or local Studio links with the preview pane focused, matching `/studio --watch`, and label the exposed raw source clearly as read-only and disk-following.
|
|
14
|
+
- Normalize Studio buttons and dropdown select controls with flat browser-neutral styling and consistent chevrons instead of WebKit's native shaded controls.
|
|
15
|
+
|
|
7
16
|
## [0.9.54] — 2026-09-02
|
|
8
17
|
|
|
9
18
|
### Added
|
package/README.md
CHANGED
|
@@ -118,6 +118,7 @@ Open **Review → Side question**, select **Side questions** in the right pane,
|
|
|
118
118
|
- **Also use files from** controls the read-only boundary available to the side agent: no other files, the document folder, the current repository, or another chosen folder. Related-file access requires an allowed folder for the current Studio session; if the selected root is not already allowed, Studio names the canonical folder and asks before starting the thread. An exact-file grant never exposes its parent folder.
|
|
119
119
|
- With **Repository** selected, **Include Git context** can capture status, staged and unstaged tracked-file changes, and up to 20 recent commit summaries. The bounded snapshot is frozen when the thread starts and exposed through fixed read-only tools; untracked contents remain available only through the existing root-confined file reader.
|
|
120
120
|
- **Include the current main conversation snapshot** is opt-in. Side questions and answers otherwise never enter the main Pi history; **Bring to main conversation** is the explicit handoff.
|
|
121
|
+
- **Thinking** defaults to **Low** for quick asides. **X-high** and **Max** appear when the active model advertises support, for complex side questions where the extra latency is worthwhile.
|
|
121
122
|
- Local context tools can map filenames, search readable text, and read selected ranges. Studio revalidates the allowed context root before prompts and built-in local tool calls; symlinks, root replacement, and traversal outside that root are rejected. PDF extraction uses `pdftotext`; DOCX, ODT, and EPUB extraction uses Pandoc.
|
|
122
123
|
- **Allow web search** is opt-in and appears when `BRAVE_API_KEY` is available to the Pi process. Model-chosen search queries are sent to Brave Search; the side agent is instructed not to copy private local passages into queries. Web answers cite result URLs and identify when they rely on search-result snippets rather than full page content.
|
|
123
124
|
- **Additional Pi tools** lists eligible tools already registered by the user's installed Pi extensions. Selection is explicit, remembered locally, and frozen when the thread starts. Studio reloads only the extensions owning the selected tools into the isolated side runtime and activates only those tool names; it does not import or depend on any particular third-party extension. Gateway tools are labelled and require an additional confirmation because selecting one may expose further services configured behind it.
|
|
@@ -151,7 +152,7 @@ caption: Optional caption
|
|
|
151
152
|
|
|
152
153
|
To view an existing PDF directly, run `/studio report.pdf` (or `/studio "path with spaces/report.pdf"`). The PDF opens read-only in a focused companion preview, even when the full Studio workspace is already open. A `#page=N` suffix selects the initial page. Auto-refresh is off by default; use `/studio --watch report.pdf` to start with it on, or toggle **Auto-refresh** in the PDF card or focused viewer. Studio checks only while the tab is visible and waits for two matching file-version observations before reloading, which avoids reading a PDF while LaTeX is still writing it. **Cmd/Ctrl+Alt+R** manually refreshes the focused or visible PDF. This is distinct from `/studio-pdf`, which exports Markdown, LaTeX, code, or the last response to a new PDF.
|
|
153
154
|
|
|
154
|
-
For a disk-authoritative text or code preview, run `/studio --watch notes.md` or choose **Preview file (follow changes)** from a local-link menu / **Preview (follow)** in Files. This opens a transient read-only companion tab rather than an editable buffer. Studio polls the canonical file, debounces change bursts, compares SHA-256 content revisions, accepts ordinary and atomic-replacement saves, and never writes back. Each watcher socket is bound to the exact path in a bounded, expiring server-created tab capability; if that identity expires before a reload or reconnect, the tab stays read-only and asks you to open a fresh watched preview. A missing, unreadable, oversized, binary, or path-replaced source leaves the last good preview visible and automatically recovers when the same canonical file becomes readable again. Render failures likewise retain the last successfully staged document rather than replacing it with partial output. On successful updates, Studio restores a nearby stable content anchor where possible and falls back to the previous scroll ratio.
|
|
155
|
+
For a disk-authoritative text or code preview, run `/studio --watch notes.md` or choose **Preview file (follow changes)** from a local-link menu / **Preview (follow)** in Files. This opens a transient read-only companion tab with the preview pane focused; leaving pane focus reveals the same disk-following text as **Source** with a visible **Read-only · follows disk** badge rather than as an editable buffer. Studio polls the canonical file, debounces change bursts, compares SHA-256 content revisions, accepts ordinary and atomic-replacement saves, and never writes back. Each watcher socket is bound to the exact path in a bounded, expiring server-created tab capability; if that identity expires before a reload or reconnect, the tab stays read-only and asks you to open a fresh watched preview. A missing, unreadable, oversized, binary, or path-replaced source leaves the last good preview visible and automatically recovers when the same canonical file becomes readable again. Render failures likewise retain the last successfully staged document rather than replacing it with partial output. On successful updates, Studio restores a nearby stable content anchor where possible and falls back to the previous scroll ratio.
|
|
155
156
|
|
|
156
157
|
### Mermaid icons
|
|
157
158
|
|
package/ROADMAP.md
CHANGED
|
@@ -61,6 +61,14 @@ Builds on the resource-location foundation:
|
|
|
61
61
|
- add **Cmd/Ctrl+Shift+S** for Save As and keep autosave opt-in rather than default;
|
|
62
62
|
- carry the resulting disk identity and revision model forward into `StudioBufferStore`.
|
|
63
63
|
|
|
64
|
+
## 0.9.55 — Watched-preview and control polish (shipped 2026-09-02)
|
|
65
|
+
|
|
66
|
+
A browser-neutral UX pass before buffer-first work:
|
|
67
|
+
|
|
68
|
+
- focus followed previews on their rendered pane and label the exposed source as read-only and disk-following;
|
|
69
|
+
- normalize Studio buttons and dropdown controls across Chromium and WebKit without changing native select interaction;
|
|
70
|
+
- expose X-high and Max side-question thinking only when supported by the active model, while retaining Low as the default.
|
|
71
|
+
|
|
64
72
|
## 0.10.0 — Buffer-first editing
|
|
65
73
|
|
|
66
74
|
The first architectural `0.10` release should add:
|
package/client/studio-client.js
CHANGED
|
@@ -74,7 +74,9 @@
|
|
|
74
74
|
: null;
|
|
75
75
|
const referenceBadgeEl = document.getElementById("referenceBadge");
|
|
76
76
|
const editorViewSelect = document.getElementById("editorViewSelect");
|
|
77
|
+
const editorViewSelectWrap = document.getElementById("editorViewSelectWrap");
|
|
77
78
|
const rightViewSelect = document.getElementById("rightViewSelect");
|
|
79
|
+
const rightViewSelectWrap = document.getElementById("rightViewSelectWrap");
|
|
78
80
|
const followSelect = document.getElementById("followSelect");
|
|
79
81
|
const responseHighlightSelect = document.getElementById("responseHighlightSelect");
|
|
80
82
|
const responseFontSizeSelect = document.getElementById("responseFontSizeSelect");
|
|
@@ -486,6 +488,7 @@
|
|
|
486
488
|
const EDITOR_TAB_TEXT = " ";
|
|
487
489
|
const QUIZ_DEFAULT_COUNT = 5;
|
|
488
490
|
const SIDE_QUESTION_THINKING_STORAGE_KEY = "piStudio.sideQuestionThinking";
|
|
491
|
+
const SIDE_QUESTION_THINKING_LEVELS = Object.freeze(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
489
492
|
const SIDE_QUESTION_GATHER_STORAGE_KEY = "piStudio.sideQuestionGatherScope";
|
|
490
493
|
const SIDE_QUESTION_TOOLS_STORAGE_KEY = "piStudio.sideQuestionTools";
|
|
491
494
|
const SIDE_QUESTION_TRANSCRIPT_RENDER_MAX_CHARS = 400_000;
|
|
@@ -496,6 +499,7 @@
|
|
|
496
499
|
const QUIZ_ANGLES = ["general", "scientist", "mathematician", "statistician", "developer", "reviewer"];
|
|
497
500
|
const QUIZ_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
498
501
|
let sideQuestionState = null;
|
|
502
|
+
let sideQuestionThinkingLevels = [...SIDE_QUESTION_THINKING_LEVELS];
|
|
499
503
|
let sideQuestionWebSearchAvailable = false;
|
|
500
504
|
let sideQuestionAvailablePiTools = [];
|
|
501
505
|
let sideQuestionPreviewRenderNonce = 0;
|
|
@@ -524,11 +528,12 @@
|
|
|
524
528
|
thinking: (() => {
|
|
525
529
|
try {
|
|
526
530
|
const value = window.localStorage && window.localStorage.getItem(SIDE_QUESTION_THINKING_STORAGE_KEY);
|
|
527
|
-
return
|
|
531
|
+
return SIDE_QUESTION_THINKING_LEVELS.includes(value) ? value : "low";
|
|
528
532
|
} catch { return "low"; }
|
|
529
533
|
})(),
|
|
530
534
|
draft: "",
|
|
531
535
|
};
|
|
536
|
+
let sideQuestionPreferredThinking = sideQuestionUi.thinking;
|
|
532
537
|
let quizOverlayEl = null;
|
|
533
538
|
let quizDialogEl = null;
|
|
534
539
|
let quizPreviewRenderNonce = 0;
|
|
@@ -2914,9 +2919,28 @@
|
|
|
2914
2919
|
}
|
|
2915
2920
|
titleGroupEl.appendChild(makeStudioUiRefreshSeparator());
|
|
2916
2921
|
if (isEditorOnlyMode) {
|
|
2917
|
-
|
|
2922
|
+
const staticTitleEl = makeStudioUiRefreshElement(
|
|
2923
|
+
"span",
|
|
2924
|
+
"studio-refresh-static-title",
|
|
2925
|
+
isWatchedFilePreview ? "Source" : "Editor (Raw)",
|
|
2926
|
+
);
|
|
2927
|
+
if (isWatchedFilePreview) {
|
|
2928
|
+
staticTitleEl.classList.add("studio-watched-source-title");
|
|
2929
|
+
staticTitleEl.setAttribute("aria-label", "Source, read-only. Follows the file on disk.");
|
|
2930
|
+
staticTitleEl.title = "This source is read-only and follows the file on disk.";
|
|
2931
|
+
}
|
|
2932
|
+
titleGroupEl.appendChild(staticTitleEl);
|
|
2933
|
+
if (isWatchedFilePreview) {
|
|
2934
|
+
const readOnlyBadgeEl = makeStudioUiRefreshElement(
|
|
2935
|
+
"span",
|
|
2936
|
+
"studio-watched-source-read-only-badge",
|
|
2937
|
+
"Read-only · follows disk",
|
|
2938
|
+
);
|
|
2939
|
+
readOnlyBadgeEl.setAttribute("aria-hidden", "true");
|
|
2940
|
+
titleGroupEl.appendChild(readOnlyBadgeEl);
|
|
2941
|
+
}
|
|
2918
2942
|
} else if (editorViewSelect) {
|
|
2919
|
-
titleGroupEl.appendChild(editorViewSelect);
|
|
2943
|
+
titleGroupEl.appendChild(editorViewSelectWrap || editorViewSelect);
|
|
2920
2944
|
}
|
|
2921
2945
|
if (contextMenu) {
|
|
2922
2946
|
titleGroupEl.appendChild(makeStudioUiRefreshSeparator());
|
|
@@ -2942,7 +2966,7 @@
|
|
|
2942
2966
|
rightTitleGroupEl.appendChild(rightFocusBtn);
|
|
2943
2967
|
rightTitleGroupEl.appendChild(makeStudioUiRefreshSeparator());
|
|
2944
2968
|
}
|
|
2945
|
-
rightTitleGroupEl.appendChild(rightViewSelect);
|
|
2969
|
+
rightTitleGroupEl.appendChild(rightViewSelectWrap || rightViewSelect);
|
|
2946
2970
|
rightIdentityEl.appendChild(rightTitleGroupEl);
|
|
2947
2971
|
const rightToolsEl = makeStudioUiRefreshElement("div", "studio-refresh-pane-tools");
|
|
2948
2972
|
if (watchedOpenEditableBtn && isWatchedFilePreview) rightToolsEl.appendChild(watchedOpenEditableBtn);
|
|
@@ -3615,6 +3639,9 @@
|
|
|
3615
3639
|
if (typeof message.thinkingLevel === "string") {
|
|
3616
3640
|
piThinkingLevel = message.thinkingLevel.trim();
|
|
3617
3641
|
}
|
|
3642
|
+
if (Array.isArray(message.sideQuestionThinkingLevels)) {
|
|
3643
|
+
applySideQuestionThinkingLevels(message.sideQuestionThinkingLevels);
|
|
3644
|
+
}
|
|
3618
3645
|
renderFooterModelMenu();
|
|
3619
3646
|
}
|
|
3620
3647
|
|
|
@@ -3649,8 +3676,8 @@
|
|
|
3649
3676
|
});
|
|
3650
3677
|
footerModelMenuEl.innerHTML = ""
|
|
3651
3678
|
+ "<div class='footer-model-menu-heading'>Pi model & thinking</div>"
|
|
3652
|
-
+ "<label class='footer-model-menu-field'><span>Pi model</span><select id='footerPiModelSelect'>" + modelOptionsHtml.join("") + "</select></label>"
|
|
3653
|
-
+ "<label class='footer-model-menu-field'><span>Thinking</span><select id='footerPiThinkingSelect'>" + thinkingOptionsHtml.join("") + "</select></label>"
|
|
3679
|
+
+ "<label class='footer-model-menu-field'><span>Pi model</span><span class='studio-menu-select-wrap'><select id='footerPiModelSelect'>" + modelOptionsHtml.join("") + "</select></span></label>"
|
|
3680
|
+
+ "<label class='footer-model-menu-field'><span>Thinking</span><span class='studio-menu-select-wrap'><select id='footerPiThinkingSelect'>" + thinkingOptionsHtml.join("") + "</select></span></label>"
|
|
3654
3681
|
+ "<div class='footer-model-menu-note'>Affects future Pi turns. Studio Suggest has its own model setting.</div>";
|
|
3655
3682
|
}
|
|
3656
3683
|
|
|
@@ -3730,7 +3757,7 @@
|
|
|
3730
3757
|
footerThemeMenuEl.innerHTML = ""
|
|
3731
3758
|
+ "<div class='footer-model-menu-heading'>Pi theme</div>"
|
|
3732
3759
|
+ (optionsHtml.length
|
|
3733
|
-
? "<label class='footer-model-menu-field'><span>Active theme</span><select id='footerPiThemeSelect'>" + optionsHtml.join("") + "</select></label>"
|
|
3760
|
+
? "<label class='footer-model-menu-field'><span>Active theme</span><span class='studio-menu-select-wrap'><select id='footerPiThemeSelect'>" + optionsHtml.join("") + "</select></span></label>"
|
|
3734
3761
|
: "<div class='footer-model-menu-note'>No Pi themes are available yet.</div>")
|
|
3735
3762
|
+ "<div class='footer-model-menu-note'>Switches the active Pi theme and persists it to Pi settings.</div>";
|
|
3736
3763
|
}
|
|
@@ -4359,7 +4386,9 @@
|
|
|
4359
4386
|
].forEach(([btn, pane]) => {
|
|
4360
4387
|
if (!btn) return;
|
|
4361
4388
|
const isFocusedPane = paneFocusTarget === pane;
|
|
4362
|
-
const paneName = pane === "right"
|
|
4389
|
+
const paneName = pane === "right"
|
|
4390
|
+
? (isWatchedFilePreview ? "watched preview" : "response")
|
|
4391
|
+
: (isWatchedFilePreview ? "read-only source" : "editor");
|
|
4363
4392
|
btn.classList.toggle("is-active", isFocusedPane);
|
|
4364
4393
|
btn.setAttribute("aria-pressed", isFocusedPane ? "true" : "false");
|
|
4365
4394
|
btn.textContent = isFocusedPane ? "Exit focus" : "Focus pane";
|
|
@@ -11616,10 +11645,10 @@
|
|
|
11616
11645
|
return "<div class='repl-panel'>"
|
|
11617
11646
|
+ "<div class='repl-toolbar'>"
|
|
11618
11647
|
+ "<div class='repl-controls'>"
|
|
11619
|
-
+ "<label class='repl-control-label'>Runtime <select data-repl-runtime aria-label='REPL runtime'>" + runtimeOptions + "</select></label>"
|
|
11648
|
+
+ "<label class='repl-control-label'>Runtime <select class='studio-flat-select' data-repl-runtime aria-label='REPL runtime'>" + runtimeOptions + "</select></label>"
|
|
11620
11649
|
+ "<label class='repl-control-label repl-command-label'>Start cmd <input data-repl-command type='text' value='" + escapeHtml(replCommand) + "' placeholder='default' aria-label='REPL start command' title='Command used by Start for this runtime. Leave blank for the default; use this for envs, e.g. .venv/bin/python, uv run python, or conda run --no-capture-output -n env python.'></label>"
|
|
11621
11650
|
+ "<button class='repl-start-btn' type='button' data-repl-action='start'" + (replBusy || replTmuxAvailable === false ? " disabled" : "") + " title='Start or switch to a " + escapeHtml(runtimeLabel) + " session using the selected runtime and start command.'>Start</button>"
|
|
11622
|
-
+ "<label class='repl-control-label repl-session-label'>Session <select data-repl-session aria-label='REPL session'" + (visibleSessions.length ? "" : " disabled") + ">" + sessionOptions + "</select></label>"
|
|
11651
|
+
+ "<label class='repl-control-label repl-session-label'>Session <select class='studio-flat-select' data-repl-session aria-label='REPL session'" + (visibleSessions.length ? "" : " disabled") + ">" + sessionOptions + "</select></label>"
|
|
11623
11652
|
+ "<details class='repl-more-controls'>"
|
|
11624
11653
|
+ "<summary title='More REPL actions'>More</summary>"
|
|
11625
11654
|
+ "<div class='repl-more-menu'>"
|
|
@@ -11845,7 +11874,7 @@
|
|
|
11845
11874
|
const options = getFileBrowserSortOptions().map((option) => {
|
|
11846
11875
|
return "<option value='" + escapeHtml(option.value) + "'" + (option.value === currentSort ? " selected" : "") + ">" + escapeHtml(option.label) + "</option>";
|
|
11847
11876
|
}).join("");
|
|
11848
|
-
return "<select class='files-sort-select' data-files-sort aria-label='Files sort order' title='Sort file browser entries. Folders remain grouped first.'>" + options + "</select>";
|
|
11877
|
+
return "<select class='files-sort-select studio-flat-select' data-files-sort aria-label='Files sort order' title='Sort file browser entries. Folders remain grouped first.'>" + options + "</select>";
|
|
11849
11878
|
}
|
|
11850
11879
|
|
|
11851
11880
|
function buildFileBrowserEntryRowHtml(entry) {
|
|
@@ -11900,7 +11929,7 @@
|
|
|
11900
11929
|
const label = String((location && location.label) || basenameForStudioPath(path) || path);
|
|
11901
11930
|
return "<option value='" + escapeHtml(path) + "'" + (path === rootDir ? " selected" : "") + ">" + escapeHtml(label + " — " + path) + "</option>";
|
|
11902
11931
|
}).join("");
|
|
11903
|
-
return "<select class='files-location-select' data-files-location aria-label='Allowed Files location' title='Choose a folder allowed for this Studio session on the computer running Pi.'>" + options + "</select>";
|
|
11932
|
+
return "<select class='files-location-select studio-flat-select' data-files-location aria-label='Allowed Files location' title='Choose a folder allowed for this Studio session on the computer running Pi.'>" + options + "</select>";
|
|
11904
11933
|
}
|
|
11905
11934
|
|
|
11906
11935
|
function buildFileBrowserPanelHtml() {
|
|
@@ -12990,6 +13019,49 @@
|
|
|
12990
13019
|
return values.map(([value, label]) => "<option value='" + escapeHtml(value) + "'" + (value === current ? " selected" : "") + ">" + escapeHtml(label) + "</option>").join("");
|
|
12991
13020
|
}
|
|
12992
13021
|
|
|
13022
|
+
function clampSideQuestionThinkingLevel(value, levels) {
|
|
13023
|
+
const available = Array.isArray(levels) && levels.length ? levels : [...SIDE_QUESTION_THINKING_LEVELS];
|
|
13024
|
+
if (available.includes(value)) return value;
|
|
13025
|
+
const requestedIndex = SIDE_QUESTION_THINKING_LEVELS.indexOf(value);
|
|
13026
|
+
if (requestedIndex < 0) return available.includes("low") ? "low" : available[0];
|
|
13027
|
+
for (let index = requestedIndex; index < SIDE_QUESTION_THINKING_LEVELS.length; index += 1) {
|
|
13028
|
+
const candidate = SIDE_QUESTION_THINKING_LEVELS[index];
|
|
13029
|
+
if (available.includes(candidate)) return candidate;
|
|
13030
|
+
}
|
|
13031
|
+
for (let index = requestedIndex - 1; index >= 0; index -= 1) {
|
|
13032
|
+
const candidate = SIDE_QUESTION_THINKING_LEVELS[index];
|
|
13033
|
+
if (available.includes(candidate)) return candidate;
|
|
13034
|
+
}
|
|
13035
|
+
return "off";
|
|
13036
|
+
}
|
|
13037
|
+
|
|
13038
|
+
function applySideQuestionThinkingLevels(value) {
|
|
13039
|
+
const seen = new Set();
|
|
13040
|
+
const normalized = (Array.isArray(value) ? value : []).filter((level) => {
|
|
13041
|
+
if (typeof level !== "string" || !SIDE_QUESTION_THINKING_LEVELS.includes(level) || seen.has(level)) return false;
|
|
13042
|
+
seen.add(level);
|
|
13043
|
+
return true;
|
|
13044
|
+
});
|
|
13045
|
+
const next = normalized.length ? normalized : [...SIDE_QUESTION_THINKING_LEVELS];
|
|
13046
|
+
const levelsChanged = next.length !== sideQuestionThinkingLevels.length
|
|
13047
|
+
|| next.some((level, index) => level !== sideQuestionThinkingLevels[index]);
|
|
13048
|
+
sideQuestionThinkingLevels = next;
|
|
13049
|
+
const nextThinking = clampSideQuestionThinkingLevel(sideQuestionPreferredThinking, next);
|
|
13050
|
+
const thinkingChanged = nextThinking !== sideQuestionUi.thinking;
|
|
13051
|
+
if (thinkingChanged) sideQuestionUi.thinking = nextThinking;
|
|
13052
|
+
if ((levelsChanged || thinkingChanged) && rightView === "side-questions" && (!sideQuestionState || !sideQuestionState.threadId)) {
|
|
13053
|
+
renderSideQuestionView();
|
|
13054
|
+
}
|
|
13055
|
+
}
|
|
13056
|
+
|
|
13057
|
+
function getSideQuestionThinkingOptions() {
|
|
13058
|
+
return sideQuestionThinkingLevels.map((level) => {
|
|
13059
|
+
if (level === "xhigh") return [level, "X-high (slower)"];
|
|
13060
|
+
if (level === "max") return [level, "Max (slowest)"];
|
|
13061
|
+
return [level, level.charAt(0).toUpperCase() + level.slice(1)];
|
|
13062
|
+
});
|
|
13063
|
+
}
|
|
13064
|
+
|
|
12993
13065
|
function renderSideQuestionPiToolPicker() {
|
|
12994
13066
|
const selected = new Set(sideQuestionUi.toolIds);
|
|
12995
13067
|
const selectedCount = sideQuestionUi.toolIds.length;
|
|
@@ -13026,15 +13098,15 @@
|
|
|
13026
13098
|
return "<div class='side-question-empty'>"
|
|
13027
13099
|
+ "<div class='side-question-intro'><h2>Side question</h2><p>Ask something without adding it to the main Pi conversation. Choose the starting text and whether Studio may look through related files.</p></div>"
|
|
13028
13100
|
+ "<div class='side-question-context-grid'>"
|
|
13029
|
-
+ "<label>Starting text<select data-side-question-field='focusMode' aria-describedby='sideQuestionContextRule'>" + sideQuestionSelectOptions([
|
|
13101
|
+
+ "<label>Starting text<select class='studio-flat-select' data-side-question-field='focusMode' aria-describedby='sideQuestionContextRule'>" + sideQuestionSelectOptions([
|
|
13030
13102
|
["auto", "Automatic"], ["selection", "Editor selection only"], ["section", "Heading block or nearby text at cursor"], ["editor", "Whole editor document"], ["response", "Displayed response"], ["none", "No starting text"],
|
|
13031
13103
|
], sideQuestionUi.focusMode) + "</select></label>"
|
|
13032
|
-
+ "<label>Also use files from<select data-side-question-field='gatherScope'>" + sideQuestionSelectOptions([
|
|
13104
|
+
+ "<label>Also use files from<select class='studio-flat-select' data-side-question-field='gatherScope'>" + sideQuestionSelectOptions([
|
|
13033
13105
|
["none", "No other files"], ["folder", "Same folder as document"], ["repo", "Repository"], ["custom", "Choose a folder"],
|
|
13034
13106
|
], scope) + "</select></label>"
|
|
13035
|
-
+ "<label>Thinking<select data-side-question-field='thinking'>" + sideQuestionSelectOptions(
|
|
13036
|
-
|
|
13037
|
-
|
|
13107
|
+
+ "<label>Thinking<select class='studio-flat-select' data-side-question-field='thinking'>" + sideQuestionSelectOptions(
|
|
13108
|
+
getSideQuestionThinkingOptions(), sideQuestionUi.thinking
|
|
13109
|
+
) + "</select></label>"
|
|
13038
13110
|
+ "</div>"
|
|
13039
13111
|
+ "<details class='side-question-context-rule'><summary id='sideQuestionContextRule'>Automatic: selection → heading block at cursor → nearby text</summary><p>A heading block starts at the nearest Markdown/LaTeX heading above the cursor and ends before the next heading of the same or higher level. With no heading, Studio uses the surrounding text block or a nearby excerpt.</p></details>"
|
|
13040
13112
|
+ (scope === "none" ? "" : "<p class='side-question-context-access-note'>Related-file access is limited to folders allowed for this Studio session. Studio asks before starting if this folder is not already allowed.</p>")
|
|
@@ -13392,9 +13464,10 @@
|
|
|
13392
13464
|
if (sideQuestionUi.gatherScope !== "repo") sideQuestionUi.gitContext = false;
|
|
13393
13465
|
try { if (window.localStorage) window.localStorage.setItem(SIDE_QUESTION_GATHER_STORAGE_KEY, sideQuestionUi.gatherScope); } catch {}
|
|
13394
13466
|
}
|
|
13395
|
-
if (field === "thinking") {
|
|
13467
|
+
if (field === "thinking" && sideQuestionThinkingLevels.includes(target.value)) {
|
|
13468
|
+
sideQuestionPreferredThinking = target.value;
|
|
13396
13469
|
sideQuestionUi.thinking = target.value;
|
|
13397
|
-
try { if (window.localStorage) window.localStorage.setItem(SIDE_QUESTION_THINKING_STORAGE_KEY,
|
|
13470
|
+
try { if (window.localStorage) window.localStorage.setItem(SIDE_QUESTION_THINKING_STORAGE_KEY, sideQuestionPreferredThinking); } catch {}
|
|
13398
13471
|
}
|
|
13399
13472
|
if (field === "includeConversation") sideQuestionUi.includeConversation = target.checked;
|
|
13400
13473
|
if (field === "gitContext") sideQuestionUi.gitContext = target.checked && getSideQuestionGatherScope() === "repo";
|
|
@@ -16321,13 +16394,13 @@
|
|
|
16321
16394
|
return "<div class='studio-quiz-setup'>"
|
|
16322
16395
|
+ "<p class='studio-quiz-copy'>A short active-recall loop: answer one question, check it, ask about the card if useful, then move on.</p>"
|
|
16323
16396
|
+ "<div class='studio-quiz-fields'>"
|
|
16324
|
-
+ "<label>Scope<select data-quiz-field='scope'>"
|
|
16397
|
+
+ "<label>Scope<select class='studio-flat-select' data-quiz-field='scope'>"
|
|
16325
16398
|
+ QUIZ_SCOPES.map((candidate) => candidate === "selection" && !hasSelection ? "" : renderQuizOption(candidate, scope, getQuizScopeLabel(candidate))).join("")
|
|
16326
16399
|
+ "</select></label>"
|
|
16327
|
-
+ "<label>Angle<select data-quiz-field='angle'>"
|
|
16400
|
+
+ "<label>Angle<select class='studio-flat-select' data-quiz-field='angle'>"
|
|
16328
16401
|
+ QUIZ_ANGLES.map((candidate) => renderQuizOption(candidate, angle, getQuizAngleLabel(candidate))).join("")
|
|
16329
16402
|
+ "</select></label>"
|
|
16330
|
-
+ "<label>Thinking<select data-quiz-field='thinking'>"
|
|
16403
|
+
+ "<label>Thinking<select class='studio-flat-select' data-quiz-field='thinking'>"
|
|
16331
16404
|
+ QUIZ_THINKING_LEVELS.map((candidate) => renderQuizOption(candidate, thinking, getQuizThinkingLabel(candidate))).join("")
|
|
16332
16405
|
+ "</select></label>"
|
|
16333
16406
|
+ "<label>Questions<input data-quiz-field='count' type='number' min='1' max='8' value='" + String(count) + "'></label>"
|
|
@@ -22490,6 +22563,7 @@
|
|
|
22490
22563
|
}
|
|
22491
22564
|
|
|
22492
22565
|
if (message.type === "side_question_state") {
|
|
22566
|
+
if (Array.isArray(message.sideQuestionThinkingLevels)) applySideQuestionThinkingLevels(message.sideQuestionThinkingLevels);
|
|
22493
22567
|
sideQuestionWebSearchAvailable = message.webSearchAvailable === true;
|
|
22494
22568
|
if (Array.isArray(message.availablePiTools)) applySideQuestionToolCatalog(message.availablePiTools);
|
|
22495
22569
|
const previousStatus = sideQuestionState && sideQuestionState.status;
|
package/client/studio.css
CHANGED
|
@@ -222,9 +222,30 @@
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
button {
|
|
225
|
+
-webkit-appearance: none;
|
|
226
|
+
appearance: none;
|
|
225
227
|
cursor: pointer;
|
|
226
228
|
}
|
|
227
229
|
|
|
230
|
+
.studio-flat-select {
|
|
231
|
+
-webkit-appearance: none;
|
|
232
|
+
appearance: none;
|
|
233
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23717a84' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important;
|
|
234
|
+
background-position: right 8px center !important;
|
|
235
|
+
background-repeat: no-repeat !important;
|
|
236
|
+
background-size: 10px 6px !important;
|
|
237
|
+
padding-right: 26px !important;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@media (forced-colors: active) {
|
|
241
|
+
.studio-flat-select {
|
|
242
|
+
-webkit-appearance: auto;
|
|
243
|
+
appearance: auto;
|
|
244
|
+
background-image: none !important;
|
|
245
|
+
padding-right: 10px !important;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
228
249
|
button:not(:disabled):hover,
|
|
229
250
|
select:hover {
|
|
230
251
|
background: var(--panel-2);
|
|
@@ -442,20 +463,52 @@
|
|
|
442
463
|
color: var(--accent);
|
|
443
464
|
}
|
|
444
465
|
|
|
466
|
+
.studio-header-select-wrap {
|
|
467
|
+
position: relative;
|
|
468
|
+
display: inline-flex;
|
|
469
|
+
align-items: center;
|
|
470
|
+
min-width: 0;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.studio-header-select-wrap::after {
|
|
474
|
+
content: "⌄";
|
|
475
|
+
position: absolute;
|
|
476
|
+
top: 50%;
|
|
477
|
+
right: 4px;
|
|
478
|
+
color: var(--studio-info-text, var(--muted));
|
|
479
|
+
font-size: 14px;
|
|
480
|
+
font-weight: 600;
|
|
481
|
+
line-height: 1;
|
|
482
|
+
pointer-events: none;
|
|
483
|
+
transform: translateY(-58%);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.studio-header-select-wrap:hover::after,
|
|
487
|
+
.studio-header-select-wrap:focus-within::after {
|
|
488
|
+
color: var(--text);
|
|
489
|
+
}
|
|
490
|
+
|
|
445
491
|
.section-header select {
|
|
446
492
|
font-weight: 600;
|
|
447
493
|
font-size: 14px;
|
|
448
494
|
border: none;
|
|
449
495
|
border-radius: 4px;
|
|
450
496
|
background: inherit;
|
|
497
|
+
background-image: none;
|
|
451
498
|
color: inherit;
|
|
452
499
|
cursor: pointer;
|
|
453
|
-
padding: 2px 4px;
|
|
500
|
+
padding: 2px 18px 2px 4px;
|
|
454
501
|
margin: 0;
|
|
455
|
-
-webkit-appearance:
|
|
456
|
-
appearance:
|
|
502
|
+
-webkit-appearance: none;
|
|
503
|
+
appearance: none;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.section-header select:hover,
|
|
507
|
+
.section-header select:focus {
|
|
508
|
+
background-color: var(--studio-header-action-hover-bg, var(--panel-2));
|
|
457
509
|
}
|
|
458
510
|
|
|
511
|
+
body[data-studio-mode="editor-only"] #editorViewSelectWrap,
|
|
459
512
|
body[data-studio-mode="editor-only"] #editorViewSelect,
|
|
460
513
|
body[data-studio-mode="editor-only"] #sendRunBtn,
|
|
461
514
|
body[data-studio-mode="editor-only"] #queueSteerBtn,
|
|
@@ -464,7 +517,9 @@
|
|
|
464
517
|
body[data-studio-mode="editor-only"] #critiqueBtn,
|
|
465
518
|
body[data-studio-mode="editor-only"] #showMeBtn,
|
|
466
519
|
body[data-studio-mode="editor-only"] #showMeResponseBtn,
|
|
520
|
+
body[data-studio-mode="editor-only"] #followSelectWrap,
|
|
467
521
|
body[data-studio-mode="editor-only"] #followSelect,
|
|
522
|
+
body[data-studio-mode="editor-only"] #responseHighlightSelectWrap,
|
|
468
523
|
body[data-studio-mode="editor-only"] #responseHighlightSelect,
|
|
469
524
|
body[data-studio-mode="editor-only"] #pullLatestBtn,
|
|
470
525
|
body[data-studio-mode="editor-only"] #historyPrevBtn,
|
|
@@ -496,6 +551,10 @@
|
|
|
496
551
|
font-size: 14px;
|
|
497
552
|
}
|
|
498
553
|
|
|
554
|
+
body[data-watched-file-preview="1"] #leftSectionHeader .section-header-main::before {
|
|
555
|
+
content: "Source (read-only)";
|
|
556
|
+
}
|
|
557
|
+
|
|
499
558
|
body[data-watched-file-preview="1"] #rightSectionHeader .section-header-main::before {
|
|
500
559
|
content: "Watched preview";
|
|
501
560
|
}
|
|
@@ -3252,6 +3311,20 @@
|
|
|
3252
3311
|
flex: 0 0 auto;
|
|
3253
3312
|
}
|
|
3254
3313
|
|
|
3314
|
+
#responseActions .response-option-select-wrap {
|
|
3315
|
+
width: auto;
|
|
3316
|
+
flex: 0 0 auto;
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
#responseActions .response-option-select-wrap select {
|
|
3320
|
+
width: 100%;
|
|
3321
|
+
min-width: 0;
|
|
3322
|
+
padding-right: 26px;
|
|
3323
|
+
-webkit-appearance: none;
|
|
3324
|
+
appearance: none;
|
|
3325
|
+
background-image: none;
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3255
3328
|
#critiqueView.git-changes-host {
|
|
3256
3329
|
flex: 1 1 auto;
|
|
3257
3330
|
min-height: 0;
|
|
@@ -4911,15 +4984,43 @@
|
|
|
4911
4984
|
line-height: 1.35;
|
|
4912
4985
|
}
|
|
4913
4986
|
|
|
4987
|
+
.studio-menu-select-wrap {
|
|
4988
|
+
position: relative;
|
|
4989
|
+
display: flex;
|
|
4990
|
+
width: 100%;
|
|
4991
|
+
min-width: 0;
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4994
|
+
.studio-menu-select-wrap::after {
|
|
4995
|
+
content: "⌄";
|
|
4996
|
+
position: absolute;
|
|
4997
|
+
top: 50%;
|
|
4998
|
+
right: 8px;
|
|
4999
|
+
color: var(--studio-info-text, var(--muted));
|
|
5000
|
+
font-size: 14px;
|
|
5001
|
+
font-weight: 600;
|
|
5002
|
+
line-height: 1;
|
|
5003
|
+
pointer-events: none;
|
|
5004
|
+
transform: translateY(-58%);
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
.studio-menu-select-wrap:hover::after,
|
|
5008
|
+
.studio-menu-select-wrap:focus-within::after {
|
|
5009
|
+
color: var(--text);
|
|
5010
|
+
}
|
|
5011
|
+
|
|
4914
5012
|
.footer-model-menu-field select {
|
|
4915
5013
|
width: 100%;
|
|
4916
5014
|
min-width: 0;
|
|
4917
5015
|
background: var(--panel-2);
|
|
5016
|
+
background-image: none;
|
|
4918
5017
|
color: var(--text);
|
|
4919
5018
|
border: 1px solid var(--control-border);
|
|
4920
5019
|
border-radius: 8px;
|
|
4921
|
-
padding: 5px 7px;
|
|
5020
|
+
padding: 5px 24px 5px 7px;
|
|
4922
5021
|
font: inherit;
|
|
5022
|
+
-webkit-appearance: none;
|
|
5023
|
+
appearance: none;
|
|
4923
5024
|
}
|
|
4924
5025
|
|
|
4925
5026
|
.footer-model-menu-note {
|
|
@@ -5667,6 +5768,10 @@
|
|
|
5667
5768
|
font-size: 12px;
|
|
5668
5769
|
}
|
|
5669
5770
|
|
|
5771
|
+
body.studio-ui-refresh #responseActions .response-option-select-wrap select {
|
|
5772
|
+
padding-right: 24px;
|
|
5773
|
+
}
|
|
5774
|
+
|
|
5670
5775
|
|
|
5671
5776
|
body.studio-ui-refresh main {
|
|
5672
5777
|
gap: 0;
|
|
@@ -5832,8 +5937,11 @@
|
|
|
5832
5937
|
body.studio-ui-refresh #rightSectionHeader #rightViewSelect {
|
|
5833
5938
|
font-size: 14px;
|
|
5834
5939
|
font-weight: 700;
|
|
5835
|
-
padding: 3px 5px;
|
|
5940
|
+
padding: 3px 20px 3px 5px;
|
|
5836
5941
|
max-width: 230px;
|
|
5942
|
+
-webkit-appearance: none;
|
|
5943
|
+
appearance: none;
|
|
5944
|
+
background-image: none;
|
|
5837
5945
|
}
|
|
5838
5946
|
|
|
5839
5947
|
body.studio-ui-refresh .studio-refresh-static-title {
|
|
@@ -5845,6 +5953,34 @@
|
|
|
5845
5953
|
white-space: nowrap;
|
|
5846
5954
|
}
|
|
5847
5955
|
|
|
5956
|
+
body.studio-ui-refresh .studio-watched-source-title {
|
|
5957
|
+
padding-right: 1px;
|
|
5958
|
+
}
|
|
5959
|
+
|
|
5960
|
+
body.studio-ui-refresh .studio-watched-source-read-only-badge {
|
|
5961
|
+
display: inline-flex;
|
|
5962
|
+
align-items: center;
|
|
5963
|
+
gap: 5px;
|
|
5964
|
+
min-height: 20px;
|
|
5965
|
+
padding: 1px 4px;
|
|
5966
|
+
border: 0;
|
|
5967
|
+
background: transparent;
|
|
5968
|
+
color: var(--studio-info-text, var(--muted));
|
|
5969
|
+
font-size: 11px;
|
|
5970
|
+
font-weight: 500;
|
|
5971
|
+
line-height: 1.2;
|
|
5972
|
+
white-space: nowrap;
|
|
5973
|
+
}
|
|
5974
|
+
|
|
5975
|
+
body.studio-ui-refresh .studio-watched-source-read-only-badge::before {
|
|
5976
|
+
content: "";
|
|
5977
|
+
width: 5px;
|
|
5978
|
+
height: 5px;
|
|
5979
|
+
border-radius: 999px;
|
|
5980
|
+
background: currentColor;
|
|
5981
|
+
opacity: 0.72;
|
|
5982
|
+
}
|
|
5983
|
+
|
|
5848
5984
|
body.studio-ui-refresh #leftFocusBtn,
|
|
5849
5985
|
body.studio-ui-refresh #rightFocusBtn {
|
|
5850
5986
|
width: 29px;
|
package/index.ts
CHANGED
|
@@ -74,9 +74,11 @@ import {
|
|
|
74
74
|
import {
|
|
75
75
|
buildStudioSideQuestionFollowUpPrompt,
|
|
76
76
|
buildStudioSideQuestionPrompt,
|
|
77
|
+
getStudioSideQuestionThinkingLevels,
|
|
77
78
|
normalizeStudioSideQuestionFocusKind,
|
|
78
79
|
normalizeStudioSideQuestionGatherScope,
|
|
79
80
|
normalizeStudioSideQuestionThinking,
|
|
81
|
+
resolveStudioSideQuestionThinking,
|
|
80
82
|
STUDIO_SIDE_QUESTION_FOCUS_MAX_CHARS,
|
|
81
83
|
STUDIO_SIDE_QUESTION_QUESTION_MAX_CHARS,
|
|
82
84
|
} from "./shared/studio-side-question.js";
|
|
@@ -120,7 +122,7 @@ type StudioQuartoPreviewStatus = "idle" | "starting" | "running" | "stopping" |
|
|
|
120
122
|
type StudioShowMeSourceKind = "selection" | "response" | "editor" | "context";
|
|
121
123
|
type StudioSideQuestionFocusKind = "selection" | "section" | "editor" | "response" | "none";
|
|
122
124
|
type StudioSideQuestionGatherScope = "none" | "folder" | "repo" | "custom";
|
|
123
|
-
type StudioSideQuestionThinking =
|
|
125
|
+
type StudioSideQuestionThinking = ModelThinkingLevel;
|
|
124
126
|
type StudioSideQuestionStatus = "idle" | "running" | "error";
|
|
125
127
|
|
|
126
128
|
interface StudioQuartoPreviewContext {
|
|
@@ -8105,7 +8107,10 @@ async function respondLocalPreviewLinkJson(
|
|
|
8105
8107
|
...basePayload,
|
|
8106
8108
|
converted,
|
|
8107
8109
|
resourceDir: documentResourceDir,
|
|
8108
|
-
relativeUrl: buildStudioRelativeUrl(serverState.token, "editor-only", document, docId, {
|
|
8110
|
+
relativeUrl: buildStudioRelativeUrl(serverState.token, "editor-only", document, docId, {
|
|
8111
|
+
skipWorkspaceRestore: true,
|
|
8112
|
+
paneFocus: action === "watch-url" ? "right" : undefined,
|
|
8113
|
+
}),
|
|
8109
8114
|
});
|
|
8110
8115
|
}
|
|
8111
8116
|
|
|
@@ -11903,10 +11908,12 @@ ${cssVarsBlock}
|
|
|
11903
11908
|
<section id="leftPane">
|
|
11904
11909
|
<div id="leftSectionHeader" class="section-header">
|
|
11905
11910
|
<div class="section-header-main">
|
|
11906
|
-
<
|
|
11907
|
-
<
|
|
11908
|
-
|
|
11909
|
-
|
|
11911
|
+
<span id="editorViewSelectWrap" class="studio-header-select-wrap">
|
|
11912
|
+
<select id="editorViewSelect" aria-label="Editor view mode" title="Editor view mode. Shortcut: F7 when the editor pane is active; F6 switches panes.">
|
|
11913
|
+
<option value="markdown" selected>Editor (Raw)</option>
|
|
11914
|
+
<option value="preview">Editor (Preview)</option>
|
|
11915
|
+
</select>
|
|
11916
|
+
</span>
|
|
11910
11917
|
</div>
|
|
11911
11918
|
<div class="section-header-actions">
|
|
11912
11919
|
<button id="leftFocusBtn" class="pane-focus-btn" type="button" title="Show only the editor pane. Shortcut: F10 or Cmd/Ctrl+Esc.">Focus pane</button>
|
|
@@ -11934,7 +11941,7 @@ ${cssVarsBlock}
|
|
|
11934
11941
|
</div>
|
|
11935
11942
|
<div class="source-actions-row repl-action-line" hidden>
|
|
11936
11943
|
<button id="sendReplBtn" type="button" hidden title="Send the current selection, or the full editor text, to the active REPL session shown in the right pane.">Send to REPL</button>
|
|
11937
|
-
<select id="replSendModeSelect" hidden aria-label="REPL send mode" title="Choose how Send to REPL interprets the editor text.">
|
|
11944
|
+
<select id="replSendModeSelect" class="studio-flat-select" hidden aria-label="REPL send mode" title="Choose how Send to REPL interprets the editor text.">
|
|
11938
11945
|
<option value="raw" selected>Send mode: Raw</option>
|
|
11939
11946
|
<option value="literate">Send mode: Literate</option>
|
|
11940
11947
|
</select>
|
|
@@ -11943,11 +11950,11 @@ ${cssVarsBlock}
|
|
|
11943
11950
|
<button id="copyDraftBtn" type="button" title="Copy the current editor text to the clipboard.">Copy</button>
|
|
11944
11951
|
<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>
|
|
11945
11952
|
<button id="suggestCompletionOptionsBtn" type="button" hidden title="Suggestion context options">▾</button>
|
|
11946
|
-
<select id="completionContextSelect" hidden aria-label="Suggestion context mode" title="Choose how much context Suggest includes.">
|
|
11953
|
+
<select id="completionContextSelect" class="studio-flat-select" hidden aria-label="Suggestion context mode" title="Choose how much context Suggest includes.">
|
|
11947
11954
|
<option value="cursor" selected>Context: editor only</option>
|
|
11948
11955
|
<option value="session">Context: editor + latest response</option>
|
|
11949
11956
|
</select>
|
|
11950
|
-
<select id="completionModelSelect" hidden aria-label="Suggestion model" title="Choose the model used for Suggest. Suggestions use direct completion with thinking off and do not change the main Pi model.">
|
|
11957
|
+
<select id="completionModelSelect" class="studio-flat-select" hidden aria-label="Suggestion model" title="Choose the model used for Suggest. Suggestions use direct completion with thinking off and do not change the main Pi model.">
|
|
11951
11958
|
<option value="current" selected>Suggestion model: current Pi model</option>
|
|
11952
11959
|
</select>
|
|
11953
11960
|
<button id="openCompanionBtn" type="button" title="Open a blank editor-only Studio tab.">New editor tab</button>
|
|
@@ -11955,7 +11962,7 @@ ${cssVarsBlock}
|
|
|
11955
11962
|
</div>
|
|
11956
11963
|
<div class="source-actions-row">
|
|
11957
11964
|
<button id="insertHeaderBtn" type="button" title="Insert annotated-reply protocol header (source metadata, [an: ...] syntax hint, precedence note, and end marker).">Annotation header</button>
|
|
11958
|
-
<select id="annotationModeSelect" aria-label="Inline annotation visibility mode" title="On: keep and send [an: ...] markers. Hide: keep markers in the editor, hide them in preview, and strip before Run/Critique.">
|
|
11965
|
+
<select id="annotationModeSelect" class="studio-flat-select" aria-label="Inline annotation visibility mode" title="On: keep and send [an: ...] markers. Hide: keep markers in the editor, hide them in preview, and strip before Run/Critique.">
|
|
11959
11966
|
<option value="on" selected>Inline annotations: On</option>
|
|
11960
11967
|
<option value="off">Inline annotations: Hide</option>
|
|
11961
11968
|
</select>
|
|
@@ -11963,7 +11970,7 @@ ${cssVarsBlock}
|
|
|
11963
11970
|
<button id="saveAnnotatedBtn" type="button" title="Save full editor content (including [an: ...] markers) as a .annotated.md file.">Save .annotated.md</button>
|
|
11964
11971
|
</div>
|
|
11965
11972
|
<div class="source-actions-row">
|
|
11966
|
-
<select id="lensSelect" aria-label="Critique focus">
|
|
11973
|
+
<select id="lensSelect" class="studio-flat-select" aria-label="Critique focus">
|
|
11967
11974
|
<option value="auto" selected>Critique: Auto</option>
|
|
11968
11975
|
<option value="writing">Critique: Writing</option>
|
|
11969
11976
|
<option value="code">Critique: Code</option>
|
|
@@ -11973,7 +11980,7 @@ ${cssVarsBlock}
|
|
|
11973
11980
|
<button id="showMeResponseBtn" type="button" hidden title="Explain the response displayed in the right pane using the smallest useful visual or structural representation.">Explain displayed response</button>
|
|
11974
11981
|
<button id="askAsideBtn" type="button" title="Open a separate side-question thread without adding it to the main Pi conversation.">Side question</button>
|
|
11975
11982
|
<button id="quizBtn" type="button" title="Open an active quiz for the current editor selection or document.">Quiz me</button>
|
|
11976
|
-
<select id="highlightSelect" aria-label="Editor syntax highlighting">
|
|
11983
|
+
<select id="highlightSelect" class="studio-flat-select" aria-label="Editor syntax highlighting">
|
|
11977
11984
|
<option value="off">Syntax highlight: Off</option>
|
|
11978
11985
|
<option value="bash">Syntax highlight: Bash</option>
|
|
11979
11986
|
<option value="c">Syntax highlight: C</option>
|
|
@@ -12003,11 +12010,11 @@ ${cssVarsBlock}
|
|
|
12003
12010
|
<option value="xml">Syntax highlight: XML</option>
|
|
12004
12011
|
<option value="yaml">Syntax highlight: YAML</option>
|
|
12005
12012
|
</select>
|
|
12006
|
-
<select id="lineNumbersSelect" aria-label="Editor line numbers">
|
|
12013
|
+
<select id="lineNumbersSelect" class="studio-flat-select" aria-label="Editor line numbers">
|
|
12007
12014
|
<option value="off">Line numbers: Off</option>
|
|
12008
12015
|
<option value="on" selected>Line numbers: On</option>
|
|
12009
12016
|
</select>
|
|
12010
|
-
<select id="editorFontSizeSelect" aria-label="Editor text size" title="Adjust raw editor text size.">
|
|
12017
|
+
<select id="editorFontSizeSelect" class="studio-flat-select" aria-label="Editor text size" title="Adjust raw editor text size.">
|
|
12011
12018
|
<option value="10">Editor text: 10px</option>
|
|
12012
12019
|
<option value="11">Editor text: 11px</option>
|
|
12013
12020
|
<option value="12" selected>Editor text: 12px</option>
|
|
@@ -12105,17 +12112,19 @@ ${cssVarsBlock}
|
|
|
12105
12112
|
<section id="rightPane">
|
|
12106
12113
|
<div id="rightSectionHeader" class="section-header">
|
|
12107
12114
|
<div class="section-header-main">
|
|
12108
|
-
<
|
|
12109
|
-
<
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12115
|
+
<span id="rightViewSelectWrap" class="studio-header-select-wrap">
|
|
12116
|
+
<select id="rightViewSelect" aria-label="Response view mode" title="Right pane view mode. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+1–8 switches directly between all right-pane views. Cmd/Ctrl+Alt+P/E/W/F/Q keep mnemonic shortcuts for Preview, Editor Preview, Working, Files, and Side questions.">
|
|
12117
|
+
<option value="markdown">Response (Raw)</option>
|
|
12118
|
+
<option value="preview" selected>Response (Preview)</option>
|
|
12119
|
+
<option value="editor-preview">Editor (Preview)</option>
|
|
12120
|
+
<option value="editor-quarto-preview" hidden>Editor (Quarto Preview)</option>
|
|
12121
|
+
<option value="trace">Working</option>
|
|
12122
|
+
<option value="changes">Changes</option>
|
|
12123
|
+
<option value="files">Files</option>
|
|
12124
|
+
<option value="repl">REPL</option>
|
|
12125
|
+
<option value="side-questions">Side questions</option>
|
|
12126
|
+
</select>
|
|
12127
|
+
</span>
|
|
12119
12128
|
</div>
|
|
12120
12129
|
<div class="section-header-actions">
|
|
12121
12130
|
<button id="rightFocusBtn" class="pane-focus-btn" type="button" title="Show only the response pane. Shortcut: F10 or Cmd/Ctrl+Esc.">Focus pane</button>
|
|
@@ -12142,28 +12151,34 @@ ${cssVarsBlock}
|
|
|
12142
12151
|
<div class="response-wrap">
|
|
12143
12152
|
<div id="responseActions" class="response-actions">
|
|
12144
12153
|
<div class="response-actions-row response-options-row">
|
|
12145
|
-
<
|
|
12146
|
-
<
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
<
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12154
|
+
<span id="followSelectWrap" class="studio-menu-select-wrap response-option-select-wrap">
|
|
12155
|
+
<select id="followSelect" aria-label="Auto-update response">
|
|
12156
|
+
<option value="on" selected>Auto-update response: On</option>
|
|
12157
|
+
<option value="off">Auto-update response: Off</option>
|
|
12158
|
+
</select>
|
|
12159
|
+
</span>
|
|
12160
|
+
<span id="responseHighlightSelectWrap" class="studio-menu-select-wrap response-option-select-wrap">
|
|
12161
|
+
<select id="responseHighlightSelect" aria-label="Response markdown highlighting">
|
|
12162
|
+
<option value="off">Syntax highlight: Off</option>
|
|
12163
|
+
<option value="on" selected>Syntax highlight: On</option>
|
|
12164
|
+
</select>
|
|
12165
|
+
</span>
|
|
12166
|
+
<span id="responseFontSizeSelectWrap" class="studio-menu-select-wrap response-option-select-wrap">
|
|
12167
|
+
<select id="responseFontSizeSelect" aria-label="Response text size" title="Adjust right-pane response, preview, and working text size.">
|
|
12168
|
+
<option value="11">Response text: 11px</option>
|
|
12169
|
+
<option value="12">Response text: 12px</option>
|
|
12170
|
+
<option value="12.5">Response text: 12.5px</option>
|
|
12171
|
+
<option value="13">Response text: 13px</option>
|
|
12172
|
+
<option value="13.5" selected>Response text: 13.5px</option>
|
|
12173
|
+
<option value="14">Response text: 14px</option>
|
|
12174
|
+
<option value="14.5">Response text: 14.5px</option>
|
|
12175
|
+
<option value="15">Response text: 15px</option>
|
|
12176
|
+
<option value="15.5">Response text: 15.5px</option>
|
|
12177
|
+
<option value="16">Response text: 16px</option>
|
|
12178
|
+
<option value="18">Response text: 18px</option>
|
|
12179
|
+
<option value="20">Response text: 20px</option>
|
|
12180
|
+
</select>
|
|
12181
|
+
</span>
|
|
12167
12182
|
</div>
|
|
12168
12183
|
<div class="response-actions-row history-row">
|
|
12169
12184
|
<button id="pullLatestBtn" type="button" title="Fetch the latest assistant response when auto-update is off.">Fetch latest response</button>
|
|
@@ -13692,6 +13707,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
13692
13707
|
}
|
|
13693
13708
|
: null;
|
|
13694
13709
|
|
|
13710
|
+
const getCurrentStudioSideQuestionThinkingLevels = (): StudioSideQuestionThinking[] =>
|
|
13711
|
+
getStudioSideQuestionThinkingLevels(latestModelRequestCtx?.model ?? lastCommandCtx?.model) as StudioSideQuestionThinking[];
|
|
13712
|
+
|
|
13695
13713
|
const getStudioThemeOptions = () => {
|
|
13696
13714
|
try {
|
|
13697
13715
|
const themes = lastCommandCtx?.ui?.getAllThemes?.() ?? [];
|
|
@@ -13727,6 +13745,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
13727
13745
|
modelLabel: currentModelLabel,
|
|
13728
13746
|
currentModel: getCurrentStudioModelDescriptor(),
|
|
13729
13747
|
thinkingLevel: getThinkingLevelSafe() ?? "off",
|
|
13748
|
+
sideQuestionThinkingLevels: getCurrentStudioSideQuestionThinkingLevels(),
|
|
13730
13749
|
piModels: modelOptions,
|
|
13731
13750
|
suggestionModels: modelOptions,
|
|
13732
13751
|
currentTheme: getCurrentStudioThemeDescriptor(),
|
|
@@ -14033,6 +14052,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
14033
14052
|
const payload: Record<string, unknown> = {
|
|
14034
14053
|
type: "side_question_state",
|
|
14035
14054
|
state: getStudioSideQuestionPublicState(),
|
|
14055
|
+
sideQuestionThinkingLevels: getCurrentStudioSideQuestionThinkingLevels(),
|
|
14036
14056
|
webSearchAvailable: Boolean(String(process.env.BRAVE_API_KEY || "").trim()),
|
|
14037
14057
|
};
|
|
14038
14058
|
if (client) payload.availablePiTools = getPublicStudioSideQuestionToolCatalog();
|
|
@@ -14254,6 +14274,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
14254
14274
|
const model = latestModelRequestCtx?.model ?? ctx.model;
|
|
14255
14275
|
if (!model) throw new Error("No active Pi model is available for side questions.");
|
|
14256
14276
|
await resolveStudioModelRequestAuth({ model, modelRegistry: ctx.modelRegistry }, model);
|
|
14277
|
+
const thinking = resolveStudioSideQuestionThinking(model, context.thinking) as StudioSideQuestionThinking;
|
|
14257
14278
|
const gitSnapshot = context.gitContext
|
|
14258
14279
|
? await captureStudioSideQuestionGitSnapshot(contextRoot, { runGit: runStudioSideQuestionGitCommand }) as StudioSideQuestionGitSnapshot
|
|
14259
14280
|
: null;
|
|
@@ -14292,7 +14313,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
14292
14313
|
customTools: tools,
|
|
14293
14314
|
sessionManager: sideSessionManager,
|
|
14294
14315
|
model,
|
|
14295
|
-
thinking
|
|
14316
|
+
thinking,
|
|
14296
14317
|
inheritedPrompt: stripStudioDynamicSystemPromptFooter(ctx.getSystemPrompt()),
|
|
14297
14318
|
});
|
|
14298
14319
|
session = agentRuntime.session;
|
|
@@ -14301,7 +14322,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
14301
14322
|
cwd: workingDirectory,
|
|
14302
14323
|
sessionManager: sideSessionManager,
|
|
14303
14324
|
model,
|
|
14304
|
-
thinkingLevel:
|
|
14325
|
+
thinkingLevel: thinking,
|
|
14305
14326
|
tools: activeToolNames,
|
|
14306
14327
|
customTools: tools,
|
|
14307
14328
|
resourceLoader: createStudioSideQuestionResourceLoader(ctx),
|
|
@@ -14334,7 +14355,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
14334
14355
|
tools: selectedPiTools,
|
|
14335
14356
|
},
|
|
14336
14357
|
modelLabel: formatStudioModelOptionLabel(model),
|
|
14337
|
-
thinking
|
|
14358
|
+
thinking,
|
|
14338
14359
|
messages: [],
|
|
14339
14360
|
activity: [],
|
|
14340
14361
|
error: context.webSearch && !webAvailable ? "Web search was requested but BRAVE_API_KEY is not configured; this thread will use local context only." : "",
|
|
@@ -14638,6 +14659,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
14638
14659
|
modelLabel: currentModelLabel,
|
|
14639
14660
|
currentModel: getCurrentStudioModelDescriptor(),
|
|
14640
14661
|
thinkingLevel: getThinkingLevelSafe() ?? "off",
|
|
14662
|
+
sideQuestionThinkingLevels: getCurrentStudioSideQuestionThinkingLevels(),
|
|
14641
14663
|
piModels: getStudioModelOptions(),
|
|
14642
14664
|
suggestionModels: getStudioModelOptions(),
|
|
14643
14665
|
currentTheme: getCurrentStudioThemeDescriptor(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.55",
|
|
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",
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
import { clampThinkingLevel, getSupportedThinkingLevels } from "@earendil-works/pi-ai";
|
|
2
|
+
|
|
1
3
|
export const STUDIO_SIDE_QUESTION_FOCUS_MAX_CHARS = 60_000;
|
|
2
4
|
export const STUDIO_SIDE_QUESTION_QUESTION_MAX_CHARS = 12_000;
|
|
5
|
+
export const STUDIO_SIDE_QUESTION_THINKING_LEVELS = Object.freeze([
|
|
6
|
+
"off",
|
|
7
|
+
"minimal",
|
|
8
|
+
"low",
|
|
9
|
+
"medium",
|
|
10
|
+
"high",
|
|
11
|
+
"xhigh",
|
|
12
|
+
"max",
|
|
13
|
+
]);
|
|
3
14
|
|
|
4
15
|
export function normalizeStudioSideQuestionFocusKind(value) {
|
|
5
16
|
const normalized = String(value ?? "").trim().toLowerCase();
|
|
@@ -20,10 +31,28 @@ export function normalizeStudioSideQuestionGatherScope(value) {
|
|
|
20
31
|
|
|
21
32
|
export function normalizeStudioSideQuestionThinking(value) {
|
|
22
33
|
const normalized = String(value ?? "").trim().toLowerCase();
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
return STUDIO_SIDE_QUESTION_THINKING_LEVELS.includes(normalized) ? normalized : "low";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getStudioSideQuestionThinkingLevels(model) {
|
|
38
|
+
if (!model) return [...STUDIO_SIDE_QUESTION_THINKING_LEVELS];
|
|
39
|
+
try {
|
|
40
|
+
const supported = getSupportedThinkingLevels(model)
|
|
41
|
+
.filter((level) => STUDIO_SIDE_QUESTION_THINKING_LEVELS.includes(level));
|
|
42
|
+
return supported.length > 0 ? supported : ["off"];
|
|
43
|
+
} catch {
|
|
44
|
+
return [...STUDIO_SIDE_QUESTION_THINKING_LEVELS];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveStudioSideQuestionThinking(model, value) {
|
|
49
|
+
const requested = normalizeStudioSideQuestionThinking(value);
|
|
50
|
+
if (!model) return requested;
|
|
51
|
+
try {
|
|
52
|
+
return clampThinkingLevel(model, requested);
|
|
53
|
+
} catch {
|
|
54
|
+
return requested;
|
|
25
55
|
}
|
|
26
|
-
return "low";
|
|
27
56
|
}
|
|
28
57
|
|
|
29
58
|
function sanitizePromptContent(value) {
|