pi-studio 0.5.35 → 0.5.37
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 +18 -0
- package/README.md +1 -0
- package/client/studio-annotation-helpers.js +543 -0
- package/client/studio-client.js +283 -139
- package/client/studio.css +133 -0
- package/index.ts +196 -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.css
CHANGED
|
@@ -220,6 +220,12 @@
|
|
|
220
220
|
filter: brightness(0.95);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
#scratchpadBtn.has-content {
|
|
224
|
+
border-color: var(--accent);
|
|
225
|
+
color: var(--accent);
|
|
226
|
+
font-weight: 600;
|
|
227
|
+
}
|
|
228
|
+
|
|
223
229
|
.section-header select {
|
|
224
230
|
font-weight: 600;
|
|
225
231
|
font-size: 14px;
|
|
@@ -523,6 +529,14 @@
|
|
|
523
529
|
vertical-align: baseline;
|
|
524
530
|
}
|
|
525
531
|
|
|
532
|
+
.annotation-preview-marker code {
|
|
533
|
+
font-family: var(--mono-font, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
534
|
+
font-size: 0.95em;
|
|
535
|
+
background: rgba(0, 0, 0, 0.08);
|
|
536
|
+
border-radius: 3px;
|
|
537
|
+
padding: 0 0.2em;
|
|
538
|
+
}
|
|
539
|
+
|
|
526
540
|
#sourcePreview {
|
|
527
541
|
flex: 1 1 auto;
|
|
528
542
|
min-height: 0;
|
|
@@ -1331,6 +1345,125 @@
|
|
|
1331
1345
|
background: var(--panel);
|
|
1332
1346
|
}
|
|
1333
1347
|
|
|
1348
|
+
body.scratchpad-open {
|
|
1349
|
+
overflow: hidden;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
.scratchpad-overlay {
|
|
1353
|
+
position: fixed;
|
|
1354
|
+
inset: 0;
|
|
1355
|
+
z-index: 50;
|
|
1356
|
+
display: flex;
|
|
1357
|
+
align-items: center;
|
|
1358
|
+
justify-content: center;
|
|
1359
|
+
padding: 24px;
|
|
1360
|
+
background: rgba(0, 0, 0, 0.48);
|
|
1361
|
+
backdrop-filter: blur(2px);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
.scratchpad-overlay[hidden] {
|
|
1365
|
+
display: none !important;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
.scratchpad-dialog {
|
|
1369
|
+
width: min(860px, 100%);
|
|
1370
|
+
max-height: min(82vh, 900px);
|
|
1371
|
+
border: 1px solid var(--border);
|
|
1372
|
+
border-radius: 14px;
|
|
1373
|
+
background: var(--panel);
|
|
1374
|
+
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.28);
|
|
1375
|
+
display: flex;
|
|
1376
|
+
flex-direction: column;
|
|
1377
|
+
overflow: hidden;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
.scratchpad-header {
|
|
1381
|
+
display: flex;
|
|
1382
|
+
align-items: flex-start;
|
|
1383
|
+
justify-content: space-between;
|
|
1384
|
+
gap: 12px;
|
|
1385
|
+
padding: 16px 18px 12px;
|
|
1386
|
+
border-bottom: 1px solid var(--border-muted);
|
|
1387
|
+
background: var(--panel-2);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
.scratchpad-header > div {
|
|
1391
|
+
flex: 1 1 auto;
|
|
1392
|
+
min-width: 0;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
.scratchpad-header h2 {
|
|
1396
|
+
margin: 0;
|
|
1397
|
+
font-size: 17px;
|
|
1398
|
+
font-weight: 600;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
.scratchpad-description {
|
|
1402
|
+
margin: 6px 0 0;
|
|
1403
|
+
font-size: 12px;
|
|
1404
|
+
line-height: 1.45;
|
|
1405
|
+
color: var(--muted);
|
|
1406
|
+
max-width: none;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
.scratchpad-close-btn {
|
|
1410
|
+
padding: 6px 10px;
|
|
1411
|
+
line-height: 1;
|
|
1412
|
+
flex: 0 0 auto;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
.scratchpad-textarea {
|
|
1416
|
+
width: 100%;
|
|
1417
|
+
min-height: 280px;
|
|
1418
|
+
flex: 1 1 auto;
|
|
1419
|
+
border: 0;
|
|
1420
|
+
border-bottom: 1px solid var(--border-muted);
|
|
1421
|
+
border-radius: 0;
|
|
1422
|
+
margin: 0;
|
|
1423
|
+
padding: 16px 18px;
|
|
1424
|
+
background: var(--panel);
|
|
1425
|
+
color: var(--text);
|
|
1426
|
+
font-family: var(--font-mono);
|
|
1427
|
+
font-size: 13px;
|
|
1428
|
+
line-height: 1.55;
|
|
1429
|
+
resize: vertical;
|
|
1430
|
+
outline: none;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
.scratchpad-footer {
|
|
1434
|
+
display: flex;
|
|
1435
|
+
align-items: center;
|
|
1436
|
+
justify-content: space-between;
|
|
1437
|
+
gap: 12px;
|
|
1438
|
+
flex-wrap: wrap;
|
|
1439
|
+
padding: 12px 18px 16px;
|
|
1440
|
+
background: var(--panel);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
.scratchpad-meta {
|
|
1444
|
+
font-size: 12px;
|
|
1445
|
+
color: var(--muted);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
.scratchpad-actions {
|
|
1449
|
+
display: inline-flex;
|
|
1450
|
+
align-items: center;
|
|
1451
|
+
gap: 8px;
|
|
1452
|
+
flex-wrap: wrap;
|
|
1453
|
+
justify-content: flex-end;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
#scratchpadDoneBtn:not(:disabled) {
|
|
1457
|
+
background: var(--accent);
|
|
1458
|
+
border-color: var(--accent);
|
|
1459
|
+
color: var(--accent-contrast);
|
|
1460
|
+
font-weight: 600;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
#scratchpadDoneBtn:not(:disabled):hover {
|
|
1464
|
+
filter: brightness(0.95);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1334
1467
|
#status.error { color: var(--error); }
|
|
1335
1468
|
#status.warning { color: var(--warn); }
|
|
1336
1469
|
#status.success { color: var(--ok); }
|
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");
|
|
@@ -5668,6 +5740,7 @@ ${cssVarsBlock}
|
|
|
5668
5740
|
</div>
|
|
5669
5741
|
<div class="section-header-actions">
|
|
5670
5742
|
<button id="leftFocusBtn" class="pane-focus-btn" type="button" title="Show only the editor pane. Shortcut: F10 or Cmd/Ctrl+Esc.">Focus pane</button>
|
|
5743
|
+
<button id="scratchpadBtn" type="button" title="Open a local persistent scratchpad for quick notes. Scratchpad text is never run, critiqued, or exported unless you explicitly insert it into the editor.">Scratchpad</button>
|
|
5671
5744
|
</div>
|
|
5672
5745
|
</div>
|
|
5673
5746
|
<div class="source-wrap">
|
|
@@ -5803,11 +5876,34 @@ ${cssVarsBlock}
|
|
|
5803
5876
|
<span class="shortcut-hint">Focus pane: F10 (or Cmd/Ctrl+Esc) to toggle · Run / queue steering: Cmd/Ctrl+Enter · Stop request: Esc</span>
|
|
5804
5877
|
</footer>
|
|
5805
5878
|
|
|
5879
|
+
<div id="scratchpadOverlay" class="scratchpad-overlay" hidden>
|
|
5880
|
+
<div id="scratchpadDialog" class="scratchpad-dialog" role="dialog" aria-modal="true" aria-labelledby="scratchpadTitle">
|
|
5881
|
+
<div class="scratchpad-header">
|
|
5882
|
+
<div>
|
|
5883
|
+
<h2 id="scratchpadTitle">Scratchpad</h2>
|
|
5884
|
+
<p class="scratchpad-description">Local persistent notes for thoughts you want to park while working. Closing the scratchpad does not clear it: notes persist locally until you edit or clear them. Scratchpad text is not run, critiqued, sent, or exported unless you explicitly insert it into the editor.</p>
|
|
5885
|
+
</div>
|
|
5886
|
+
<button id="scratchpadCloseBtn" type="button" class="scratchpad-close-btn" aria-label="Keep current scratchpad text and close scratchpad" title="Keep current scratchpad text and close scratchpad">✕</button>
|
|
5887
|
+
</div>
|
|
5888
|
+
<textarea id="scratchpadText" class="scratchpad-textarea" placeholder="Jot quick thoughts, TODOs, or prompt ideas here..."></textarea>
|
|
5889
|
+
<div class="scratchpad-footer">
|
|
5890
|
+
<span id="scratchpadMeta" class="scratchpad-meta">Empty · local only</span>
|
|
5891
|
+
<div class="scratchpad-actions">
|
|
5892
|
+
<button id="scratchpadInsertBtn" type="button" title="Insert the scratchpad text into the editor at the current selection, or append it if no editor selection is available.">Insert into editor</button>
|
|
5893
|
+
<button id="scratchpadCopyBtn" type="button" title="Copy scratchpad text to the clipboard.">Copy</button>
|
|
5894
|
+
<button id="scratchpadClearBtn" type="button" title="Clear scratchpad text.">Clear</button>
|
|
5895
|
+
<button id="scratchpadDoneBtn" type="button" title="Keep the current scratchpad text and close the scratchpad.">Keep and close</button>
|
|
5896
|
+
</div>
|
|
5897
|
+
</div>
|
|
5898
|
+
</div>
|
|
5899
|
+
</div>
|
|
5900
|
+
|
|
5806
5901
|
<!-- Defer sanitizer script so studio can boot/connect even if CDN is slow or blocked. -->
|
|
5807
5902
|
<script defer src="https://cdn.jsdelivr.net/npm/dompurify@3.2.6/dist/purify.min.js"></script>
|
|
5808
5903
|
<script>
|
|
5809
5904
|
window.__PI_STUDIO_BOOT__ = ${bootConfigJson};
|
|
5810
5905
|
</script>
|
|
5906
|
+
<script src="${annotationHelpersScriptHref}"></script>
|
|
5811
5907
|
<script src="${clientScriptHref}"></script>
|
|
5812
5908
|
</body>
|
|
5813
5909
|
</html>`;
|
|
@@ -7353,7 +7449,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7353
7449
|
return;
|
|
7354
7450
|
}
|
|
7355
7451
|
|
|
7356
|
-
if (requestUrl.pathname === "/studio-client.js") {
|
|
7452
|
+
if (requestUrl.pathname === "/studio-annotation-helpers.js" || requestUrl.pathname === "/studio-client.js") {
|
|
7357
7453
|
const token = requestUrl.searchParams.get("token") ?? "";
|
|
7358
7454
|
if (token !== serverState.token) {
|
|
7359
7455
|
respondText(res, 403, "Invalid or expired studio token. Re-run /studio.");
|
|
@@ -7367,8 +7463,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
7367
7463
|
return;
|
|
7368
7464
|
}
|
|
7369
7465
|
|
|
7466
|
+
const targetUrl = requestUrl.pathname === "/studio-annotation-helpers.js"
|
|
7467
|
+
? STUDIO_ANNOTATION_HELPERS_URL
|
|
7468
|
+
: STUDIO_CLIENT_URL;
|
|
7469
|
+
const targetLabel = requestUrl.pathname === "/studio-annotation-helpers.js"
|
|
7470
|
+
? "studio annotation helper script"
|
|
7471
|
+
: "studio client script";
|
|
7472
|
+
|
|
7370
7473
|
try {
|
|
7371
|
-
const clientScript = readFileSync(
|
|
7474
|
+
const clientScript = readFileSync(targetUrl, "utf-8");
|
|
7372
7475
|
res.writeHead(200, {
|
|
7373
7476
|
"Content-Type": "application/javascript; charset=utf-8",
|
|
7374
7477
|
"Cache-Control": "no-store",
|
|
@@ -7377,7 +7480,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7377
7480
|
});
|
|
7378
7481
|
res.end(clientScript);
|
|
7379
7482
|
} catch (error) {
|
|
7380
|
-
respondText(res, 500, `Failed to load
|
|
7483
|
+
respondText(res, 500, `Failed to load ${targetLabel}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7381
7484
|
}
|
|
7382
7485
|
return;
|
|
7383
7486
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.37",
|
|
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"
|