@arcreflex/agent-transcripts 0.1.8 → 0.1.9
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/CLAUDE.md +4 -0
- package/README.md +40 -3
- package/bun.lock +89 -0
- package/package.json +3 -2
- package/src/adapters/claude-code.ts +203 -32
- package/src/cache.ts +129 -0
- package/src/cli.ts +86 -5
- package/src/convert.ts +6 -14
- package/src/render-html.ts +1096 -0
- package/src/render-index.ts +611 -0
- package/src/render.ts +6 -110
- package/src/serve.ts +308 -0
- package/src/sync.ts +131 -18
- package/src/title.ts +172 -0
- package/src/types.ts +7 -0
- package/src/utils/html.ts +12 -0
- package/src/utils/openrouter.ts +116 -0
- package/src/utils/provenance.ts +25 -41
- package/src/utils/tree.ts +116 -0
- package/test/fixtures/claude/non-message-parents.input.jsonl +9 -0
- package/test/fixtures/claude/non-message-parents.output.md +30 -0
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render: intermediate transcript format → HTML
|
|
3
|
+
*
|
|
4
|
+
* Parallel to render.ts but outputs standalone HTML with:
|
|
5
|
+
* - Collapsible tool calls with input/result details
|
|
6
|
+
* - Collapsible thinking sections
|
|
7
|
+
* - Raw JSON toggle for each block
|
|
8
|
+
* - Inline styles (no external dependencies)
|
|
9
|
+
* - Terminal-inspired dark theme with amber accents
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Transcript, Message, ToolCall } from "./types.ts";
|
|
13
|
+
import { createHighlighter, type Highlighter } from "shiki";
|
|
14
|
+
import {
|
|
15
|
+
buildTree,
|
|
16
|
+
findLatestLeaf,
|
|
17
|
+
tracePath,
|
|
18
|
+
getFirstLine,
|
|
19
|
+
} from "./utils/tree.ts";
|
|
20
|
+
import { escapeHtml } from "./utils/html.ts";
|
|
21
|
+
|
|
22
|
+
// Lazy-loaded shiki highlighter
|
|
23
|
+
let highlighter: Highlighter | null = null;
|
|
24
|
+
|
|
25
|
+
async function getHighlighter(): Promise<Highlighter> {
|
|
26
|
+
if (!highlighter) {
|
|
27
|
+
highlighter = await createHighlighter({
|
|
28
|
+
themes: ["ayu-dark", "github-light"],
|
|
29
|
+
langs: ["markdown", "javascript", "typescript", "python", "bash", "json"],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return highlighter;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Styles - Terminal Chronicle Theme
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
const STYLES = `
|
|
40
|
+
/* ============================================================================
|
|
41
|
+
Agent Transcripts - Terminal Chronicle Theme
|
|
42
|
+
Inspired by the Claude Code TUI: dark, focused, monospace-forward
|
|
43
|
+
============================================================================ */
|
|
44
|
+
|
|
45
|
+
@import url('https://fonts.googleapis.com/css2?family=Berkeley+Mono:wght@400;500&family=IBM+Plex+Mono:wght@400;500;600&family=Inter:wght@400;500&display=swap');
|
|
46
|
+
|
|
47
|
+
:root {
|
|
48
|
+
/* Typography - Monospace primary, clean sans for body */
|
|
49
|
+
--font-mono: 'Berkeley Mono', 'IBM Plex Mono', 'JetBrains Mono', 'SF Mono', Consolas, monospace;
|
|
50
|
+
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
51
|
+
|
|
52
|
+
/* Dark theme - Terminal aesthetic */
|
|
53
|
+
--bg: #0d0d0d;
|
|
54
|
+
--bg-elevated: #141414;
|
|
55
|
+
--bg-surface: #1a1a1a;
|
|
56
|
+
--fg: #e4e4e4;
|
|
57
|
+
--fg-secondary: #a3a3a3;
|
|
58
|
+
--muted: #666666;
|
|
59
|
+
--border: #2a2a2a;
|
|
60
|
+
--border-subtle: #222222;
|
|
61
|
+
|
|
62
|
+
/* Accent - Amber/Orange (Claude Code cursor vibe) */
|
|
63
|
+
--accent: #f59e0b;
|
|
64
|
+
--accent-dim: #b45309;
|
|
65
|
+
--accent-glow: rgba(245, 158, 11, 0.15);
|
|
66
|
+
|
|
67
|
+
/* Semantic colors */
|
|
68
|
+
--user-accent: #3b82f6;
|
|
69
|
+
--user-bg: rgba(59, 130, 246, 0.08);
|
|
70
|
+
--user-border: rgba(59, 130, 246, 0.3);
|
|
71
|
+
--assistant-bg: var(--bg-elevated);
|
|
72
|
+
--assistant-border: var(--border);
|
|
73
|
+
--system-accent: #8b5cf6;
|
|
74
|
+
--system-bg: rgba(139, 92, 246, 0.06);
|
|
75
|
+
--system-border: rgba(139, 92, 246, 0.25);
|
|
76
|
+
--error-accent: #ef4444;
|
|
77
|
+
--error-bg: rgba(239, 68, 68, 0.08);
|
|
78
|
+
--error-border: rgba(239, 68, 68, 0.3);
|
|
79
|
+
--tool-accent: #10b981;
|
|
80
|
+
--tool-bg: rgba(16, 185, 129, 0.06);
|
|
81
|
+
--tool-border: rgba(16, 185, 129, 0.2);
|
|
82
|
+
|
|
83
|
+
/* Code blocks */
|
|
84
|
+
--code-bg: #0f0f0f;
|
|
85
|
+
--code-border: #252525;
|
|
86
|
+
--thinking-bg: #111111;
|
|
87
|
+
--thinking-border: #1f1f1f;
|
|
88
|
+
--raw-bg: #0a0a0a;
|
|
89
|
+
|
|
90
|
+
/* Links */
|
|
91
|
+
--link: #60a5fa;
|
|
92
|
+
--link-hover: #93c5fd;
|
|
93
|
+
|
|
94
|
+
/* Shadows & effects */
|
|
95
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
96
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
97
|
+
--glow: 0 0 20px var(--accent-glow);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Light theme - Minimal, paper-like */
|
|
101
|
+
@media (prefers-color-scheme: light) {
|
|
102
|
+
:root {
|
|
103
|
+
--bg: #fafafa;
|
|
104
|
+
--bg-elevated: #ffffff;
|
|
105
|
+
--bg-surface: #f5f5f5;
|
|
106
|
+
--fg: #171717;
|
|
107
|
+
--fg-secondary: #525252;
|
|
108
|
+
--muted: #a3a3a3;
|
|
109
|
+
--border: #e5e5e5;
|
|
110
|
+
--border-subtle: #f0f0f0;
|
|
111
|
+
|
|
112
|
+
--accent: #d97706;
|
|
113
|
+
--accent-dim: #92400e;
|
|
114
|
+
--accent-glow: rgba(217, 119, 6, 0.1);
|
|
115
|
+
|
|
116
|
+
--user-accent: #2563eb;
|
|
117
|
+
--user-bg: rgba(37, 99, 235, 0.04);
|
|
118
|
+
--user-border: rgba(37, 99, 235, 0.2);
|
|
119
|
+
--assistant-bg: var(--bg-elevated);
|
|
120
|
+
--assistant-border: var(--border);
|
|
121
|
+
--system-accent: #7c3aed;
|
|
122
|
+
--system-bg: rgba(124, 58, 237, 0.04);
|
|
123
|
+
--system-border: rgba(124, 58, 237, 0.15);
|
|
124
|
+
--error-accent: #dc2626;
|
|
125
|
+
--error-bg: rgba(220, 38, 38, 0.04);
|
|
126
|
+
--error-border: rgba(220, 38, 38, 0.2);
|
|
127
|
+
--tool-accent: #059669;
|
|
128
|
+
--tool-bg: rgba(5, 150, 105, 0.04);
|
|
129
|
+
--tool-border: rgba(5, 150, 105, 0.15);
|
|
130
|
+
|
|
131
|
+
--code-bg: #f5f5f5;
|
|
132
|
+
--code-border: #e5e5e5;
|
|
133
|
+
--thinking-bg: #fafafa;
|
|
134
|
+
--thinking-border: #e5e5e5;
|
|
135
|
+
--raw-bg: #f0f0f0;
|
|
136
|
+
|
|
137
|
+
--link: #2563eb;
|
|
138
|
+
--link-hover: #1d4ed8;
|
|
139
|
+
|
|
140
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
141
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
142
|
+
--glow: none;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
147
|
+
|
|
148
|
+
html {
|
|
149
|
+
font-size: 15px;
|
|
150
|
+
-webkit-font-smoothing: antialiased;
|
|
151
|
+
-moz-osx-font-smoothing: grayscale;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
body {
|
|
155
|
+
font-family: var(--font-body);
|
|
156
|
+
background: var(--bg);
|
|
157
|
+
color: var(--fg);
|
|
158
|
+
line-height: 1.65;
|
|
159
|
+
margin: 0;
|
|
160
|
+
padding: 0;
|
|
161
|
+
min-height: 100vh;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Main container */
|
|
165
|
+
.transcript-container {
|
|
166
|
+
max-width: 54rem;
|
|
167
|
+
margin: 0 auto;
|
|
168
|
+
padding: 2.5rem 2rem 4rem;
|
|
169
|
+
position: relative;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Subtle left border accent */
|
|
173
|
+
.transcript-container::before {
|
|
174
|
+
content: '';
|
|
175
|
+
position: fixed;
|
|
176
|
+
left: 0;
|
|
177
|
+
top: 0;
|
|
178
|
+
bottom: 0;
|
|
179
|
+
width: 2px;
|
|
180
|
+
background: linear-gradient(
|
|
181
|
+
180deg,
|
|
182
|
+
transparent 0%,
|
|
183
|
+
var(--accent-dim) 15%,
|
|
184
|
+
var(--accent) 50%,
|
|
185
|
+
var(--accent-dim) 85%,
|
|
186
|
+
transparent 100%
|
|
187
|
+
);
|
|
188
|
+
opacity: 0.6;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
a {
|
|
192
|
+
color: var(--link);
|
|
193
|
+
text-decoration: none;
|
|
194
|
+
transition: color 0.15s ease;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
a:hover {
|
|
198
|
+
color: var(--link-hover);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* ============================================================================
|
|
202
|
+
Header - Terminal prompt style
|
|
203
|
+
============================================================================ */
|
|
204
|
+
|
|
205
|
+
header {
|
|
206
|
+
margin-bottom: 2.5rem;
|
|
207
|
+
padding-bottom: 1.5rem;
|
|
208
|
+
border-bottom: 1px solid var(--border);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
header h1 {
|
|
212
|
+
font-family: var(--font-mono);
|
|
213
|
+
font-weight: 500;
|
|
214
|
+
font-size: 1.125rem;
|
|
215
|
+
line-height: 1.4;
|
|
216
|
+
margin: 0 0 0.75rem 0;
|
|
217
|
+
color: var(--fg);
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: baseline;
|
|
220
|
+
gap: 0.5rem;
|
|
221
|
+
flex-wrap: wrap;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
header h1::before {
|
|
225
|
+
content: '>';
|
|
226
|
+
color: var(--accent);
|
|
227
|
+
font-weight: 600;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.meta {
|
|
231
|
+
font-family: var(--font-mono);
|
|
232
|
+
color: var(--muted);
|
|
233
|
+
font-size: 0.75rem;
|
|
234
|
+
line-height: 1.7;
|
|
235
|
+
display: flex;
|
|
236
|
+
flex-direction: column;
|
|
237
|
+
gap: 0.25rem;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.meta code {
|
|
241
|
+
color: var(--fg-secondary);
|
|
242
|
+
background: none;
|
|
243
|
+
padding: 0;
|
|
244
|
+
font-size: inherit;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.warnings {
|
|
248
|
+
background: var(--error-bg);
|
|
249
|
+
border: 1px solid var(--error-border);
|
|
250
|
+
border-left: 3px solid var(--error-accent);
|
|
251
|
+
padding: 0.875rem 1rem;
|
|
252
|
+
border-radius: 0 4px 4px 0;
|
|
253
|
+
margin-top: 1.25rem;
|
|
254
|
+
font-family: var(--font-mono);
|
|
255
|
+
font-size: 0.8rem;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.warnings strong {
|
|
259
|
+
color: var(--error-accent);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.warnings ul {
|
|
263
|
+
margin: 0.5rem 0 0 0;
|
|
264
|
+
padding-left: 1.25rem;
|
|
265
|
+
color: var(--fg-secondary);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.warnings li {
|
|
269
|
+
margin: 0.25rem 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* ============================================================================
|
|
273
|
+
Messages
|
|
274
|
+
============================================================================ */
|
|
275
|
+
|
|
276
|
+
main {
|
|
277
|
+
position: relative;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.message {
|
|
281
|
+
margin: 1.25rem 0;
|
|
282
|
+
position: relative;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* User messages - boxed with background, extended to align content with assistant */
|
|
286
|
+
.message.user {
|
|
287
|
+
padding: 1rem 1.25rem;
|
|
288
|
+
margin-left: -1.25rem;
|
|
289
|
+
margin-right: -1.25rem;
|
|
290
|
+
border-radius: 6px;
|
|
291
|
+
background: var(--user-bg);
|
|
292
|
+
border: 1px solid var(--user-border);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.message-header {
|
|
296
|
+
display: flex;
|
|
297
|
+
justify-content: space-between;
|
|
298
|
+
align-items: center;
|
|
299
|
+
margin-bottom: 0.625rem;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.message-label {
|
|
303
|
+
font-family: var(--font-mono);
|
|
304
|
+
font-weight: 500;
|
|
305
|
+
font-size: 0.6875rem;
|
|
306
|
+
text-transform: uppercase;
|
|
307
|
+
letter-spacing: 0.1em;
|
|
308
|
+
display: flex;
|
|
309
|
+
align-items: center;
|
|
310
|
+
gap: 0.5rem;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* Role indicator dot */
|
|
314
|
+
.message-label::before {
|
|
315
|
+
content: '';
|
|
316
|
+
display: inline-block;
|
|
317
|
+
width: 6px;
|
|
318
|
+
height: 6px;
|
|
319
|
+
border-radius: 50%;
|
|
320
|
+
background: currentColor;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.message.user .message-label {
|
|
324
|
+
color: var(--user-accent);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.message.user .message-label::before {
|
|
328
|
+
background: var(--user-accent);
|
|
329
|
+
box-shadow: 0 0 6px var(--user-accent);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* Assistant messages - no box, flows on page */
|
|
333
|
+
.message.assistant {
|
|
334
|
+
padding: 0;
|
|
335
|
+
background: transparent;
|
|
336
|
+
border: none;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.message.assistant .message-header {
|
|
340
|
+
padding-bottom: 0.5rem;
|
|
341
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
342
|
+
margin-bottom: 1rem;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.message.assistant .message-label {
|
|
346
|
+
color: var(--accent);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.message.assistant .message-label::before {
|
|
350
|
+
background: var(--accent);
|
|
351
|
+
box-shadow: 0 0 6px var(--accent);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* System messages */
|
|
355
|
+
.message.system {
|
|
356
|
+
padding: 1rem 1.25rem;
|
|
357
|
+
border-radius: 6px;
|
|
358
|
+
background: var(--system-bg);
|
|
359
|
+
border: 1px solid var(--system-border);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.message.system .message-label {
|
|
363
|
+
color: var(--system-accent);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.message.system .message-label::before {
|
|
367
|
+
background: var(--system-accent);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* Error messages */
|
|
371
|
+
.message.error {
|
|
372
|
+
padding: 1rem 1.25rem;
|
|
373
|
+
border-radius: 6px;
|
|
374
|
+
background: var(--error-bg);
|
|
375
|
+
border: 1px solid var(--error-border);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.message.error .message-label {
|
|
379
|
+
color: var(--error-accent);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.message.error .message-label::before {
|
|
383
|
+
background: var(--error-accent);
|
|
384
|
+
box-shadow: 0 0 6px var(--error-accent);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* ============================================================================
|
|
388
|
+
Content
|
|
389
|
+
============================================================================ */
|
|
390
|
+
|
|
391
|
+
.content {
|
|
392
|
+
font-size: 0.9375rem;
|
|
393
|
+
line-height: 1.7;
|
|
394
|
+
color: var(--fg);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* Shiki syntax highlighting - dual theme support */
|
|
398
|
+
.content .shiki,
|
|
399
|
+
.thinking .shiki {
|
|
400
|
+
background: transparent !important;
|
|
401
|
+
border: none;
|
|
402
|
+
padding: 0;
|
|
403
|
+
margin: 0;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.content .shiki span,
|
|
407
|
+
.thinking .shiki span {
|
|
408
|
+
color: var(--shiki-light);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
@media (prefers-color-scheme: dark) {
|
|
412
|
+
.content .shiki span,
|
|
413
|
+
.thinking .shiki span {
|
|
414
|
+
color: var(--shiki-dark);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.content p {
|
|
419
|
+
margin: 0 0 0.875rem 0;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.content p:last-child {
|
|
423
|
+
margin-bottom: 0;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.content ul, .content ol {
|
|
427
|
+
margin: 0 0 0.875rem 0;
|
|
428
|
+
padding-left: 1.5rem;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.content li {
|
|
432
|
+
margin: 0.25rem 0;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/* ============================================================================
|
|
436
|
+
Code - Primary visual element
|
|
437
|
+
============================================================================ */
|
|
438
|
+
|
|
439
|
+
pre, code {
|
|
440
|
+
font-family: var(--font-mono);
|
|
441
|
+
font-size: 0.8125rem;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
code {
|
|
445
|
+
background: var(--code-bg);
|
|
446
|
+
border: 1px solid var(--code-border);
|
|
447
|
+
padding: 0.125rem 0.375rem;
|
|
448
|
+
border-radius: 3px;
|
|
449
|
+
color: var(--fg);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
pre {
|
|
453
|
+
background: var(--code-bg);
|
|
454
|
+
border: 1px solid var(--code-border);
|
|
455
|
+
padding: 1rem 1.25rem;
|
|
456
|
+
border-radius: 4px;
|
|
457
|
+
overflow-x: auto;
|
|
458
|
+
margin: 0.875rem 0;
|
|
459
|
+
line-height: 1.5;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
pre code {
|
|
463
|
+
background: none;
|
|
464
|
+
border: none;
|
|
465
|
+
padding: 0;
|
|
466
|
+
border-radius: 0;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/* ============================================================================
|
|
470
|
+
Details & Thinking
|
|
471
|
+
============================================================================ */
|
|
472
|
+
|
|
473
|
+
details {
|
|
474
|
+
margin: 0.625rem 0;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
summary {
|
|
478
|
+
cursor: pointer;
|
|
479
|
+
user-select: none;
|
|
480
|
+
font-family: var(--font-mono);
|
|
481
|
+
font-size: 0.75rem;
|
|
482
|
+
color: var(--muted);
|
|
483
|
+
padding: 0.25rem 0;
|
|
484
|
+
display: inline-flex;
|
|
485
|
+
align-items: center;
|
|
486
|
+
gap: 0.375rem;
|
|
487
|
+
transition: color 0.15s ease;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
summary:hover {
|
|
491
|
+
color: var(--fg-secondary);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
summary::marker,
|
|
495
|
+
summary::-webkit-details-marker {
|
|
496
|
+
color: var(--accent-dim);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.thinking {
|
|
500
|
+
border-left: 2px solid var(--accent-dim);
|
|
501
|
+
padding: 0.5rem 0 0.5rem 1rem;
|
|
502
|
+
margin: 0.75rem 0;
|
|
503
|
+
font-size: 0.8125rem;
|
|
504
|
+
color: var(--fg-secondary);
|
|
505
|
+
font-style: italic;
|
|
506
|
+
line-height: 1.65;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/* ============================================================================
|
|
510
|
+
Tool Calls - Inline command style
|
|
511
|
+
============================================================================ */
|
|
512
|
+
|
|
513
|
+
.tool-calls {
|
|
514
|
+
margin: 0.75rem 0;
|
|
515
|
+
position: relative;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.tool-call {
|
|
519
|
+
margin: 0.25rem 0;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
details.tool-call {
|
|
523
|
+
margin: 0.25rem 0;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
details.tool-call > summary {
|
|
527
|
+
cursor: pointer;
|
|
528
|
+
list-style: none;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
details.tool-call > summary::-webkit-details-marker {
|
|
532
|
+
display: none;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
details.tool-call > summary::marker {
|
|
536
|
+
display: none;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.tool-call-header {
|
|
540
|
+
font-family: var(--font-mono);
|
|
541
|
+
font-size: 0.8125rem;
|
|
542
|
+
line-height: 1.5;
|
|
543
|
+
color: var(--tool-accent);
|
|
544
|
+
display: inline-flex;
|
|
545
|
+
align-items: baseline;
|
|
546
|
+
gap: 0;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
details.tool-call > .tool-call-header::before {
|
|
550
|
+
content: '▸';
|
|
551
|
+
margin-right: 0.5rem;
|
|
552
|
+
font-size: 0.625rem;
|
|
553
|
+
transition: transform 0.15s ease;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
details.tool-call[open] > .tool-call-header::before {
|
|
557
|
+
transform: rotate(90deg);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.tool-call-name {
|
|
561
|
+
font-weight: 500;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.tool-call-summary {
|
|
565
|
+
color: var(--muted);
|
|
566
|
+
margin-left: 0.5rem;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.tool-call-error {
|
|
570
|
+
color: var(--error-accent);
|
|
571
|
+
margin-left: 0.5rem;
|
|
572
|
+
font-weight: 500;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.tool-detail-content {
|
|
576
|
+
margin-top: 0.375rem;
|
|
577
|
+
padding: 0.75rem 1rem;
|
|
578
|
+
background: var(--code-bg);
|
|
579
|
+
border: 1px solid var(--code-border);
|
|
580
|
+
border-radius: 3px;
|
|
581
|
+
font-family: var(--font-mono);
|
|
582
|
+
font-size: 0.6875rem;
|
|
583
|
+
white-space: pre-wrap;
|
|
584
|
+
word-wrap: break-word;
|
|
585
|
+
max-height: 16rem;
|
|
586
|
+
overflow-y: auto;
|
|
587
|
+
line-height: 1.5;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* ============================================================================
|
|
591
|
+
Branch Notes
|
|
592
|
+
============================================================================ */
|
|
593
|
+
|
|
594
|
+
.branch-note {
|
|
595
|
+
background: var(--bg-surface);
|
|
596
|
+
border: 1px solid var(--border);
|
|
597
|
+
border-left: 2px solid var(--accent);
|
|
598
|
+
padding: 0.875rem 1rem;
|
|
599
|
+
border-radius: 0 4px 4px 0;
|
|
600
|
+
margin: 1.25rem 0;
|
|
601
|
+
font-family: var(--font-mono);
|
|
602
|
+
font-size: 0.75rem;
|
|
603
|
+
color: var(--fg-secondary);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.branch-note strong {
|
|
607
|
+
color: var(--fg);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.branch-note ul {
|
|
611
|
+
margin: 0.375rem 0 0 0;
|
|
612
|
+
padding-left: 1.25rem;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.branch-note code {
|
|
616
|
+
font-size: 0.6875rem;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/* ============================================================================
|
|
620
|
+
Raw JSON Toggle - appears on hover/focus
|
|
621
|
+
============================================================================ */
|
|
622
|
+
|
|
623
|
+
.raw-toggle {
|
|
624
|
+
position: absolute;
|
|
625
|
+
top: 0.2rem;
|
|
626
|
+
left: -2rem;
|
|
627
|
+
background: none;
|
|
628
|
+
border: none;
|
|
629
|
+
padding: 0.25rem;
|
|
630
|
+
font-size: 0.625rem;
|
|
631
|
+
color: var(--muted);
|
|
632
|
+
cursor: pointer;
|
|
633
|
+
font-family: var(--font-mono);
|
|
634
|
+
font-weight: 500;
|
|
635
|
+
opacity: 0;
|
|
636
|
+
transition: opacity 0.15s ease, color 0.15s ease;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/* Show on hover or when parent has focus-within */
|
|
640
|
+
.message:hover .raw-toggle,
|
|
641
|
+
.message:focus-within .raw-toggle,
|
|
642
|
+
.tool-calls:hover .raw-toggle,
|
|
643
|
+
.tool-calls:focus-within .raw-toggle,
|
|
644
|
+
.raw-toggle:focus {
|
|
645
|
+
opacity: 1;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.raw-toggle:hover {
|
|
649
|
+
color: var(--fg);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.raw-toggle.active {
|
|
653
|
+
opacity: 1;
|
|
654
|
+
color: var(--accent);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.raw-view {
|
|
658
|
+
display: none;
|
|
659
|
+
margin-top: 0.75rem;
|
|
660
|
+
padding: 0.875rem 1rem;
|
|
661
|
+
background: var(--raw-bg);
|
|
662
|
+
border: 1px solid var(--code-border);
|
|
663
|
+
border-radius: 3px;
|
|
664
|
+
font-family: var(--font-mono);
|
|
665
|
+
font-size: 0.625rem;
|
|
666
|
+
white-space: pre-wrap;
|
|
667
|
+
word-wrap: break-word;
|
|
668
|
+
max-height: 20rem;
|
|
669
|
+
overflow-y: auto;
|
|
670
|
+
line-height: 1.5;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.raw-view.visible {
|
|
674
|
+
display: block;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.rendered-view.hidden {
|
|
678
|
+
display: none;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/* ============================================================================
|
|
682
|
+
Scrollbar
|
|
683
|
+
============================================================================ */
|
|
684
|
+
|
|
685
|
+
::-webkit-scrollbar {
|
|
686
|
+
width: 6px;
|
|
687
|
+
height: 6px;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
::-webkit-scrollbar-track {
|
|
691
|
+
background: var(--border-subtle);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
::-webkit-scrollbar-thumb {
|
|
695
|
+
background: var(--muted);
|
|
696
|
+
border-radius: 3px;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
::-webkit-scrollbar-thumb:hover {
|
|
700
|
+
background: var(--fg-secondary);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/* ============================================================================
|
|
704
|
+
Responsive
|
|
705
|
+
============================================================================ */
|
|
706
|
+
|
|
707
|
+
@media (max-width: 640px) {
|
|
708
|
+
html {
|
|
709
|
+
font-size: 14px;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.transcript-container {
|
|
713
|
+
padding: 1.5rem 1rem 3rem;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.transcript-container::before {
|
|
717
|
+
display: none;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
header h1 {
|
|
721
|
+
font-size: 1rem;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.message {
|
|
725
|
+
margin: 1rem 0;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.message.user {
|
|
729
|
+
padding: 0.875rem 1rem;
|
|
730
|
+
margin-left: -1rem;
|
|
731
|
+
margin-right: -1rem;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
pre {
|
|
735
|
+
padding: 0.75rem 1rem;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/* ============================================================================
|
|
740
|
+
Print
|
|
741
|
+
============================================================================ */
|
|
742
|
+
|
|
743
|
+
@media print {
|
|
744
|
+
body {
|
|
745
|
+
background: #fff;
|
|
746
|
+
color: #000;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.transcript-container::before {
|
|
750
|
+
display: none;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.raw-toggle {
|
|
754
|
+
display: none;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
.message {
|
|
758
|
+
break-inside: avoid;
|
|
759
|
+
box-shadow: none;
|
|
760
|
+
border: 1px solid #ccc;
|
|
761
|
+
background: #fff;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
a {
|
|
765
|
+
color: inherit;
|
|
766
|
+
}
|
|
767
|
+
}`;
|
|
768
|
+
|
|
769
|
+
// ============================================================================
|
|
770
|
+
// JavaScript for raw toggle
|
|
771
|
+
// ============================================================================
|
|
772
|
+
|
|
773
|
+
const SCRIPT = `
|
|
774
|
+
(function() {
|
|
775
|
+
document.querySelectorAll('.raw-toggle').forEach(function(btn) {
|
|
776
|
+
btn.addEventListener('click', function() {
|
|
777
|
+
const block = btn.closest('.message, .tool-calls');
|
|
778
|
+
const rawView = block.querySelector('.raw-view');
|
|
779
|
+
const renderedView = block.querySelector('.rendered-view');
|
|
780
|
+
|
|
781
|
+
if (rawView.classList.contains('visible')) {
|
|
782
|
+
rawView.classList.remove('visible');
|
|
783
|
+
renderedView.classList.remove('hidden');
|
|
784
|
+
btn.classList.remove('active');
|
|
785
|
+
} else {
|
|
786
|
+
rawView.classList.add('visible');
|
|
787
|
+
renderedView.classList.add('hidden');
|
|
788
|
+
btn.classList.add('active');
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
});
|
|
792
|
+
})();
|
|
793
|
+
`;
|
|
794
|
+
|
|
795
|
+
// ============================================================================
|
|
796
|
+
// HTML Utilities
|
|
797
|
+
// ============================================================================
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Format JSON for display with indentation.
|
|
801
|
+
*/
|
|
802
|
+
function formatJson(obj: unknown): string {
|
|
803
|
+
try {
|
|
804
|
+
return JSON.stringify(obj, null, 2);
|
|
805
|
+
} catch {
|
|
806
|
+
return String(obj);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Convert markdown content to syntax-highlighted HTML.
|
|
812
|
+
* Uses shiki to highlight markdown, preserving the raw markdown structure
|
|
813
|
+
* while making it visually distinct (code blocks, inline code, etc.).
|
|
814
|
+
*/
|
|
815
|
+
async function contentToHtml(content: string): Promise<string> {
|
|
816
|
+
const hl = await getHighlighter();
|
|
817
|
+
const html = hl.codeToHtml(content, {
|
|
818
|
+
lang: "markdown",
|
|
819
|
+
themes: {
|
|
820
|
+
light: "github-light",
|
|
821
|
+
dark: "ayu-dark",
|
|
822
|
+
},
|
|
823
|
+
defaultColor: false, // Use CSS variables for theme switching
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
return html;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// ============================================================================
|
|
830
|
+
// Message Rendering
|
|
831
|
+
// ============================================================================
|
|
832
|
+
|
|
833
|
+
function renderToolCall(call: ToolCall): string {
|
|
834
|
+
let headerContent = `<span class="tool-call-name">${escapeHtml(call.name)}</span>`;
|
|
835
|
+
if (call.summary) {
|
|
836
|
+
headerContent += `<span class="tool-call-summary">${escapeHtml(call.summary)}</span>`;
|
|
837
|
+
}
|
|
838
|
+
if (call.error) {
|
|
839
|
+
headerContent += `<span class="tool-call-error">${escapeHtml(call.error)}</span>`;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (call.result && call.result.length > 0) {
|
|
843
|
+
// Wrap in details/summary for expandable result
|
|
844
|
+
return `<details class="tool-call">
|
|
845
|
+
<summary class="tool-call-header">${headerContent}</summary>
|
|
846
|
+
<div class="tool-detail-content">${escapeHtml(call.result)}</div>
|
|
847
|
+
</details>`;
|
|
848
|
+
} else {
|
|
849
|
+
// No result, just show the header
|
|
850
|
+
return `<div class="tool-call">
|
|
851
|
+
<div class="tool-call-header">${headerContent}</div>
|
|
852
|
+
</div>`;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function renderRawToggle(): string {
|
|
857
|
+
return `<button class="raw-toggle" title="Toggle raw JSON"></></button>`;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
interface RenderContext {
|
|
861
|
+
showAssistantHeader: boolean;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
async function renderMessage(
|
|
865
|
+
msg: Message,
|
|
866
|
+
ctx: RenderContext,
|
|
867
|
+
): Promise<string> {
|
|
868
|
+
const rawJson = msg.rawJson
|
|
869
|
+
? escapeHtml(formatJson(JSON.parse(msg.rawJson)))
|
|
870
|
+
: "";
|
|
871
|
+
|
|
872
|
+
switch (msg.type) {
|
|
873
|
+
case "user":
|
|
874
|
+
return `
|
|
875
|
+
<div class="message user">
|
|
876
|
+
<div class="message-header">
|
|
877
|
+
<span class="message-label">User</span>
|
|
878
|
+
</div>
|
|
879
|
+
${msg.rawJson ? renderRawToggle() : ""}
|
|
880
|
+
<div class="rendered-view">
|
|
881
|
+
<div class="content">${await contentToHtml(msg.content)}</div>
|
|
882
|
+
</div>
|
|
883
|
+
${msg.rawJson ? `<div class="raw-view">${rawJson}</div>` : ""}
|
|
884
|
+
</div>`;
|
|
885
|
+
|
|
886
|
+
case "assistant": {
|
|
887
|
+
let rendered = "";
|
|
888
|
+
|
|
889
|
+
if (msg.thinking) {
|
|
890
|
+
rendered += `
|
|
891
|
+
<details>
|
|
892
|
+
<summary>thinking...</summary>
|
|
893
|
+
<div class="thinking">${await contentToHtml(msg.thinking)}</div>
|
|
894
|
+
</details>`;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (msg.content.trim()) {
|
|
898
|
+
rendered += `
|
|
899
|
+
<div class="content">${await contentToHtml(msg.content)}</div>`;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const header = ctx.showAssistantHeader
|
|
903
|
+
? `
|
|
904
|
+
<div class="message-header">
|
|
905
|
+
<span class="message-label">Assistant</span>
|
|
906
|
+
</div>`
|
|
907
|
+
: "";
|
|
908
|
+
|
|
909
|
+
return `
|
|
910
|
+
<div class="message assistant">${header}
|
|
911
|
+
${msg.rawJson ? renderRawToggle() : ""}
|
|
912
|
+
<div class="rendered-view">${rendered}
|
|
913
|
+
</div>
|
|
914
|
+
${msg.rawJson ? `<div class="raw-view">${rawJson}</div>` : ""}
|
|
915
|
+
</div>`;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
case "system":
|
|
919
|
+
return `
|
|
920
|
+
<div class="message system">
|
|
921
|
+
<div class="message-header">
|
|
922
|
+
<span class="message-label">System</span>
|
|
923
|
+
</div>
|
|
924
|
+
${msg.rawJson ? renderRawToggle() : ""}
|
|
925
|
+
<div class="rendered-view">
|
|
926
|
+
<div class="content"><pre>${escapeHtml(msg.content)}</pre></div>
|
|
927
|
+
</div>
|
|
928
|
+
${msg.rawJson ? `<div class="raw-view">${rawJson}</div>` : ""}
|
|
929
|
+
</div>`;
|
|
930
|
+
|
|
931
|
+
case "tool_calls": {
|
|
932
|
+
return `
|
|
933
|
+
<div class="tool-calls">
|
|
934
|
+
${msg.rawJson ? renderRawToggle() : ""}
|
|
935
|
+
<div class="rendered-view">
|
|
936
|
+
${msg.calls.map(renderToolCall).join("\n ")}
|
|
937
|
+
</div>
|
|
938
|
+
${msg.rawJson ? `<div class="raw-view">${rawJson}</div>` : ""}
|
|
939
|
+
</div>`;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
case "error":
|
|
943
|
+
return `
|
|
944
|
+
<div class="message error">
|
|
945
|
+
<div class="message-header">
|
|
946
|
+
<span class="message-label">Error</span>
|
|
947
|
+
</div>
|
|
948
|
+
${msg.rawJson ? renderRawToggle() : ""}
|
|
949
|
+
<div class="rendered-view">
|
|
950
|
+
<div class="content"><pre>${escapeHtml(msg.content)}</pre></div>
|
|
951
|
+
</div>
|
|
952
|
+
${msg.rawJson ? `<div class="raw-view">${rawJson}</div>` : ""}
|
|
953
|
+
</div>`;
|
|
954
|
+
|
|
955
|
+
default:
|
|
956
|
+
return "";
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// ============================================================================
|
|
961
|
+
// Main Renderer
|
|
962
|
+
// ============================================================================
|
|
963
|
+
|
|
964
|
+
export interface RenderHtmlOptions {
|
|
965
|
+
head?: string; // render branch ending at this message ID
|
|
966
|
+
title?: string; // page title (used in index linking)
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Render transcript to standalone HTML.
|
|
971
|
+
*/
|
|
972
|
+
export async function renderTranscriptHtml(
|
|
973
|
+
transcript: Transcript,
|
|
974
|
+
options: RenderHtmlOptions = {},
|
|
975
|
+
): Promise<string> {
|
|
976
|
+
const { head, title } = options;
|
|
977
|
+
|
|
978
|
+
const pageTitle = title || `Transcript - ${transcript.source.file}`;
|
|
979
|
+
|
|
980
|
+
// Build header section
|
|
981
|
+
let headerHtml = `
|
|
982
|
+
<header>
|
|
983
|
+
<h1>${escapeHtml(pageTitle)}</h1>
|
|
984
|
+
<div class="meta">
|
|
985
|
+
<span>source: <code>${escapeHtml(transcript.source.file)}</code></span>
|
|
986
|
+
<span>adapter: ${escapeHtml(transcript.source.adapter)}</span>
|
|
987
|
+
</div>`;
|
|
988
|
+
|
|
989
|
+
if (transcript.metadata.warnings.length > 0) {
|
|
990
|
+
headerHtml += `
|
|
991
|
+
<div class="warnings">
|
|
992
|
+
<strong>! warnings</strong>
|
|
993
|
+
<ul>
|
|
994
|
+
${transcript.metadata.warnings.map((w) => `<li>${escapeHtml(w.type)}: ${escapeHtml(w.detail)}</li>`).join("\n ")}
|
|
995
|
+
</ul>
|
|
996
|
+
</div>`;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
headerHtml += "\n</header>";
|
|
1000
|
+
|
|
1001
|
+
// Build messages section
|
|
1002
|
+
let messagesHtml = "";
|
|
1003
|
+
|
|
1004
|
+
if (transcript.messages.length === 0) {
|
|
1005
|
+
messagesHtml = "<p><em>No messages in this transcript.</em></p>";
|
|
1006
|
+
} else {
|
|
1007
|
+
const { bySourceRef, children, parents } = buildTree(transcript.messages);
|
|
1008
|
+
|
|
1009
|
+
let target: string | undefined;
|
|
1010
|
+
if (head) {
|
|
1011
|
+
if (!bySourceRef.has(head)) {
|
|
1012
|
+
messagesHtml = `<p class="error">Message ID <code>${escapeHtml(head)}</code> not found</p>`;
|
|
1013
|
+
} else {
|
|
1014
|
+
target = head;
|
|
1015
|
+
}
|
|
1016
|
+
} else {
|
|
1017
|
+
target = findLatestLeaf(bySourceRef, children);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
if (target) {
|
|
1021
|
+
const path = tracePath(target, parents);
|
|
1022
|
+
const pathSet = new Set(path);
|
|
1023
|
+
let inAssistantTurn = false;
|
|
1024
|
+
|
|
1025
|
+
for (const sourceRef of path) {
|
|
1026
|
+
const msgs = bySourceRef.get(sourceRef);
|
|
1027
|
+
if (!msgs) continue;
|
|
1028
|
+
|
|
1029
|
+
for (const msg of msgs) {
|
|
1030
|
+
// Track when we enter/exit assistant turns
|
|
1031
|
+
const isAssistantContent =
|
|
1032
|
+
msg.type === "assistant" || msg.type === "tool_calls";
|
|
1033
|
+
|
|
1034
|
+
// Show header only at the START of an assistant turn (after user)
|
|
1035
|
+
const showAssistantHeader = isAssistantContent && !inAssistantTurn;
|
|
1036
|
+
|
|
1037
|
+
messagesHtml += await renderMessage(msg, { showAssistantHeader });
|
|
1038
|
+
|
|
1039
|
+
// Update turn state
|
|
1040
|
+
if (msg.type === "user") {
|
|
1041
|
+
inAssistantTurn = false;
|
|
1042
|
+
} else if (isAssistantContent) {
|
|
1043
|
+
inAssistantTurn = true;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// Branch notes
|
|
1048
|
+
if (!head) {
|
|
1049
|
+
const childSet = children.get(sourceRef);
|
|
1050
|
+
if (childSet && childSet.size > 1) {
|
|
1051
|
+
const otherBranches = [...childSet].filter((c) => !pathSet.has(c));
|
|
1052
|
+
if (otherBranches.length > 0) {
|
|
1053
|
+
messagesHtml += `
|
|
1054
|
+
<div class="branch-note">
|
|
1055
|
+
<strong>Other branches:</strong>
|
|
1056
|
+
<ul>
|
|
1057
|
+
${otherBranches
|
|
1058
|
+
.map((branchRef) => {
|
|
1059
|
+
const branchMsgs = bySourceRef.get(branchRef);
|
|
1060
|
+
if (branchMsgs && branchMsgs.length > 0) {
|
|
1061
|
+
const firstLine = getFirstLine(branchMsgs[0]);
|
|
1062
|
+
return `<li><code>${escapeHtml(branchRef)}</code> "${escapeHtml(firstLine)}"</li>`;
|
|
1063
|
+
}
|
|
1064
|
+
return "";
|
|
1065
|
+
})
|
|
1066
|
+
.filter(Boolean)
|
|
1067
|
+
.join("\n ")}
|
|
1068
|
+
</ul>
|
|
1069
|
+
</div>`;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// Assemble full HTML
|
|
1078
|
+
return `<!DOCTYPE html>
|
|
1079
|
+
<html lang="en">
|
|
1080
|
+
<head>
|
|
1081
|
+
<meta charset="UTF-8">
|
|
1082
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1083
|
+
<title>${escapeHtml(pageTitle)}</title>
|
|
1084
|
+
<style>${STYLES}</style>
|
|
1085
|
+
</head>
|
|
1086
|
+
<body>
|
|
1087
|
+
<div class="transcript-container">
|
|
1088
|
+
${headerHtml}
|
|
1089
|
+
<main>
|
|
1090
|
+
${messagesHtml}
|
|
1091
|
+
</main>
|
|
1092
|
+
</div>
|
|
1093
|
+
<script>${SCRIPT}</script>
|
|
1094
|
+
</body>
|
|
1095
|
+
</html>`;
|
|
1096
|
+
}
|