pi-studio 0.1.3 → 0.1.4
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 +2 -0
- package/README.md +2 -0
- package/index.ts +78 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,8 @@ All notable changes to `pi-studio` are documented here.
|
|
|
22
22
|
- Math delimiter normalization before preview rendering for `\(...\)` and `\[...\]` syntax (fence-aware).
|
|
23
23
|
- **Load file in editor** action in top controls (browser file picker into editor).
|
|
24
24
|
- README screenshot gallery for dark/light workspace and critique/annotation views.
|
|
25
|
+
- Response-side markdown highlighting toggle (`Highlight response: Off|On`) in `Response: Markdown` view, with local preference persistence.
|
|
26
|
+
- Obsidian wiki-image syntax normalization (`![[path]]`, `![[path|alt]]`) before pandoc preview rendering.
|
|
25
27
|
|
|
26
28
|
### Changed
|
|
27
29
|
- Removed Annotate/Critique tabs and related mode state.
|
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ Status: experimental alpha.
|
|
|
33
33
|
- critique: **Load critique (notes)** / **Load critique (full)**
|
|
34
34
|
- File actions: **Save As…**, **Save Over**, **Load file in editor**
|
|
35
35
|
- View toggles: `Editor: Markdown|Preview`, `Response: Markdown|Preview`
|
|
36
|
+
- Optional markdown highlighting toggles for editor and response markdown views
|
|
36
37
|
- Theme-aware browser UI based on current pi theme
|
|
37
38
|
|
|
38
39
|
## Commands
|
|
@@ -73,6 +74,7 @@ pi -e https://github.com/omaclaren/pi-studio
|
|
|
73
74
|
- One studio request at a time.
|
|
74
75
|
- Studio URLs include a token query parameter; avoid sharing full Studio URLs.
|
|
75
76
|
- Preview panes render markdown via `pandoc` (`gfm+tex_math_dollars` → HTML5 + MathML), sanitized in-browser with `dompurify`.
|
|
77
|
+
- Preview rendering normalizes Obsidian wiki-image syntax (`![[path]]`, `![[path|alt]]`) into standard markdown images.
|
|
76
78
|
- Install pandoc for full preview rendering (`brew install pandoc` on macOS).
|
|
77
79
|
- If `pandoc` is unavailable, preview falls back to plain markdown text with an inline warning.
|
|
78
80
|
- Some screenshots may show the Grammarly browser widget. Grammarly is a separate browser extension and is not part of pi-studio, but it works alongside it.
|
package/index.ts
CHANGED
|
@@ -491,10 +491,16 @@ function stripMathMlAnnotationTags(html: string): string {
|
|
|
491
491
|
.replace(/<annotation\b[\s\S]*?<\/annotation>/gi, "");
|
|
492
492
|
}
|
|
493
493
|
|
|
494
|
+
function normalizeObsidianImages(markdown: string): string {
|
|
495
|
+
return markdown
|
|
496
|
+
.replace(/!\[\[([^|\]]+)\|([^\]]+)\]\]/g, "")
|
|
497
|
+
.replace(/!\[\[([^\]]+)\]\]/g, "");
|
|
498
|
+
}
|
|
499
|
+
|
|
494
500
|
async function renderStudioMarkdownWithPandoc(markdown: string): Promise<string> {
|
|
495
501
|
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
|
496
502
|
const args = ["-f", "gfm+tex_math_dollars-raw_html", "-t", "html5", "--mathml", "--no-highlight"];
|
|
497
|
-
const normalizedMarkdown = normalizeMathDelimiters(markdown);
|
|
503
|
+
const normalizedMarkdown = normalizeObsidianImages(normalizeMathDelimiters(markdown));
|
|
498
504
|
|
|
499
505
|
return await new Promise<string>((resolve, reject) => {
|
|
500
506
|
const child = spawn(pandocCommand, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -1362,6 +1368,15 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1362
1368
|
line-height: 1.5;
|
|
1363
1369
|
}
|
|
1364
1370
|
|
|
1371
|
+
.response-markdown-highlight {
|
|
1372
|
+
margin: 0;
|
|
1373
|
+
white-space: pre-wrap;
|
|
1374
|
+
word-break: break-word;
|
|
1375
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
1376
|
+
font-size: 13px;
|
|
1377
|
+
line-height: 1.5;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1365
1380
|
.preview-loading {
|
|
1366
1381
|
color: var(--muted);
|
|
1367
1382
|
font-style: italic;
|
|
@@ -1514,6 +1529,10 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1514
1529
|
<option value="on" selected>Auto-update response: On</option>
|
|
1515
1530
|
<option value="off">Auto-update response: Off</option>
|
|
1516
1531
|
</select>
|
|
1532
|
+
<select id="responseHighlightSelect" aria-label="Response markdown highlighting">
|
|
1533
|
+
<option value="off" selected>Highlight response: Off</option>
|
|
1534
|
+
<option value="on">Highlight response: On</option>
|
|
1535
|
+
</select>
|
|
1517
1536
|
<button id="pullLatestBtn" type="button" title="Fetch the latest assistant response when auto-update is off.">Get latest response</button>
|
|
1518
1537
|
<button id="loadResponseBtn" type="button">Load response into editor</button>
|
|
1519
1538
|
<button id="loadCritiqueNotesBtn" type="button" hidden>Load critique (notes)</button>
|
|
@@ -1570,6 +1589,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1570
1589
|
const editorViewSelect = document.getElementById("editorViewSelect");
|
|
1571
1590
|
const rightViewSelect = document.getElementById("rightViewSelect");
|
|
1572
1591
|
const followSelect = document.getElementById("followSelect");
|
|
1592
|
+
const responseHighlightSelect = document.getElementById("responseHighlightSelect");
|
|
1573
1593
|
const pullLatestBtn = document.getElementById("pullLatestBtn");
|
|
1574
1594
|
const insertHeaderBtn = document.getElementById("insertHeaderBtn");
|
|
1575
1595
|
const critiqueBtn = document.getElementById("critiqueBtn");
|
|
@@ -1617,10 +1637,13 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1617
1637
|
let paneFocusTarget = "off";
|
|
1618
1638
|
const EDITOR_HIGHLIGHT_MAX_CHARS = 80_000;
|
|
1619
1639
|
const EDITOR_HIGHLIGHT_STORAGE_KEY = "piStudio.editorHighlightEnabled";
|
|
1640
|
+
const RESPONSE_HIGHLIGHT_MAX_CHARS = 120_000;
|
|
1641
|
+
const RESPONSE_HIGHLIGHT_STORAGE_KEY = "piStudio.responseHighlightEnabled";
|
|
1620
1642
|
let sourcePreviewRenderTimer = null;
|
|
1621
1643
|
let sourcePreviewRenderNonce = 0;
|
|
1622
1644
|
let responsePreviewRenderNonce = 0;
|
|
1623
1645
|
let editorHighlightEnabled = false;
|
|
1646
|
+
let responseHighlightEnabled = false;
|
|
1624
1647
|
let editorHighlightRenderRaf = null;
|
|
1625
1648
|
|
|
1626
1649
|
function getIdleStatus() {
|
|
@@ -1946,6 +1969,19 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1946
1969
|
return;
|
|
1947
1970
|
}
|
|
1948
1971
|
|
|
1972
|
+
if (responseHighlightEnabled) {
|
|
1973
|
+
if (markdown.length > RESPONSE_HIGHLIGHT_MAX_CHARS) {
|
|
1974
|
+
critiqueViewEl.innerHTML = buildPreviewErrorHtml(
|
|
1975
|
+
"Response is too large for markdown highlighting. Showing plain markdown.",
|
|
1976
|
+
markdown,
|
|
1977
|
+
);
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
critiqueViewEl.innerHTML = "<div class='response-markdown-highlight'>" + highlightMarkdown(markdown) + "</div>";
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1949
1985
|
critiqueViewEl.innerHTML = buildPlainMarkdownHtml(markdown);
|
|
1950
1986
|
}
|
|
1951
1987
|
|
|
@@ -2005,6 +2041,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2005
2041
|
editorViewSelect.disabled = uiBusy;
|
|
2006
2042
|
rightViewSelect.disabled = uiBusy;
|
|
2007
2043
|
followSelect.disabled = uiBusy;
|
|
2044
|
+
if (responseHighlightSelect) responseHighlightSelect.disabled = uiBusy || rightView !== "markdown";
|
|
2008
2045
|
insertHeaderBtn.disabled = uiBusy;
|
|
2009
2046
|
critiqueBtn.disabled = uiBusy;
|
|
2010
2047
|
lensSelect.disabled = uiBusy;
|
|
@@ -2052,6 +2089,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2052
2089
|
rightView = nextView === "preview" ? "preview" : "markdown";
|
|
2053
2090
|
rightViewSelect.value = rightView;
|
|
2054
2091
|
renderActiveResult();
|
|
2092
|
+
syncActionButtons();
|
|
2055
2093
|
}
|
|
2056
2094
|
|
|
2057
2095
|
function getToken() {
|
|
@@ -2225,24 +2263,40 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2225
2263
|
sourceHighlightEl.scrollLeft = sourceTextEl.scrollLeft;
|
|
2226
2264
|
}
|
|
2227
2265
|
|
|
2228
|
-
function
|
|
2266
|
+
function readStoredToggle(storageKey) {
|
|
2229
2267
|
if (!window.localStorage) return false;
|
|
2230
2268
|
try {
|
|
2231
|
-
return window.localStorage.getItem(
|
|
2269
|
+
return window.localStorage.getItem(storageKey) === "on";
|
|
2232
2270
|
} catch {
|
|
2233
2271
|
return false;
|
|
2234
2272
|
}
|
|
2235
2273
|
}
|
|
2236
2274
|
|
|
2237
|
-
function
|
|
2275
|
+
function persistStoredToggle(storageKey, enabled) {
|
|
2238
2276
|
if (!window.localStorage) return;
|
|
2239
2277
|
try {
|
|
2240
|
-
window.localStorage.setItem(
|
|
2278
|
+
window.localStorage.setItem(storageKey, enabled ? "on" : "off");
|
|
2241
2279
|
} catch {
|
|
2242
2280
|
// ignore storage failures
|
|
2243
2281
|
}
|
|
2244
2282
|
}
|
|
2245
2283
|
|
|
2284
|
+
function readStoredEditorHighlightEnabled() {
|
|
2285
|
+
return readStoredToggle(EDITOR_HIGHLIGHT_STORAGE_KEY);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
function readStoredResponseHighlightEnabled() {
|
|
2289
|
+
return readStoredToggle(RESPONSE_HIGHLIGHT_STORAGE_KEY);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
function persistEditorHighlightEnabled(enabled) {
|
|
2293
|
+
persistStoredToggle(EDITOR_HIGHLIGHT_STORAGE_KEY, enabled);
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
function persistResponseHighlightEnabled(enabled) {
|
|
2297
|
+
persistStoredToggle(RESPONSE_HIGHLIGHT_STORAGE_KEY, enabled);
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2246
2300
|
function updateEditorHighlightState() {
|
|
2247
2301
|
const enabled = editorHighlightEnabled && editorView === "markdown";
|
|
2248
2302
|
|
|
@@ -2283,6 +2337,15 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2283
2337
|
updateEditorHighlightState();
|
|
2284
2338
|
}
|
|
2285
2339
|
|
|
2340
|
+
function setResponseHighlightEnabled(enabled) {
|
|
2341
|
+
responseHighlightEnabled = Boolean(enabled);
|
|
2342
|
+
persistResponseHighlightEnabled(responseHighlightEnabled);
|
|
2343
|
+
if (responseHighlightSelect) {
|
|
2344
|
+
responseHighlightSelect.value = responseHighlightEnabled ? "on" : "off";
|
|
2345
|
+
}
|
|
2346
|
+
renderActiveResult();
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2286
2349
|
function extractSection(markdown, title) {
|
|
2287
2350
|
if (!markdown || !title) return "";
|
|
2288
2351
|
|
|
@@ -2726,6 +2789,12 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2726
2789
|
});
|
|
2727
2790
|
}
|
|
2728
2791
|
|
|
2792
|
+
if (responseHighlightSelect) {
|
|
2793
|
+
responseHighlightSelect.addEventListener("change", () => {
|
|
2794
|
+
setResponseHighlightEnabled(responseHighlightSelect.value === "on");
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2729
2798
|
pullLatestBtn.addEventListener("click", () => {
|
|
2730
2799
|
if (queuedLatestResponse) {
|
|
2731
2800
|
if (applyLatestPayload(queuedLatestResponse)) {
|
|
@@ -2977,6 +3046,10 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2977
3046
|
|| Boolean(highlightSelect && highlightSelect.value === "on");
|
|
2978
3047
|
setEditorHighlightEnabled(initialHighlightEnabled);
|
|
2979
3048
|
|
|
3049
|
+
const initialResponseHighlightEnabled = readStoredResponseHighlightEnabled()
|
|
3050
|
+
|| Boolean(responseHighlightSelect && responseHighlightSelect.value === "on");
|
|
3051
|
+
setResponseHighlightEnabled(initialResponseHighlightEnabled);
|
|
3052
|
+
|
|
2980
3053
|
setEditorView(editorView);
|
|
2981
3054
|
setRightView(rightView);
|
|
2982
3055
|
renderSourcePreview();
|