pi-studio 0.7.1 → 0.8.0
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 +7 -0
- package/README.md +5 -4
- package/client/studio-client.js +489 -29
- package/client/studio.css +61 -0
- package/index.ts +1120 -17
- package/package.json +1 -1
package/client/studio-client.js
CHANGED
|
@@ -88,6 +88,10 @@
|
|
|
88
88
|
const loadCritiqueNotesBtn = document.getElementById("loadCritiqueNotesBtn");
|
|
89
89
|
const loadCritiqueFullBtn = document.getElementById("loadCritiqueFullBtn");
|
|
90
90
|
const copyResponseBtn = document.getElementById("copyResponseBtn");
|
|
91
|
+
const exportPreviewControlsEl = document.getElementById("exportPreviewControls");
|
|
92
|
+
const exportPreviewMenuEl = document.getElementById("exportPreviewMenu");
|
|
93
|
+
const exportPreviewPdfBtn = document.getElementById("exportPreviewPdfBtn");
|
|
94
|
+
const exportPreviewHtmlBtn = document.getElementById("exportPreviewHtmlBtn");
|
|
91
95
|
const exportPdfBtn = document.getElementById("exportPdfBtn");
|
|
92
96
|
const historyPrevBtn = document.getElementById("historyPrevBtn");
|
|
93
97
|
const historyNextBtn = document.getElementById("historyNextBtn");
|
|
@@ -201,6 +205,9 @@
|
|
|
201
205
|
let responseHistory = [];
|
|
202
206
|
let responseHistoryIndex = -1;
|
|
203
207
|
let traceState = null;
|
|
208
|
+
let liveTraceState = null;
|
|
209
|
+
const traceSnapshotCache = new Map();
|
|
210
|
+
let traceDisplayContext = { mode: "live", responseId: null, historyIndex: -1, total: 0, summary: null };
|
|
204
211
|
let traceFilter = "all";
|
|
205
212
|
let traceAutoScroll = true;
|
|
206
213
|
let traceRenderRaf = null;
|
|
@@ -215,7 +222,7 @@
|
|
|
215
222
|
let terminalActivityLabel = "";
|
|
216
223
|
let lastSpecificToolLabel = "";
|
|
217
224
|
let uiBusy = false;
|
|
218
|
-
let
|
|
225
|
+
let previewExportInProgress = false;
|
|
219
226
|
let compactInProgress = false;
|
|
220
227
|
let modelLabel = (document.body && document.body.dataset && document.body.dataset.modelLabel) || "none";
|
|
221
228
|
let terminalSessionLabel = (document.body && document.body.dataset && document.body.dataset.terminalLabel) || "unknown";
|
|
@@ -409,6 +416,102 @@
|
|
|
409
416
|
renderTraceViewIfActive();
|
|
410
417
|
}
|
|
411
418
|
|
|
419
|
+
function shouldDisplayLiveTrace() {
|
|
420
|
+
return !Array.isArray(responseHistory)
|
|
421
|
+
|| responseHistory.length === 0
|
|
422
|
+
|| responseHistoryIndex < 0
|
|
423
|
+
|| responseHistoryIndex >= responseHistory.length - 1;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function setTraceDisplayContext(nextContext) {
|
|
427
|
+
const fallback = { mode: "live", responseId: null, historyIndex: -1, total: 0, summary: null };
|
|
428
|
+
traceDisplayContext = Object.assign(fallback, nextContext && typeof nextContext === "object" ? nextContext : {});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function ensureLiveTraceState() {
|
|
432
|
+
if (!liveTraceState) liveTraceState = createEmptyTraceState();
|
|
433
|
+
return liveTraceState;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function upsertTraceEntryInState(state, entry) {
|
|
437
|
+
const normalized = normalizeTraceEntry(entry, Array.isArray(state.entries) ? state.entries.length : 0);
|
|
438
|
+
if (!normalized) return null;
|
|
439
|
+
if (!Array.isArray(state.entries)) state.entries = [];
|
|
440
|
+
const index = state.entries.findIndex((candidate) => candidate.id === normalized.id);
|
|
441
|
+
if (index >= 0) {
|
|
442
|
+
state.entries[index] = normalized;
|
|
443
|
+
} else {
|
|
444
|
+
state.entries.push(normalized);
|
|
445
|
+
}
|
|
446
|
+
state.updatedAt = normalized.updatedAt;
|
|
447
|
+
return normalized;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function replaceLiveTraceState(nextState) {
|
|
451
|
+
liveTraceState = normalizeTraceState(nextState);
|
|
452
|
+
if (shouldDisplayLiveTrace()) {
|
|
453
|
+
setTraceDisplayContext({ mode: "live", responseId: null, historyIndex: responseHistoryIndex, total: responseHistory.length, summary: null });
|
|
454
|
+
replaceTraceState(liveTraceState);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function upsertLiveTraceEntry(entry) {
|
|
459
|
+
const normalized = upsertTraceEntryInState(ensureLiveTraceState(), entry);
|
|
460
|
+
if (!normalized) return;
|
|
461
|
+
if (shouldDisplayLiveTrace()) {
|
|
462
|
+
setTraceDisplayContext({ mode: "live", responseId: null, historyIndex: responseHistoryIndex, total: responseHistory.length, summary: null });
|
|
463
|
+
upsertTraceEntry(normalized);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function appendLiveTraceAssistantDelta(entryId, deltaKind, delta, updatedAt) {
|
|
468
|
+
if (typeof delta !== "string" || !delta) return;
|
|
469
|
+
const state = ensureLiveTraceState();
|
|
470
|
+
const targetId = typeof entryId === "string" && entryId.trim() ? entryId.trim() : null;
|
|
471
|
+
let entry = targetId ? state.entries.find((candidate) => candidate.id === targetId) : null;
|
|
472
|
+
if (!entry || entry.type !== "assistant") {
|
|
473
|
+
entry = normalizeTraceEntry({
|
|
474
|
+
id: targetId || ("trace-assistant-live-" + Date.now()),
|
|
475
|
+
type: "assistant",
|
|
476
|
+
startedAt: updatedAt,
|
|
477
|
+
updatedAt,
|
|
478
|
+
thinking: "",
|
|
479
|
+
text: "",
|
|
480
|
+
status: "streaming",
|
|
481
|
+
stopReason: null,
|
|
482
|
+
}, state.entries.length);
|
|
483
|
+
if (!entry) return;
|
|
484
|
+
state.entries.push(entry);
|
|
485
|
+
}
|
|
486
|
+
if (deltaKind === "thinking") {
|
|
487
|
+
entry.thinking += delta;
|
|
488
|
+
} else {
|
|
489
|
+
entry.text += delta;
|
|
490
|
+
}
|
|
491
|
+
entry.status = "streaming";
|
|
492
|
+
entry.updatedAt = parseFiniteNumber(updatedAt) || Date.now();
|
|
493
|
+
state.updatedAt = entry.updatedAt;
|
|
494
|
+
if (shouldDisplayLiveTrace()) {
|
|
495
|
+
setTraceDisplayContext({ mode: "live", responseId: null, historyIndex: responseHistoryIndex, total: responseHistory.length, summary: null });
|
|
496
|
+
appendTraceAssistantDelta(entryId, deltaKind, delta, updatedAt);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function updateLiveTraceStatusFromMessage(message) {
|
|
501
|
+
if (!message || typeof message !== "object") return;
|
|
502
|
+
const state = ensureLiveTraceState();
|
|
503
|
+
state.runId = parseNonEmptyString(message.runId) || state.runId;
|
|
504
|
+
if (Object.prototype.hasOwnProperty.call(message, "requestId")) state.requestId = parseNonEmptyString(message.requestId);
|
|
505
|
+
if (Object.prototype.hasOwnProperty.call(message, "requestKind")) state.requestKind = parseNonEmptyString(message.requestKind);
|
|
506
|
+
if (Object.prototype.hasOwnProperty.call(message, "startedAt")) state.startedAt = parseFiniteNumber(message.startedAt);
|
|
507
|
+
if (Object.prototype.hasOwnProperty.call(message, "updatedAt")) state.updatedAt = parseFiniteNumber(message.updatedAt);
|
|
508
|
+
if (Object.prototype.hasOwnProperty.call(message, "status")) state.status = normalizeTraceStatus(message.status);
|
|
509
|
+
if (shouldDisplayLiveTrace()) {
|
|
510
|
+
setTraceDisplayContext({ mode: "live", responseId: null, historyIndex: responseHistoryIndex, total: responseHistory.length, summary: null });
|
|
511
|
+
updateTraceStatusFromMessage(message);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
412
515
|
function normalizeTraceFilter(filter) {
|
|
413
516
|
return filter === "thinking" || filter === "tools" ? filter : "all";
|
|
414
517
|
}
|
|
@@ -1034,7 +1137,11 @@
|
|
|
1034
1137
|
}
|
|
1035
1138
|
rightIdentityEl.appendChild(rightTitleGroupEl);
|
|
1036
1139
|
const rightToolsEl = makeStudioUiRefreshElement("div", "studio-refresh-pane-tools");
|
|
1037
|
-
if (
|
|
1140
|
+
if (exportPreviewControlsEl) {
|
|
1141
|
+
rightToolsEl.appendChild(exportPreviewControlsEl);
|
|
1142
|
+
} else if (exportPdfBtn) {
|
|
1143
|
+
rightToolsEl.appendChild(exportPdfBtn);
|
|
1144
|
+
}
|
|
1038
1145
|
rightHeaderEl.replaceChildren(rightIdentityEl, rightToolsEl);
|
|
1039
1146
|
}
|
|
1040
1147
|
|
|
@@ -2027,6 +2134,20 @@
|
|
|
2027
2134
|
return kind === "critique" ? "critique" : "annotation";
|
|
2028
2135
|
}
|
|
2029
2136
|
|
|
2137
|
+
function normalizeTraceSummary(summary) {
|
|
2138
|
+
if (!summary || typeof summary !== "object") return null;
|
|
2139
|
+
return {
|
|
2140
|
+
hasTrace: summary.hasTrace === true,
|
|
2141
|
+
entryCount: typeof summary.entryCount === "number" && Number.isFinite(summary.entryCount)
|
|
2142
|
+
? Math.max(0, Math.floor(summary.entryCount))
|
|
2143
|
+
: 0,
|
|
2144
|
+
startedAt: parseFiniteNumber(summary.startedAt),
|
|
2145
|
+
updatedAt: parseFiniteNumber(summary.updatedAt),
|
|
2146
|
+
status: normalizeTraceStatus(summary.status),
|
|
2147
|
+
truncated: summary.truncated === true,
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2030
2151
|
function normalizeHistoryItem(item, fallbackIndex) {
|
|
2031
2152
|
if (!item || typeof item !== "object") return null;
|
|
2032
2153
|
if (typeof item.markdown !== "string") return null;
|
|
@@ -2057,6 +2178,7 @@
|
|
|
2057
2178
|
const promptTriggerText = typeof item.promptTriggerText === "string"
|
|
2058
2179
|
? item.promptTriggerText
|
|
2059
2180
|
: (item.promptTriggerText == null ? null : String(item.promptTriggerText));
|
|
2181
|
+
const traceSummary = normalizeTraceSummary(item.traceSummary);
|
|
2060
2182
|
|
|
2061
2183
|
return {
|
|
2062
2184
|
id,
|
|
@@ -2069,6 +2191,7 @@
|
|
|
2069
2191
|
promptTriggerKind,
|
|
2070
2192
|
promptSteeringCount,
|
|
2071
2193
|
promptTriggerText,
|
|
2194
|
+
traceSummary,
|
|
2072
2195
|
};
|
|
2073
2196
|
}
|
|
2074
2197
|
|
|
@@ -2078,6 +2201,48 @@
|
|
|
2078
2201
|
return responseHistory[responseHistoryIndex] || null;
|
|
2079
2202
|
}
|
|
2080
2203
|
|
|
2204
|
+
function syncTraceForSelectedHistoryItem() {
|
|
2205
|
+
const item = getSelectedHistoryItem();
|
|
2206
|
+
const total = Array.isArray(responseHistory) ? responseHistory.length : 0;
|
|
2207
|
+
const index = responseHistoryIndex;
|
|
2208
|
+
if (!item) {
|
|
2209
|
+
setTraceDisplayContext({ mode: "live", responseId: null, historyIndex: index, total, summary: null });
|
|
2210
|
+
replaceTraceState(liveTraceState || createEmptyTraceState());
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
if (index >= total - 1) {
|
|
2214
|
+
setTraceDisplayContext({ mode: "live", responseId: null, historyIndex: index, total, summary: item.traceSummary || null });
|
|
2215
|
+
replaceTraceState(liveTraceState || createEmptyTraceState());
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
const summary = item.traceSummary || null;
|
|
2220
|
+
if (!summary || !summary.hasTrace) {
|
|
2221
|
+
setTraceDisplayContext({ mode: "missing", responseId: item.id, historyIndex: index, total, summary });
|
|
2222
|
+
replaceTraceState(createEmptyTraceState());
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
const cached = traceSnapshotCache.get(item.id);
|
|
2227
|
+
if (cached) {
|
|
2228
|
+
setTraceDisplayContext({ mode: "history", responseId: item.id, historyIndex: index, total, summary });
|
|
2229
|
+
replaceTraceState(cached);
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
setTraceDisplayContext({ mode: "loading", responseId: item.id, historyIndex: index, total, summary });
|
|
2234
|
+
replaceTraceState({
|
|
2235
|
+
runId: null,
|
|
2236
|
+
requestId: null,
|
|
2237
|
+
requestKind: null,
|
|
2238
|
+
status: "idle",
|
|
2239
|
+
startedAt: summary.startedAt || null,
|
|
2240
|
+
updatedAt: summary.updatedAt || null,
|
|
2241
|
+
entries: [],
|
|
2242
|
+
});
|
|
2243
|
+
sendMessage({ type: "get_trace_snapshot", responseHistoryId: item.id });
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2081
2246
|
function clearActiveResponseView() {
|
|
2082
2247
|
pendingResponseScrollReset = false;
|
|
2083
2248
|
latestResponseMarkdown = "";
|
|
@@ -2152,6 +2317,7 @@
|
|
|
2152
2317
|
const nextId = nextItem && typeof nextItem.id === "string" ? nextItem.id : null;
|
|
2153
2318
|
const applied = applySelectedHistoryItem({ resetScroll: previousId !== nextId });
|
|
2154
2319
|
updateHistoryControls();
|
|
2320
|
+
syncTraceForSelectedHistoryItem();
|
|
2155
2321
|
|
|
2156
2322
|
if (applied && !(options && options.silent)) {
|
|
2157
2323
|
const item = getSelectedHistoryItem();
|
|
@@ -2202,20 +2368,43 @@
|
|
|
2202
2368
|
return selectHistoryIndex(targetIndex, { silent: Boolean(options && options.silent) });
|
|
2203
2369
|
}
|
|
2204
2370
|
|
|
2371
|
+
function getTraceHistoryContextLabel() {
|
|
2372
|
+
const context = traceDisplayContext || {};
|
|
2373
|
+
const total = typeof context.total === "number" && Number.isFinite(context.total) ? context.total : responseHistory.length;
|
|
2374
|
+
const index = typeof context.historyIndex === "number" && Number.isFinite(context.historyIndex) ? context.historyIndex : responseHistoryIndex;
|
|
2375
|
+
if (context.mode === "history" || context.mode === "missing" || context.mode === "loading") {
|
|
2376
|
+
return total > 0 && index >= 0 ? ("response " + (index + 1) + "/" + total) : "selected response";
|
|
2377
|
+
}
|
|
2378
|
+
return "live";
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2205
2381
|
function updateReferenceBadge() {
|
|
2206
2382
|
if (!referenceBadgeEl) return;
|
|
2207
2383
|
|
|
2208
2384
|
if (rightView === "trace") {
|
|
2209
2385
|
const state = traceState || createEmptyTraceState();
|
|
2386
|
+
const context = traceDisplayContext || {};
|
|
2210
2387
|
const entryCount = getTraceEntriesForFilter(traceFilter).length;
|
|
2211
2388
|
const time = formatReferenceTime(state.startedAt || state.updatedAt);
|
|
2389
|
+
if (context.mode === "loading") {
|
|
2390
|
+
referenceBadgeEl.textContent = "Working: loading " + getTraceHistoryContextLabel();
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
if (context.mode === "missing") {
|
|
2394
|
+
referenceBadgeEl.textContent = "Working: no saved working for " + getTraceHistoryContextLabel();
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2212
2397
|
if (state.status === "idle") {
|
|
2213
2398
|
referenceBadgeEl.textContent = "Working: no active run yet";
|
|
2214
2399
|
return;
|
|
2215
2400
|
}
|
|
2216
|
-
const statusLabel =
|
|
2401
|
+
const statusLabel = context.mode === "history"
|
|
2402
|
+
? "saved"
|
|
2403
|
+
: (state.status === "running" ? "live" : "complete");
|
|
2217
2404
|
referenceBadgeEl.textContent = "Working: " + statusLabel
|
|
2405
|
+
+ (context.mode === "history" ? (" · " + getTraceHistoryContextLabel()) : "")
|
|
2218
2406
|
+ (entryCount ? (" · " + entryCount + " entr" + (entryCount === 1 ? "y" : "ies")) : "")
|
|
2407
|
+
+ (context.summary && context.summary.truncated ? " · truncated" : "")
|
|
2219
2408
|
+ (time ? (" · " + time) : "");
|
|
2220
2409
|
return;
|
|
2221
2410
|
}
|
|
@@ -2305,6 +2494,15 @@
|
|
|
2305
2494
|
return prepared;
|
|
2306
2495
|
}
|
|
2307
2496
|
|
|
2497
|
+
function prepareEditorTextForHtmlExport(text) {
|
|
2498
|
+
const prepared = prepareEditorTextForPreview(text);
|
|
2499
|
+
const lang = normalizeFenceLanguage(editorLanguage || "");
|
|
2500
|
+
if (lang && lang !== "markdown" && lang !== "latex") {
|
|
2501
|
+
return wrapAsFencedCodeBlock(prepared, lang);
|
|
2502
|
+
}
|
|
2503
|
+
return prepared;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2308
2506
|
function updateSyncBadge(normalizedEditorText) {
|
|
2309
2507
|
if (!syncBadgeEl) return;
|
|
2310
2508
|
|
|
@@ -3387,7 +3585,7 @@
|
|
|
3387
3585
|
}
|
|
3388
3586
|
|
|
3389
3587
|
async function exportRightPanePdf() {
|
|
3390
|
-
if (uiBusy ||
|
|
3588
|
+
if (uiBusy || previewExportInProgress) {
|
|
3391
3589
|
setStatus("Studio is busy.", "warning");
|
|
3392
3590
|
return;
|
|
3393
3591
|
}
|
|
@@ -3427,7 +3625,7 @@
|
|
|
3427
3625
|
filenameHint = stem + "-preview.pdf";
|
|
3428
3626
|
}
|
|
3429
3627
|
|
|
3430
|
-
|
|
3628
|
+
previewExportInProgress = true;
|
|
3431
3629
|
updateResultActionButtons();
|
|
3432
3630
|
setStatus("Exporting PDF…", "warning");
|
|
3433
3631
|
|
|
@@ -3540,11 +3738,201 @@
|
|
|
3540
3738
|
const detail = error && error.message ? error.message : String(error || "unknown error");
|
|
3541
3739
|
setStatus("PDF export failed: " + detail, "error");
|
|
3542
3740
|
} finally {
|
|
3543
|
-
|
|
3741
|
+
previewExportInProgress = false;
|
|
3544
3742
|
updateResultActionButtons();
|
|
3545
3743
|
}
|
|
3546
3744
|
}
|
|
3547
3745
|
|
|
3746
|
+
async function exportRightPaneHtml() {
|
|
3747
|
+
if (uiBusy || previewExportInProgress) {
|
|
3748
|
+
setStatus("Studio is busy.", "warning");
|
|
3749
|
+
return;
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
const token = getToken();
|
|
3753
|
+
if (!token) {
|
|
3754
|
+
setStatus("Missing Studio token in URL. Re-run /studio.", "error");
|
|
3755
|
+
return;
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
3759
|
+
if (!rightPaneShowsPreview) {
|
|
3760
|
+
setStatus("Switch right pane to Response (Preview) or Editor (Preview) to export HTML.", "warning");
|
|
3761
|
+
return;
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
const markdown = rightView === "editor-preview"
|
|
3765
|
+
? prepareEditorTextForHtmlExport(sourceTextEl.value)
|
|
3766
|
+
: prepareEditorTextForPreview(latestResponseMarkdown);
|
|
3767
|
+
if (!markdown || !markdown.trim()) {
|
|
3768
|
+
setStatus("Nothing to export yet.", "warning");
|
|
3769
|
+
return;
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
const effectivePath = getEffectiveSavePath();
|
|
3773
|
+
const sourcePath = effectivePath || sourceState.path || "";
|
|
3774
|
+
const resourceDir = (!sourcePath && resourceDirInput) ? resourceDirInput.value.trim() : "";
|
|
3775
|
+
const isEditorPreview = rightView === "editor-preview";
|
|
3776
|
+
const editorHtmlLanguage = isEditorPreview ? normalizeFenceLanguage(editorLanguage || "") : "";
|
|
3777
|
+
const isLatex = isEditorPreview
|
|
3778
|
+
? editorHtmlLanguage === "latex"
|
|
3779
|
+
: /\\documentclass\b|\\begin\{document\}/.test(markdown);
|
|
3780
|
+
let filenameHint = isEditorPreview ? "studio-editor-preview.html" : "studio-response-preview.html";
|
|
3781
|
+
let titleHint = isEditorPreview ? "Studio editor preview" : "Studio response preview";
|
|
3782
|
+
if (sourcePath) {
|
|
3783
|
+
const baseName = sourcePath.split(/[\\/]/).pop() || "studio";
|
|
3784
|
+
const stem = baseName.replace(/\.[^.]+$/, "") || "studio";
|
|
3785
|
+
filenameHint = stem + "-preview.html";
|
|
3786
|
+
titleHint = stem + " preview";
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
previewExportInProgress = true;
|
|
3790
|
+
updateResultActionButtons();
|
|
3791
|
+
setStatus("Exporting HTML…", "warning");
|
|
3792
|
+
|
|
3793
|
+
try {
|
|
3794
|
+
const response = await fetch("/export-html?token=" + encodeURIComponent(token), {
|
|
3795
|
+
method: "POST",
|
|
3796
|
+
headers: {
|
|
3797
|
+
"Content-Type": "application/json",
|
|
3798
|
+
},
|
|
3799
|
+
body: JSON.stringify({
|
|
3800
|
+
markdown: String(markdown || ""),
|
|
3801
|
+
sourcePath: sourcePath,
|
|
3802
|
+
resourceDir: resourceDir,
|
|
3803
|
+
isLatex: isLatex,
|
|
3804
|
+
editorHtmlLanguage: editorHtmlLanguage,
|
|
3805
|
+
filenameHint: filenameHint,
|
|
3806
|
+
title: titleHint,
|
|
3807
|
+
}),
|
|
3808
|
+
});
|
|
3809
|
+
|
|
3810
|
+
const contentType = String(response.headers.get("content-type") || "").toLowerCase();
|
|
3811
|
+
if (!response.ok) {
|
|
3812
|
+
let message = "HTML export failed with HTTP " + response.status + ".";
|
|
3813
|
+
if (contentType.includes("application/json")) {
|
|
3814
|
+
const payload = await response.json().catch(() => null);
|
|
3815
|
+
if (payload && typeof payload.error === "string") {
|
|
3816
|
+
message = payload.error;
|
|
3817
|
+
}
|
|
3818
|
+
} else {
|
|
3819
|
+
const text = await response.text().catch(() => "");
|
|
3820
|
+
if (text && text.trim()) {
|
|
3821
|
+
message = text.trim();
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
throw new Error(message);
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
if (contentType.includes("application/json")) {
|
|
3828
|
+
const payload = await response.json().catch(() => null);
|
|
3829
|
+
if (!payload || typeof payload.downloadUrl !== "string") {
|
|
3830
|
+
throw new Error("HTML export prepared successfully, but Studio did not receive a download URL.");
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
const exportWarning = typeof payload.warning === "string" ? payload.warning.trim() : "";
|
|
3834
|
+
const openError = typeof payload.openError === "string" ? payload.openError.trim() : "";
|
|
3835
|
+
const openedExternal = payload.openedExternal === true;
|
|
3836
|
+
let downloadName = typeof payload.filename === "string" && payload.filename.trim()
|
|
3837
|
+
? payload.filename.trim()
|
|
3838
|
+
: (filenameHint || "studio-preview.html");
|
|
3839
|
+
if (!/\.html?$/i.test(downloadName)) {
|
|
3840
|
+
downloadName += ".html";
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
if (openedExternal) {
|
|
3844
|
+
if (exportWarning) {
|
|
3845
|
+
setStatus("Opened HTML in default browser with warning: " + exportWarning, "warning");
|
|
3846
|
+
} else {
|
|
3847
|
+
setStatus("Opened HTML in default browser: " + downloadName, "success");
|
|
3848
|
+
}
|
|
3849
|
+
return;
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
const link = document.createElement("a");
|
|
3853
|
+
link.href = payload.downloadUrl;
|
|
3854
|
+
link.download = downloadName;
|
|
3855
|
+
link.rel = "noopener";
|
|
3856
|
+
document.body.appendChild(link);
|
|
3857
|
+
link.click();
|
|
3858
|
+
link.remove();
|
|
3859
|
+
|
|
3860
|
+
if (openError) {
|
|
3861
|
+
if (exportWarning) {
|
|
3862
|
+
setStatus("Opened browser fallback because external viewer failed (" + openError + "). Warning: " + exportWarning, "warning");
|
|
3863
|
+
} else {
|
|
3864
|
+
setStatus("Opened browser fallback because external viewer failed (" + openError + ").", "warning");
|
|
3865
|
+
}
|
|
3866
|
+
} else if (exportWarning) {
|
|
3867
|
+
setStatus("Exported HTML with warning: " + exportWarning, "warning");
|
|
3868
|
+
} else {
|
|
3869
|
+
setStatus("Exported HTML: " + downloadName, "success");
|
|
3870
|
+
}
|
|
3871
|
+
return;
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
const exportWarning = String(response.headers.get("x-pi-studio-export-warning") || "").trim();
|
|
3875
|
+
const blob = await response.blob();
|
|
3876
|
+
const headerFilename = parseContentDispositionFilename(response.headers.get("content-disposition"));
|
|
3877
|
+
let downloadName = headerFilename || filenameHint || "studio-preview.html";
|
|
3878
|
+
if (!/\.html?$/i.test(downloadName)) {
|
|
3879
|
+
downloadName += ".html";
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3882
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
3883
|
+
const link = document.createElement("a");
|
|
3884
|
+
link.href = blobUrl;
|
|
3885
|
+
link.download = downloadName;
|
|
3886
|
+
link.rel = "noopener";
|
|
3887
|
+
document.body.appendChild(link);
|
|
3888
|
+
link.click();
|
|
3889
|
+
link.remove();
|
|
3890
|
+
window.setTimeout(() => {
|
|
3891
|
+
URL.revokeObjectURL(blobUrl);
|
|
3892
|
+
}, 1800);
|
|
3893
|
+
|
|
3894
|
+
if (exportWarning) {
|
|
3895
|
+
setStatus("Exported HTML with warning: " + exportWarning, "warning");
|
|
3896
|
+
} else {
|
|
3897
|
+
setStatus("Exported HTML: " + downloadName, "success");
|
|
3898
|
+
}
|
|
3899
|
+
} catch (error) {
|
|
3900
|
+
const detail = error && error.message ? error.message : String(error || "unknown error");
|
|
3901
|
+
setStatus("HTML export failed: " + detail, "error");
|
|
3902
|
+
} finally {
|
|
3903
|
+
previewExportInProgress = false;
|
|
3904
|
+
updateResultActionButtons();
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
function closeExportPreviewMenu() {
|
|
3909
|
+
if (!exportPreviewMenuEl) return;
|
|
3910
|
+
exportPreviewMenuEl.hidden = true;
|
|
3911
|
+
if (exportPdfBtn) {
|
|
3912
|
+
exportPdfBtn.classList.remove("is-open");
|
|
3913
|
+
exportPdfBtn.setAttribute("aria-expanded", "false");
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
function toggleExportPreviewMenu() {
|
|
3918
|
+
if (!exportPreviewMenuEl || !exportPdfBtn || exportPdfBtn.disabled) return;
|
|
3919
|
+
if (typeof closeStudioUiRefreshMenus === "function") {
|
|
3920
|
+
closeStudioUiRefreshMenus();
|
|
3921
|
+
}
|
|
3922
|
+
const willOpen = exportPreviewMenuEl.hidden;
|
|
3923
|
+
exportPreviewMenuEl.hidden = !willOpen;
|
|
3924
|
+
exportPdfBtn.classList.toggle("is-open", willOpen);
|
|
3925
|
+
exportPdfBtn.setAttribute("aria-expanded", willOpen ? "true" : "false");
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
function exportRightPaneFormat(format) {
|
|
3929
|
+
closeExportPreviewMenu();
|
|
3930
|
+
if (format === "html") {
|
|
3931
|
+
return exportRightPaneHtml();
|
|
3932
|
+
}
|
|
3933
|
+
return exportRightPanePdf();
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3548
3936
|
function normalizeCopyableBlockText(text) {
|
|
3549
3937
|
return String(text || "").replace(/\r\n/g, "\n").replace(/\u200b/g, "");
|
|
3550
3938
|
}
|
|
@@ -3857,17 +4245,27 @@
|
|
|
3857
4245
|
const visibleWorking = buildVisibleWorkingText(filter);
|
|
3858
4246
|
const hasVisibleContent = Boolean(visibleWorking.trim());
|
|
3859
4247
|
const started = formatReferenceTime(state.startedAt || state.updatedAt);
|
|
3860
|
-
const
|
|
3861
|
-
|
|
3862
|
-
|
|
4248
|
+
const context = traceDisplayContext || {};
|
|
4249
|
+
const statusLabel = context.mode === "history"
|
|
4250
|
+
? "Saved"
|
|
4251
|
+
: (context.mode === "loading"
|
|
4252
|
+
? "Loading"
|
|
4253
|
+
: (context.mode === "missing"
|
|
4254
|
+
? "Not saved"
|
|
4255
|
+
: (state.status === "running" ? "Live" : (state.status === "complete" ? "Complete" : "Idle"))));
|
|
3863
4256
|
const filterMeta = filter === "thinking"
|
|
3864
4257
|
? "Thinking only"
|
|
3865
4258
|
: (filter === "tools" ? "Tools only" : null);
|
|
4259
|
+
const historyMeta = (context.mode === "history" || context.mode === "missing" || context.mode === "loading")
|
|
4260
|
+
? getTraceHistoryContextLabel()
|
|
4261
|
+
: null;
|
|
3866
4262
|
const toolbar = "<div class='trace-toolbar'>"
|
|
3867
4263
|
+ "<div class='trace-summary'>"
|
|
3868
4264
|
+ "<span class='trace-summary-badge'>Working</span>"
|
|
3869
4265
|
+ "<span class='trace-summary-status trace-status-" + escapeHtml(String(state.status || "idle")) + "'>" + escapeHtml(statusLabel) + "</span>"
|
|
4266
|
+
+ (historyMeta ? ("<span class='trace-summary-meta'>" + escapeHtml(historyMeta) + "</span>") : "")
|
|
3870
4267
|
+ (started ? ("<span class='trace-summary-meta'>Started " + escapeHtml(started) + "</span>") : "")
|
|
4268
|
+
+ (context.summary && context.summary.truncated ? "<span class='trace-summary-meta'>Truncated</span>" : "")
|
|
3871
4269
|
+ (filterMeta ? ("<span class='trace-summary-meta'>" + escapeHtml(filterMeta) + "</span>") : "")
|
|
3872
4270
|
+ "</div>"
|
|
3873
4271
|
+ "<div class='trace-controls'>"
|
|
@@ -3882,13 +4280,17 @@
|
|
|
3882
4280
|
+ "</div>";
|
|
3883
4281
|
|
|
3884
4282
|
if (!entries.length) {
|
|
3885
|
-
const emptyMessage =
|
|
3886
|
-
? "
|
|
3887
|
-
: (
|
|
3888
|
-
? "No
|
|
3889
|
-
: (
|
|
3890
|
-
? "
|
|
3891
|
-
:
|
|
4283
|
+
const emptyMessage = context.mode === "loading"
|
|
4284
|
+
? "Loading saved working for this response…"
|
|
4285
|
+
: (context.mode === "missing"
|
|
4286
|
+
? "No working was saved for this response."
|
|
4287
|
+
: (filter === "thinking"
|
|
4288
|
+
? "No thinking steps in this working view yet."
|
|
4289
|
+
: (filter === "tools"
|
|
4290
|
+
? "No tool steps in this working view yet."
|
|
4291
|
+
: (state.status === "running"
|
|
4292
|
+
? "Waiting for the first model or tool update…"
|
|
4293
|
+
: "No live working view yet. Start a run or critique to watch working details here."))));
|
|
3892
4294
|
return "<div class='trace-panel'>" + toolbar + "<div class='trace-empty'>" + escapeHtml(emptyMessage) + "</div></div>";
|
|
3893
4295
|
}
|
|
3894
4296
|
|
|
@@ -4065,19 +4467,34 @@
|
|
|
4065
4467
|
|
|
4066
4468
|
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
4067
4469
|
const exportText = rightView === "editor-preview" ? prepareEditorTextForPreview(sourceTextEl.value) : latestResponseMarkdown;
|
|
4068
|
-
const
|
|
4470
|
+
const canExportPreview = rightPaneShowsPreview && Boolean(String(exportText || "").trim());
|
|
4069
4471
|
if (exportPdfBtn) {
|
|
4070
|
-
exportPdfBtn.disabled = uiBusy ||
|
|
4472
|
+
exportPdfBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview;
|
|
4473
|
+
exportPdfBtn.textContent = previewExportInProgress ? "Exporting…" : "Export right preview";
|
|
4071
4474
|
if (rightView === "trace") {
|
|
4072
|
-
exportPdfBtn.title = "Working view does not support
|
|
4475
|
+
exportPdfBtn.title = "Working view does not support preview export.";
|
|
4073
4476
|
} else if (rightView === "markdown") {
|
|
4074
|
-
exportPdfBtn.title = "Switch right pane to Response (Preview) or Editor (Preview) to export
|
|
4075
|
-
} else if (!
|
|
4477
|
+
exportPdfBtn.title = "Switch right pane to Response (Preview) or Editor (Preview) to export.";
|
|
4478
|
+
} else if (!canExportPreview) {
|
|
4076
4479
|
exportPdfBtn.title = "Nothing to export yet.";
|
|
4077
4480
|
} else {
|
|
4078
|
-
exportPdfBtn.title = "
|
|
4481
|
+
exportPdfBtn.title = "Choose PDF or HTML and export the current right-pane preview.";
|
|
4079
4482
|
}
|
|
4080
4483
|
}
|
|
4484
|
+
if (exportPreviewPdfBtn) {
|
|
4485
|
+
exportPreviewPdfBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview;
|
|
4486
|
+
}
|
|
4487
|
+
if (exportPreviewHtmlBtn) {
|
|
4488
|
+
exportPreviewHtmlBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview;
|
|
4489
|
+
}
|
|
4490
|
+
if (exportPreviewControlsEl) {
|
|
4491
|
+
exportPreviewControlsEl.title = canExportPreview
|
|
4492
|
+
? "Choose a format and export the current right-pane preview."
|
|
4493
|
+
: "Switch right pane to a non-empty preview before exporting.";
|
|
4494
|
+
}
|
|
4495
|
+
if (!canExportPreview || previewExportInProgress) {
|
|
4496
|
+
closeExportPreviewMenu();
|
|
4497
|
+
}
|
|
4081
4498
|
|
|
4082
4499
|
pullLatestBtn.disabled = uiBusy || followLatest;
|
|
4083
4500
|
pullLatestBtn.textContent = queuedLatestResponse ? "Fetch latest response *" : "Fetch latest response";
|
|
@@ -10014,7 +10431,7 @@
|
|
|
10014
10431
|
}
|
|
10015
10432
|
|
|
10016
10433
|
if (message.traceState) {
|
|
10017
|
-
|
|
10434
|
+
replaceLiveTraceState(message.traceState);
|
|
10018
10435
|
}
|
|
10019
10436
|
|
|
10020
10437
|
let appliedHistory = false;
|
|
@@ -10064,22 +10481,41 @@
|
|
|
10064
10481
|
}
|
|
10065
10482
|
|
|
10066
10483
|
if (message.type === "trace_reset") {
|
|
10067
|
-
|
|
10484
|
+
replaceLiveTraceState(message.trace);
|
|
10068
10485
|
return;
|
|
10069
10486
|
}
|
|
10070
10487
|
|
|
10071
10488
|
if (message.type === "trace_status") {
|
|
10072
|
-
|
|
10489
|
+
updateLiveTraceStatusFromMessage(message);
|
|
10073
10490
|
return;
|
|
10074
10491
|
}
|
|
10075
10492
|
|
|
10076
10493
|
if (message.type === "trace_entry_upsert") {
|
|
10077
|
-
|
|
10494
|
+
upsertLiveTraceEntry(message.entry);
|
|
10078
10495
|
return;
|
|
10079
10496
|
}
|
|
10080
10497
|
|
|
10081
10498
|
if (message.type === "trace_assistant_delta") {
|
|
10082
|
-
|
|
10499
|
+
appendLiveTraceAssistantDelta(message.entryId, message.deltaKind, message.delta, message.updatedAt);
|
|
10500
|
+
return;
|
|
10501
|
+
}
|
|
10502
|
+
|
|
10503
|
+
if (message.type === "trace_snapshot") {
|
|
10504
|
+
const responseId = typeof message.responseHistoryId === "string" ? message.responseHistoryId.trim() : "";
|
|
10505
|
+
if (responseId && message.traceState) {
|
|
10506
|
+
const normalizedSnapshot = normalizeTraceState(message.traceState);
|
|
10507
|
+
traceSnapshotCache.set(responseId, normalizedSnapshot);
|
|
10508
|
+
if (traceDisplayContext && traceDisplayContext.responseId === responseId) {
|
|
10509
|
+
setTraceDisplayContext({
|
|
10510
|
+
mode: "history",
|
|
10511
|
+
responseId,
|
|
10512
|
+
historyIndex: responseHistoryIndex,
|
|
10513
|
+
total: responseHistory.length,
|
|
10514
|
+
summary: normalizeTraceSummary(message.summary) || (getSelectedHistoryItem() ? getSelectedHistoryItem().traceSummary : null),
|
|
10515
|
+
});
|
|
10516
|
+
replaceTraceState(normalizedSnapshot);
|
|
10517
|
+
}
|
|
10518
|
+
}
|
|
10083
10519
|
return;
|
|
10084
10520
|
}
|
|
10085
10521
|
|
|
@@ -11189,11 +11625,35 @@
|
|
|
11189
11625
|
});
|
|
11190
11626
|
|
|
11191
11627
|
if (exportPdfBtn) {
|
|
11192
|
-
exportPdfBtn.addEventListener("click", () => {
|
|
11193
|
-
|
|
11628
|
+
exportPdfBtn.addEventListener("click", (event) => {
|
|
11629
|
+
event.preventDefault();
|
|
11630
|
+
event.stopPropagation();
|
|
11631
|
+
toggleExportPreviewMenu();
|
|
11632
|
+
});
|
|
11633
|
+
}
|
|
11634
|
+
|
|
11635
|
+
if (exportPreviewMenuEl) {
|
|
11636
|
+
exportPreviewMenuEl.addEventListener("click", (event) => {
|
|
11637
|
+
const target = event.target;
|
|
11638
|
+
const actionBtn = target instanceof Element ? target.closest("[data-export-preview-format]") : null;
|
|
11639
|
+
if (!actionBtn) return;
|
|
11640
|
+
event.preventDefault();
|
|
11641
|
+
event.stopPropagation();
|
|
11642
|
+
if (actionBtn.disabled) return;
|
|
11643
|
+
const format = String(actionBtn.getAttribute("data-export-preview-format") || "pdf").toLowerCase();
|
|
11644
|
+
void exportRightPaneFormat(format === "html" ? "html" : "pdf");
|
|
11194
11645
|
});
|
|
11195
11646
|
}
|
|
11196
11647
|
|
|
11648
|
+
document.addEventListener("click", (event) => {
|
|
11649
|
+
const target = event.target;
|
|
11650
|
+
if (target instanceof Element && target.closest("#exportPreviewControls")) return;
|
|
11651
|
+
closeExportPreviewMenu();
|
|
11652
|
+
});
|
|
11653
|
+
document.addEventListener("keydown", (event) => {
|
|
11654
|
+
if (event.key === "Escape") closeExportPreviewMenu();
|
|
11655
|
+
});
|
|
11656
|
+
|
|
11197
11657
|
saveAsBtn.addEventListener("click", () => {
|
|
11198
11658
|
const content = sourceTextEl.value;
|
|
11199
11659
|
if (!content.trim()) {
|