pi-studio 0.5.35 → 0.5.36
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 +10 -0
- package/client/studio-annotation-helpers.js +543 -0
- package/client/studio-client.js +61 -132
- package/client/studio.css +8 -0
- package/index.ts +173 -93
- package/package.json +5 -1
- package/shared/studio-annotation-scanner.js +370 -0
- package/shared/studio-pdf-escape.js +34 -0
package/client/studio-client.js
CHANGED
|
@@ -241,8 +241,11 @@
|
|
|
241
241
|
let responseHighlightEnabled = false;
|
|
242
242
|
let editorHighlightRenderRaf = null;
|
|
243
243
|
let annotationsEnabled = true;
|
|
244
|
-
const ANNOTATION_MARKER_REGEX = /\[an:\s*([^\]]+?)\]/gi;
|
|
245
244
|
const PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX = "PISTUDIOANNOT";
|
|
245
|
+
const annotationHelpers = globalThis.PiStudioAnnotationHelpers;
|
|
246
|
+
if (!annotationHelpers || typeof annotationHelpers.collectInlineAnnotationMarkers !== "function") {
|
|
247
|
+
throw new Error("Studio annotation helpers failed to load.");
|
|
248
|
+
}
|
|
246
249
|
const EMPTY_OVERLAY_LINE = "\u200b";
|
|
247
250
|
const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
248
251
|
const MATHJAX_CDN_URL = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js";
|
|
@@ -1163,15 +1166,11 @@
|
|
|
1163
1166
|
}
|
|
1164
1167
|
|
|
1165
1168
|
function hasAnnotationMarkers(text) {
|
|
1166
|
-
|
|
1167
|
-
ANNOTATION_MARKER_REGEX.lastIndex = 0;
|
|
1168
|
-
const hasMarker = ANNOTATION_MARKER_REGEX.test(source);
|
|
1169
|
-
ANNOTATION_MARKER_REGEX.lastIndex = 0;
|
|
1170
|
-
return hasMarker;
|
|
1169
|
+
return annotationHelpers.hasAnnotationMarkers(text);
|
|
1171
1170
|
}
|
|
1172
1171
|
|
|
1173
1172
|
function stripAnnotationMarkers(text) {
|
|
1174
|
-
return
|
|
1173
|
+
return annotationHelpers.stripAnnotationMarkers(text);
|
|
1175
1174
|
}
|
|
1176
1175
|
|
|
1177
1176
|
function prepareEditorTextForSend(text) {
|
|
@@ -1184,78 +1183,8 @@
|
|
|
1184
1183
|
return annotationsEnabled ? raw : stripAnnotationMarkers(raw);
|
|
1185
1184
|
}
|
|
1186
1185
|
|
|
1187
|
-
function normalizePreviewAnnotationLabel(text) {
|
|
1188
|
-
return String(text || "")
|
|
1189
|
-
.replace(/\r\n/g, "\n")
|
|
1190
|
-
.replace(/\s*\n\s*/g, " ")
|
|
1191
|
-
.replace(/\s{2,}/g, " ")
|
|
1192
|
-
.trim();
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
1186
|
function prepareMarkdownForPandocPreview(markdown) {
|
|
1196
|
-
|
|
1197
|
-
const placeholders = [];
|
|
1198
|
-
if (!source) {
|
|
1199
|
-
return { markdown: source, placeholders: placeholders };
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
const lines = source.split("\n");
|
|
1203
|
-
const out = [];
|
|
1204
|
-
let plainBuffer = [];
|
|
1205
|
-
let inFence = false;
|
|
1206
|
-
let fenceChar = null;
|
|
1207
|
-
let fenceLength = 0;
|
|
1208
|
-
|
|
1209
|
-
function flushPlain() {
|
|
1210
|
-
if (plainBuffer.length === 0) return;
|
|
1211
|
-
const segment = plainBuffer.join("\n").replace(/\[an:\s*([^\]]+?)\]/gi, function(_match, markerText) {
|
|
1212
|
-
const label = normalizePreviewAnnotationLabel(markerText);
|
|
1213
|
-
if (!label) return "";
|
|
1214
|
-
const token = PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX + placeholders.length + "TOKEN";
|
|
1215
|
-
placeholders.push({ token: token, text: label, title: "[an: " + label + "]" });
|
|
1216
|
-
return token;
|
|
1217
|
-
});
|
|
1218
|
-
out.push(segment);
|
|
1219
|
-
plainBuffer = [];
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
for (const line of lines) {
|
|
1223
|
-
const trimmed = line.trimStart();
|
|
1224
|
-
const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
|
|
1225
|
-
|
|
1226
|
-
if (fenceMatch) {
|
|
1227
|
-
const marker = fenceMatch[1] || "";
|
|
1228
|
-
const markerChar = marker.charAt(0);
|
|
1229
|
-
const markerLength = marker.length;
|
|
1230
|
-
|
|
1231
|
-
if (!inFence) {
|
|
1232
|
-
flushPlain();
|
|
1233
|
-
inFence = true;
|
|
1234
|
-
fenceChar = markerChar;
|
|
1235
|
-
fenceLength = markerLength;
|
|
1236
|
-
out.push(line);
|
|
1237
|
-
continue;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
if (fenceChar === markerChar && markerLength >= fenceLength) {
|
|
1241
|
-
inFence = false;
|
|
1242
|
-
fenceChar = null;
|
|
1243
|
-
fenceLength = 0;
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
out.push(line);
|
|
1247
|
-
continue;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
if (inFence) {
|
|
1251
|
-
out.push(line);
|
|
1252
|
-
} else {
|
|
1253
|
-
plainBuffer.push(line);
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
flushPlain();
|
|
1258
|
-
return { markdown: out.join("\n"), placeholders: placeholders };
|
|
1187
|
+
return annotationHelpers.prepareMarkdownForPandocPreview(markdown, PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX);
|
|
1259
1188
|
}
|
|
1260
1189
|
|
|
1261
1190
|
function wrapAsFencedCodeBlock(text, language) {
|
|
@@ -1774,8 +1703,9 @@
|
|
|
1774
1703
|
if (entry) {
|
|
1775
1704
|
const markerEl = document.createElement("span");
|
|
1776
1705
|
markerEl.className = "annotation-preview-marker";
|
|
1777
|
-
|
|
1778
|
-
markerEl.title = typeof entry.title === "string" ? entry.title :
|
|
1706
|
+
const markerText = typeof entry.text === "string" ? entry.text : token;
|
|
1707
|
+
markerEl.title = typeof entry.title === "string" ? entry.title : markerText;
|
|
1708
|
+
setAnnotationPreviewMarkerContent(markerEl, markerText);
|
|
1779
1709
|
fragment.appendChild(markerEl);
|
|
1780
1710
|
} else {
|
|
1781
1711
|
fragment.appendChild(document.createTextNode(token));
|
|
@@ -1819,33 +1749,28 @@
|
|
|
1819
1749
|
for (const textNode of textNodes) {
|
|
1820
1750
|
const text = typeof textNode.nodeValue === "string" ? textNode.nodeValue : "";
|
|
1821
1751
|
if (!text) continue;
|
|
1822
|
-
|
|
1823
|
-
if (
|
|
1824
|
-
ANNOTATION_MARKER_REGEX.lastIndex = 0;
|
|
1752
|
+
const markers = annotationHelpers.collectInlineAnnotationMarkers(text);
|
|
1753
|
+
if (markers.length === 0) continue;
|
|
1825
1754
|
|
|
1826
1755
|
const fragment = document.createDocumentFragment();
|
|
1827
1756
|
let lastIndex = 0;
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
if (start > lastIndex) {
|
|
1833
|
-
fragment.appendChild(document.createTextNode(text.slice(lastIndex, start)));
|
|
1757
|
+
markers.forEach(function(marker) {
|
|
1758
|
+
const token = marker.raw || "";
|
|
1759
|
+
if (marker.start > lastIndex) {
|
|
1760
|
+
fragment.appendChild(document.createTextNode(text.slice(lastIndex, marker.start)));
|
|
1834
1761
|
}
|
|
1835
1762
|
|
|
1836
1763
|
if (mode === "highlight") {
|
|
1837
1764
|
const markerEl = document.createElement("span");
|
|
1838
1765
|
markerEl.className = "annotation-preview-marker";
|
|
1839
|
-
|
|
1766
|
+
const markerText = annotationHelpers.normalizePreviewAnnotationLabel(marker.body) || token;
|
|
1840
1767
|
markerEl.title = token;
|
|
1768
|
+
setAnnotationPreviewMarkerContent(markerEl, markerText);
|
|
1841
1769
|
fragment.appendChild(markerEl);
|
|
1842
1770
|
}
|
|
1843
1771
|
|
|
1844
|
-
lastIndex =
|
|
1845
|
-
|
|
1846
|
-
ANNOTATION_MARKER_REGEX.lastIndex += 1;
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1772
|
+
lastIndex = marker.end;
|
|
1773
|
+
});
|
|
1849
1774
|
|
|
1850
1775
|
if (lastIndex < text.length) {
|
|
1851
1776
|
fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
|
|
@@ -2732,50 +2657,44 @@
|
|
|
2732
2657
|
return "<span class='" + className + "'>" + escapeHtml(String(text || "")) + "</span>";
|
|
2733
2658
|
}
|
|
2734
2659
|
|
|
2735
|
-
function
|
|
2660
|
+
function buildAnnotationPreviewMarkerHtml(text, title) {
|
|
2736
2661
|
const titleAttr = title ? " title='" + escapeHtml(String(title)) + "'" : "";
|
|
2737
|
-
|
|
2662
|
+
const rendered = typeof annotationHelpers.renderPreviewAnnotationHtml === "function"
|
|
2663
|
+
? annotationHelpers.renderPreviewAnnotationHtml(text)
|
|
2664
|
+
: escapeHtml(String(text || ""));
|
|
2665
|
+
return "<span class='annotation-preview-marker'" + titleAttr + ">" + rendered + "</span>";
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
function setAnnotationPreviewMarkerContent(markerEl, text) {
|
|
2669
|
+
if (!markerEl) return;
|
|
2670
|
+
const rendered = typeof annotationHelpers.renderPreviewAnnotationHtml === "function"
|
|
2671
|
+
? annotationHelpers.renderPreviewAnnotationHtml(text)
|
|
2672
|
+
: escapeHtml(String(text || ""));
|
|
2673
|
+
markerEl.innerHTML = rendered;
|
|
2738
2674
|
}
|
|
2739
2675
|
|
|
2740
2676
|
function highlightInlineAnnotations(text, mode) {
|
|
2741
2677
|
const source = String(text || "");
|
|
2742
2678
|
const renderMode = mode === "preview" ? "preview" : "overlay";
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
}
|
|
2756
|
-
|
|
2757
|
-
if (renderMode === "preview") {
|
|
2758
|
-
out += wrapHighlightWithTitle("annotation-preview-marker", markerText || token, token);
|
|
2759
|
-
} else {
|
|
2760
|
-
out += wrapHighlight(annotationsEnabled ? "hl-annotation" : "hl-annotation-muted", token);
|
|
2761
|
-
}
|
|
2762
|
-
lastIndex = start + token.length;
|
|
2763
|
-
if (token.length === 0) {
|
|
2764
|
-
ANNOTATION_MARKER_REGEX.lastIndex += 1;
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
ANNOTATION_MARKER_REGEX.lastIndex = 0;
|
|
2769
|
-
if (lastIndex < source.length) {
|
|
2770
|
-
out += escapeHtml(source.slice(lastIndex));
|
|
2771
|
-
}
|
|
2772
|
-
|
|
2773
|
-
return out;
|
|
2679
|
+
return annotationHelpers.replaceInlineAnnotationMarkers(
|
|
2680
|
+
source,
|
|
2681
|
+
function(marker) {
|
|
2682
|
+
const token = marker.raw || "";
|
|
2683
|
+
const markerText = annotationHelpers.normalizePreviewAnnotationLabel(marker.body) || token;
|
|
2684
|
+
if (renderMode === "preview") {
|
|
2685
|
+
return buildAnnotationPreviewMarkerHtml(markerText, token);
|
|
2686
|
+
}
|
|
2687
|
+
return wrapHighlight(annotationsEnabled ? "hl-annotation" : "hl-annotation-muted", token);
|
|
2688
|
+
},
|
|
2689
|
+
function(segment) {
|
|
2690
|
+
return escapeHtml(segment);
|
|
2691
|
+
},
|
|
2692
|
+
);
|
|
2774
2693
|
}
|
|
2775
2694
|
|
|
2776
|
-
function
|
|
2695
|
+
function highlightInlineMarkdownWithoutAnnotations(text) {
|
|
2777
2696
|
const source = String(text || "");
|
|
2778
|
-
const pattern = /(\x60[^\x60]*\x60)|(\[[^\]]+\]\([^)]+\))
|
|
2697
|
+
const pattern = /(\x60[^\x60]*\x60)|(\[[^\]]+\]\([^)]+\))/g;
|
|
2779
2698
|
let lastIndex = 0;
|
|
2780
2699
|
let out = "";
|
|
2781
2700
|
|
|
@@ -2798,8 +2717,6 @@
|
|
|
2798
2717
|
} else {
|
|
2799
2718
|
out += escapeHtml(token);
|
|
2800
2719
|
}
|
|
2801
|
-
} else if (match[3]) {
|
|
2802
|
-
out += highlightInlineAnnotations(token);
|
|
2803
2720
|
} else {
|
|
2804
2721
|
out += escapeHtml(token);
|
|
2805
2722
|
}
|
|
@@ -2814,6 +2731,18 @@
|
|
|
2814
2731
|
return out;
|
|
2815
2732
|
}
|
|
2816
2733
|
|
|
2734
|
+
function highlightInlineMarkdown(text) {
|
|
2735
|
+
return annotationHelpers.replaceInlineAnnotationMarkers(
|
|
2736
|
+
String(text || ""),
|
|
2737
|
+
function(marker) {
|
|
2738
|
+
return highlightInlineAnnotations(marker.raw || "");
|
|
2739
|
+
},
|
|
2740
|
+
function(segment) {
|
|
2741
|
+
return highlightInlineMarkdownWithoutAnnotations(segment);
|
|
2742
|
+
},
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2817
2746
|
function normalizeFenceLanguage(info) {
|
|
2818
2747
|
const raw = String(info || "").trim();
|
|
2819
2748
|
if (!raw) return "";
|
package/client/studio.css
CHANGED
|
@@ -523,6 +523,14 @@
|
|
|
523
523
|
vertical-align: baseline;
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
+
.annotation-preview-marker code {
|
|
527
|
+
font-family: var(--mono-font, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
528
|
+
font-size: 0.95em;
|
|
529
|
+
background: rgba(0, 0, 0, 0.08);
|
|
530
|
+
border-radius: 3px;
|
|
531
|
+
padding: 0 0.2em;
|
|
532
|
+
}
|
|
533
|
+
|
|
526
534
|
#sourcePreview {
|
|
527
535
|
flex: 1 1 auto;
|
|
528
536
|
min-height: 0;
|
package/index.ts
CHANGED
|
@@ -8,6 +8,16 @@ import { homedir, tmpdir } from "node:os";
|
|
|
8
8
|
import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
9
9
|
import { URL, pathToFileURL } from "node:url";
|
|
10
10
|
import { WebSocketServer, WebSocket, type RawData } from "ws";
|
|
11
|
+
import {
|
|
12
|
+
collectStudioInlineAnnotationMarkers,
|
|
13
|
+
hasStudioMarkdownAnnotationMarkers,
|
|
14
|
+
isStudioAnnotationWordChar,
|
|
15
|
+
normalizeStudioAnnotationText,
|
|
16
|
+
readStudioAnnotationProtectedTokenAt,
|
|
17
|
+
replaceStudioInlineAnnotationMarkers,
|
|
18
|
+
transformStudioMarkdownOutsideFences,
|
|
19
|
+
} from "./shared/studio-annotation-scanner.js";
|
|
20
|
+
import { escapeStudioPdfLatexTextFragment } from "./shared/studio-pdf-escape.js";
|
|
11
21
|
|
|
12
22
|
type Lens = "writing" | "code";
|
|
13
23
|
type RequestedLens = Lens | "auto";
|
|
@@ -18,6 +28,7 @@ type StudioPromptMode = "response" | "run" | "effective";
|
|
|
18
28
|
type StudioPromptTriggerKind = "run" | "steer";
|
|
19
29
|
|
|
20
30
|
const STUDIO_CSS_URL = new URL("./client/studio.css", import.meta.url);
|
|
31
|
+
const STUDIO_ANNOTATION_HELPERS_URL = new URL("./client/studio-annotation-helpers.js", import.meta.url);
|
|
21
32
|
const STUDIO_CLIENT_URL = new URL("./client/studio-client.js", import.meta.url);
|
|
22
33
|
|
|
23
34
|
interface StudioServerState {
|
|
@@ -3023,14 +3034,6 @@ function inferStudioPdfLanguage(markdown: string, editorLanguage?: string): stri
|
|
|
3023
3034
|
return undefined;
|
|
3024
3035
|
}
|
|
3025
3036
|
|
|
3026
|
-
function escapeStudioPdfLatexTextFragment(text: string): string {
|
|
3027
|
-
return String(text ?? "")
|
|
3028
|
-
.replace(/\\/g, "\\textbackslash{}")
|
|
3029
|
-
.replace(/([{}%#$&_])/g, "\\$1")
|
|
3030
|
-
.replace(/~/g, "\\textasciitilde{}")
|
|
3031
|
-
.replace(/\^/g, "\\textasciicircum{}");
|
|
3032
|
-
}
|
|
3033
|
-
|
|
3034
3037
|
function escapeStudioPdfLatexText(text: string): string {
|
|
3035
3038
|
const normalized = String(text ?? "")
|
|
3036
3039
|
.replace(/\r\n/g, "\n")
|
|
@@ -3085,71 +3088,158 @@ function escapeStudioPdfLatexText(text: string): string {
|
|
|
3085
3088
|
return out.trim();
|
|
3086
3089
|
}
|
|
3087
3090
|
|
|
3088
|
-
function
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3091
|
+
function renderStudioAnnotationCodeSpanPdfLatex(rawToken: string): string {
|
|
3092
|
+
const raw = String(rawToken ?? "");
|
|
3093
|
+
if (!raw || raw[0] !== "`") return escapeStudioPdfLatexTextFragment(raw);
|
|
3094
|
+
|
|
3095
|
+
let fenceLength = 1;
|
|
3096
|
+
while (raw[fenceLength] === "`") fenceLength += 1;
|
|
3097
|
+
const fence = "`".repeat(fenceLength);
|
|
3098
|
+
if (raw.length < fenceLength * 2 || raw.slice(raw.length - fenceLength) !== fence) {
|
|
3099
|
+
return escapeStudioPdfLatexTextFragment(raw);
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
return `\\texttt{${escapeStudioPdfLatexTextFragment(raw.slice(fenceLength, raw.length - fenceLength))}}`;
|
|
3100
3103
|
}
|
|
3101
3104
|
|
|
3102
|
-
function
|
|
3103
|
-
|
|
3104
|
-
const
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3105
|
+
function canOpenStudioAnnotationEmphasisDelimiter(source: string, startIndex: number, delimiter: string): boolean {
|
|
3106
|
+
if (source.slice(startIndex, startIndex + delimiter.length) !== delimiter) return false;
|
|
3107
|
+
const prev = startIndex > 0 ? source[startIndex - 1] ?? "" : "";
|
|
3108
|
+
const next = source[startIndex + delimiter.length] ?? "";
|
|
3109
|
+
if (!next || /\s/.test(next)) return false;
|
|
3110
|
+
return !isStudioAnnotationWordChar(prev);
|
|
3111
|
+
}
|
|
3109
3112
|
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3113
|
+
function canCloseStudioAnnotationEmphasisDelimiter(source: string, startIndex: number, delimiter: string): boolean {
|
|
3114
|
+
if (source.slice(startIndex, startIndex + delimiter.length) !== delimiter) return false;
|
|
3115
|
+
const prev = startIndex > 0 ? source[startIndex - 1] ?? "" : "";
|
|
3116
|
+
const next = source[startIndex + delimiter.length] ?? "";
|
|
3117
|
+
if (!prev || /\s/.test(prev)) return false;
|
|
3118
|
+
return !isStudioAnnotationWordChar(next);
|
|
3119
|
+
}
|
|
3115
3120
|
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3121
|
+
function renderStudioAnnotationPdfLatexContent(text: string): string {
|
|
3122
|
+
const source = String(text ?? "");
|
|
3123
|
+
let out = "";
|
|
3124
|
+
let plainStart = 0;
|
|
3125
|
+
let index = 0;
|
|
3119
3126
|
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3127
|
+
while (index < source.length) {
|
|
3128
|
+
const token = readStudioAnnotationProtectedTokenAt(source, index);
|
|
3129
|
+
if (!token) {
|
|
3130
|
+
index += 1;
|
|
3131
|
+
continue;
|
|
3132
|
+
}
|
|
3124
3133
|
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
fenceChar = markerChar;
|
|
3129
|
-
fenceLength = markerLength;
|
|
3130
|
-
out.push(line);
|
|
3131
|
-
continue;
|
|
3132
|
-
}
|
|
3134
|
+
if (index > plainStart) {
|
|
3135
|
+
out += renderStudioAnnotationPlainTextPdfLatex(source.slice(plainStart, index));
|
|
3136
|
+
}
|
|
3133
3137
|
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3138
|
+
if (token.type === "code") {
|
|
3139
|
+
out += renderStudioAnnotationCodeSpanPdfLatex(token.raw);
|
|
3140
|
+
} else if (token.type === "math") {
|
|
3141
|
+
out += escapeStudioPdfLatexText(token.raw);
|
|
3142
|
+
} else {
|
|
3143
|
+
out += escapeStudioPdfLatexTextFragment(token.raw);
|
|
3144
|
+
}
|
|
3139
3145
|
|
|
3140
|
-
|
|
3146
|
+
index = token.end;
|
|
3147
|
+
plainStart = index;
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
if (plainStart < source.length) {
|
|
3151
|
+
out += renderStudioAnnotationPlainTextPdfLatex(source.slice(plainStart));
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
return out;
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
function readStudioAnnotationPdfEmphasisSpanAt(source: string, startIndex: number, delimiter: string, commandName: string): { end: number; latex: string } | null {
|
|
3158
|
+
if (!canOpenStudioAnnotationEmphasisDelimiter(source, startIndex, delimiter)) return null;
|
|
3159
|
+
|
|
3160
|
+
let index = startIndex + delimiter.length;
|
|
3161
|
+
while (index < source.length) {
|
|
3162
|
+
if (source[index] === "\\") {
|
|
3163
|
+
index = Math.min(source.length, index + 2);
|
|
3141
3164
|
continue;
|
|
3142
3165
|
}
|
|
3143
3166
|
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3167
|
+
const protectedToken = readStudioAnnotationProtectedTokenAt(source, index);
|
|
3168
|
+
if (protectedToken) {
|
|
3169
|
+
index = protectedToken.end;
|
|
3170
|
+
continue;
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
if (canCloseStudioAnnotationEmphasisDelimiter(source, index, delimiter)) {
|
|
3174
|
+
const inner = source.slice(startIndex + delimiter.length, index);
|
|
3175
|
+
return {
|
|
3176
|
+
end: index + delimiter.length,
|
|
3177
|
+
latex: `\\${commandName}{${renderStudioAnnotationPdfLatexContent(inner)}}`,
|
|
3178
|
+
};
|
|
3148
3179
|
}
|
|
3180
|
+
|
|
3181
|
+
index += 1;
|
|
3149
3182
|
}
|
|
3150
3183
|
|
|
3151
|
-
|
|
3152
|
-
|
|
3184
|
+
return null;
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
function renderStudioAnnotationPlainTextPdfLatex(text: string): string {
|
|
3188
|
+
const source = String(text ?? "");
|
|
3189
|
+
let out = "";
|
|
3190
|
+
let index = 0;
|
|
3191
|
+
|
|
3192
|
+
while (index < source.length) {
|
|
3193
|
+
const strongMatch = readStudioAnnotationPdfEmphasisSpanAt(source, index, "**", "textbf")
|
|
3194
|
+
?? readStudioAnnotationPdfEmphasisSpanAt(source, index, "__", "textbf");
|
|
3195
|
+
if (strongMatch) {
|
|
3196
|
+
out += strongMatch.latex;
|
|
3197
|
+
index = strongMatch.end;
|
|
3198
|
+
continue;
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
const emphasisMatch = readStudioAnnotationPdfEmphasisSpanAt(source, index, "*", "emph")
|
|
3202
|
+
?? readStudioAnnotationPdfEmphasisSpanAt(source, index, "_", "emph");
|
|
3203
|
+
if (emphasisMatch) {
|
|
3204
|
+
out += emphasisMatch.latex;
|
|
3205
|
+
index = emphasisMatch.end;
|
|
3206
|
+
continue;
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
out += escapeStudioPdfLatexTextFragment(source[index] ?? "");
|
|
3210
|
+
index += 1;
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
return out;
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
function renderStudioAnnotationPdfLatex(text: string): string {
|
|
3217
|
+
const normalized = normalizeStudioAnnotationText(text);
|
|
3218
|
+
if (!normalized) return "";
|
|
3219
|
+
return renderStudioAnnotationPdfLatexContent(normalized).trim();
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
function replaceStudioAnnotationMarkersForPdfInSegment(text: string): string {
|
|
3223
|
+
const replaced = replaceStudioInlineAnnotationMarkers(
|
|
3224
|
+
String(text ?? ""),
|
|
3225
|
+
(marker) => {
|
|
3226
|
+
const cleaned = renderStudioAnnotationPdfLatex(marker.body);
|
|
3227
|
+
if (!cleaned) return "";
|
|
3228
|
+
return `\\studioannotation{${cleaned}}`;
|
|
3229
|
+
},
|
|
3230
|
+
);
|
|
3231
|
+
|
|
3232
|
+
return String(replaced ?? "")
|
|
3233
|
+
.replace(/\{\[\}\s*an:\s*([\s\S]*?)\s*\{\]\}/gi, (_match, markerText: string) => {
|
|
3234
|
+
const cleaned = renderStudioAnnotationPdfLatex(markerText);
|
|
3235
|
+
if (!cleaned) return "";
|
|
3236
|
+
return `\\studioannotation{${cleaned}}`;
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
function replaceStudioAnnotationMarkersForPdf(markdown: string): string {
|
|
3241
|
+
if (!hasStudioMarkdownAnnotationMarkers(markdown)) return String(markdown ?? "");
|
|
3242
|
+
return transformStudioMarkdownOutsideFences(markdown, (segment) => replaceStudioAnnotationMarkersForPdfInSegment(segment));
|
|
3153
3243
|
}
|
|
3154
3244
|
|
|
3155
3245
|
interface StudioPdfRenderOptions {
|
|
@@ -4146,38 +4236,19 @@ function replaceStudioAnnotationMarkersInDiffTokenLine(line: string, macroName:
|
|
|
4146
4236
|
if (!tokenMatch) return line;
|
|
4147
4237
|
|
|
4148
4238
|
const body = tokenMatch[1] ?? "";
|
|
4149
|
-
const markerPattern = /\[an:\s*([^\]]+?)\]/gi;
|
|
4150
|
-
let lastIndex = 0;
|
|
4151
|
-
let rewritten = "";
|
|
4152
|
-
let match: RegExpExecArray | null;
|
|
4153
|
-
|
|
4154
4239
|
const wrapText = (text: string): string => text ? `\\${macroName}{${text}}` : "";
|
|
4240
|
+
const rewritten = replaceStudioInlineAnnotationMarkers(
|
|
4241
|
+
body,
|
|
4242
|
+
(marker) => {
|
|
4243
|
+
const markerText = decodeStudioGeneratedCodeLatexText(normalizeStudioAnnotationText(marker.body));
|
|
4244
|
+
const cleaned = makeStudioHighlightingMathScriptsVerbatimSafe(renderStudioAnnotationPdfLatex(markerText));
|
|
4245
|
+
if (!cleaned) return "";
|
|
4246
|
+
return `\\studioannotation{${cleaned}}`;
|
|
4247
|
+
},
|
|
4248
|
+
(segment) => wrapText(segment),
|
|
4249
|
+
);
|
|
4155
4250
|
|
|
4156
|
-
|
|
4157
|
-
const token = match[0] ?? "";
|
|
4158
|
-
const start = match.index;
|
|
4159
|
-
if (start > lastIndex) {
|
|
4160
|
-
rewritten += wrapText(body.slice(lastIndex, start));
|
|
4161
|
-
}
|
|
4162
|
-
|
|
4163
|
-
const markerText = decodeStudioGeneratedCodeLatexText((match[1] ?? "").replace(/\s{2,}/g, " ").trim());
|
|
4164
|
-
const cleaned = makeStudioHighlightingMathScriptsVerbatimSafe(escapeStudioPdfLatexText(markerText));
|
|
4165
|
-
if (cleaned) {
|
|
4166
|
-
rewritten += `\\studioannotation{${cleaned}}`;
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
|
-
lastIndex = start + token.length;
|
|
4170
|
-
if (token.length === 0) {
|
|
4171
|
-
markerPattern.lastIndex += 1;
|
|
4172
|
-
}
|
|
4173
|
-
}
|
|
4174
|
-
|
|
4175
|
-
if (lastIndex === 0) return line;
|
|
4176
|
-
if (lastIndex < body.length) {
|
|
4177
|
-
rewritten += wrapText(body.slice(lastIndex));
|
|
4178
|
-
}
|
|
4179
|
-
|
|
4180
|
-
return rewritten || wrapText(body);
|
|
4251
|
+
return rewritten === body ? line : (rewritten || wrapText(body));
|
|
4181
4252
|
}
|
|
4182
4253
|
|
|
4183
4254
|
function rewriteStudioGeneratedDiffHighlighting(latex: string): string {
|
|
@@ -4496,7 +4567,7 @@ async function renderStudioPdfWithPandoc(
|
|
|
4496
4567
|
}
|
|
4497
4568
|
};
|
|
4498
4569
|
|
|
4499
|
-
if (isLatex && (latexSubfigurePdfTransform.groups.length > 0 ||
|
|
4570
|
+
if (isLatex && (latexSubfigurePdfTransform.groups.length > 0 || collectStudioInlineAnnotationMarkers(sourceWithResolvedRefs).length > 0)) {
|
|
4500
4571
|
return await renderStudioPdfFromGeneratedLatex(
|
|
4501
4572
|
sourceWithResolvedRefs,
|
|
4502
4573
|
pandocCommand,
|
|
@@ -5627,6 +5698,7 @@ function buildStudioHtml(
|
|
|
5627
5698
|
};
|
|
5628
5699
|
const cssVarsBlock = Object.entries(vars).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
5629
5700
|
const stylesheetHref = `/studio.css?token=${encodeURIComponent(studioToken ?? "")}`;
|
|
5701
|
+
const annotationHelpersScriptHref = `/studio-annotation-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
|
|
5630
5702
|
const clientScriptHref = `/studio-client.js?token=${encodeURIComponent(studioToken ?? "")}`;
|
|
5631
5703
|
const faviconHref = buildStudioFaviconDataUri(style);
|
|
5632
5704
|
const bootConfigJson = JSON.stringify({ mermaidConfig }).replace(/</g, "\\u003c");
|
|
@@ -5808,6 +5880,7 @@ ${cssVarsBlock}
|
|
|
5808
5880
|
<script>
|
|
5809
5881
|
window.__PI_STUDIO_BOOT__ = ${bootConfigJson};
|
|
5810
5882
|
</script>
|
|
5883
|
+
<script src="${annotationHelpersScriptHref}"></script>
|
|
5811
5884
|
<script src="${clientScriptHref}"></script>
|
|
5812
5885
|
</body>
|
|
5813
5886
|
</html>`;
|
|
@@ -7353,7 +7426,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7353
7426
|
return;
|
|
7354
7427
|
}
|
|
7355
7428
|
|
|
7356
|
-
if (requestUrl.pathname === "/studio-client.js") {
|
|
7429
|
+
if (requestUrl.pathname === "/studio-annotation-helpers.js" || requestUrl.pathname === "/studio-client.js") {
|
|
7357
7430
|
const token = requestUrl.searchParams.get("token") ?? "";
|
|
7358
7431
|
if (token !== serverState.token) {
|
|
7359
7432
|
respondText(res, 403, "Invalid or expired studio token. Re-run /studio.");
|
|
@@ -7367,8 +7440,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
7367
7440
|
return;
|
|
7368
7441
|
}
|
|
7369
7442
|
|
|
7443
|
+
const targetUrl = requestUrl.pathname === "/studio-annotation-helpers.js"
|
|
7444
|
+
? STUDIO_ANNOTATION_HELPERS_URL
|
|
7445
|
+
: STUDIO_CLIENT_URL;
|
|
7446
|
+
const targetLabel = requestUrl.pathname === "/studio-annotation-helpers.js"
|
|
7447
|
+
? "studio annotation helper script"
|
|
7448
|
+
: "studio client script";
|
|
7449
|
+
|
|
7370
7450
|
try {
|
|
7371
|
-
const clientScript = readFileSync(
|
|
7451
|
+
const clientScript = readFileSync(targetUrl, "utf-8");
|
|
7372
7452
|
res.writeHead(200, {
|
|
7373
7453
|
"Content-Type": "application/javascript; charset=utf-8",
|
|
7374
7454
|
"Cache-Control": "no-store",
|
|
@@ -7377,7 +7457,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7377
7457
|
});
|
|
7378
7458
|
res.end(clientScript);
|
|
7379
7459
|
} catch (error) {
|
|
7380
|
-
respondText(res, 500, `Failed to load
|
|
7460
|
+
respondText(res, 500, `Failed to load ${targetLabel}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7381
7461
|
}
|
|
7382
7462
|
return;
|
|
7383
7463
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.36",
|
|
4
4
|
"description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, prompt/response history, and live Markdown/LaTeX/code preview",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,11 +18,15 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"index.ts",
|
|
20
20
|
"client",
|
|
21
|
+
"shared",
|
|
21
22
|
"README.md",
|
|
22
23
|
"CHANGELOG.md",
|
|
23
24
|
"WORKFLOW.md",
|
|
24
25
|
"assets/screenshots"
|
|
25
26
|
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "node --test"
|
|
29
|
+
},
|
|
26
30
|
"pi": {
|
|
27
31
|
"extensions": [
|
|
28
32
|
"./index.ts"
|