agentgui 1.0.814 → 1.0.816
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 +4 -0
- package/CLAUDE.md +2 -1
- package/package.json +1 -1
- package/static/js/client.js +0 -272
- package/static/js/conv-machine.js +0 -3
- package/static/js/conversations.js +0 -5
- package/static/js/event-filter.js +0 -59
- package/static/js/image-loader.js +0 -35
- package/static/js/script-runner.js +0 -3
- package/static/js/state-barrier.js +0 -4
- package/static/js/streaming-renderer.js +0 -273
- package/static/js/syntax-highlighter.js +0 -55
- package/static/js/ui-components.js +0 -51
- package/static/js/websocket-manager.js +0 -7
- package/static/js/ws-client.js +0 -4
- package/static/js/ws-machine.js +0 -2
- package/static/theme.js +0 -8
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Streaming Renderer Engine
|
|
3
|
-
* Manages real-time event processing, batching, and DOM rendering
|
|
4
|
-
* for Claude Code streaming execution display
|
|
5
|
-
*/
|
|
6
1
|
|
|
7
2
|
class StreamingRenderer {
|
|
8
3
|
constructor(config = {}) {
|
|
9
|
-
// Configuration
|
|
10
4
|
this.config = {
|
|
11
5
|
batchSize: config.batchSize || 50,
|
|
12
6
|
batchInterval: config.batchInterval || 16, // ~60fps
|
|
@@ -17,7 +11,6 @@ class StreamingRenderer {
|
|
|
17
11
|
...config
|
|
18
12
|
};
|
|
19
13
|
|
|
20
|
-
// State
|
|
21
14
|
this.eventQueue = [];
|
|
22
15
|
this.eventHistory = [];
|
|
23
16
|
this.isProcessing = false;
|
|
@@ -34,12 +27,10 @@ class StreamingRenderer {
|
|
|
34
27
|
avgProcessTime: 0
|
|
35
28
|
};
|
|
36
29
|
|
|
37
|
-
// DOM references
|
|
38
30
|
this.outputContainer = null;
|
|
39
31
|
this.scrollContainer = null;
|
|
40
32
|
this.virtualScroller = null;
|
|
41
33
|
|
|
42
|
-
// Event listeners
|
|
43
34
|
this.listeners = {
|
|
44
35
|
'event:queued': [],
|
|
45
36
|
'event:dequeued': [],
|
|
@@ -50,14 +41,10 @@ class StreamingRenderer {
|
|
|
50
41
|
'error:render': []
|
|
51
42
|
};
|
|
52
43
|
|
|
53
|
-
// Performance monitoring
|
|
54
44
|
this.observer = null;
|
|
55
45
|
this.resizeObserver = null;
|
|
56
46
|
}
|
|
57
47
|
|
|
58
|
-
/**
|
|
59
|
-
* Initialize the renderer with DOM elements
|
|
60
|
-
*/
|
|
61
48
|
init(outputContainerId, scrollContainerId = null) {
|
|
62
49
|
this.outputContainer = document.getElementById(outputContainerId);
|
|
63
50
|
this.scrollContainer = scrollContainerId ? document.getElementById(scrollContainerId) : this.outputContainer;
|
|
@@ -71,9 +58,6 @@ class StreamingRenderer {
|
|
|
71
58
|
return this;
|
|
72
59
|
}
|
|
73
60
|
|
|
74
|
-
/**
|
|
75
|
-
* Setup scroll optimization and auto-scroll
|
|
76
|
-
*/
|
|
77
61
|
setupScrollOptimization() {
|
|
78
62
|
if (!this.scrollContainer) return;
|
|
79
63
|
this._userScrolledUp = false;
|
|
@@ -85,23 +69,17 @@ class StreamingRenderer {
|
|
|
85
69
|
});
|
|
86
70
|
}
|
|
87
71
|
|
|
88
|
-
/**
|
|
89
|
-
* Queue an event for batch processing
|
|
90
|
-
*/
|
|
91
72
|
queueEvent(event) {
|
|
92
73
|
if (!event || typeof event !== 'object') return false;
|
|
93
74
|
|
|
94
|
-
// Add timestamp if not present
|
|
95
75
|
if (!event.timestamp) {
|
|
96
76
|
event.timestamp = Date.now();
|
|
97
77
|
}
|
|
98
78
|
|
|
99
|
-
// Deduplication
|
|
100
79
|
if (this.isDuplicate(event)) {
|
|
101
80
|
return false;
|
|
102
81
|
}
|
|
103
82
|
|
|
104
|
-
// Queue size check
|
|
105
83
|
if (this.eventQueue.length >= this.config.maxQueueSize) {
|
|
106
84
|
console.warn('Event queue overflow, dropping oldest events');
|
|
107
85
|
this.eventQueue.shift();
|
|
@@ -110,7 +88,6 @@ class StreamingRenderer {
|
|
|
110
88
|
this.eventQueue.push(event);
|
|
111
89
|
this.eventHistory.push(event);
|
|
112
90
|
|
|
113
|
-
// Trim history
|
|
114
91
|
if (this.eventHistory.length > this.config.maxEventHistory) {
|
|
115
92
|
this.eventHistory.shift();
|
|
116
93
|
}
|
|
@@ -120,18 +97,12 @@ class StreamingRenderer {
|
|
|
120
97
|
return true;
|
|
121
98
|
}
|
|
122
99
|
|
|
123
|
-
/**
|
|
124
|
-
* Check if event is a duplicate
|
|
125
|
-
* For streaming_progress events, use seq+sessionId for precise dedup
|
|
126
|
-
* For other events, use type+id or type+sessionId
|
|
127
|
-
*/
|
|
128
100
|
isDuplicate(event) {
|
|
129
101
|
const key = this.getEventKey(event);
|
|
130
102
|
if (!key) return false;
|
|
131
103
|
|
|
132
104
|
const lastSeq = this.dedupMap.get(key);
|
|
133
105
|
|
|
134
|
-
// For streaming_progress with seq, compare seq numbers directly
|
|
135
106
|
if (event.type === 'streaming_progress' && event.seq !== undefined && lastSeq !== undefined) {
|
|
136
107
|
if (event.seq <= lastSeq) {
|
|
137
108
|
return true; // Same or older seq = duplicate
|
|
@@ -140,7 +111,6 @@ class StreamingRenderer {
|
|
|
140
111
|
return false;
|
|
141
112
|
}
|
|
142
113
|
|
|
143
|
-
// For other events, use time-based dedup
|
|
144
114
|
const now = Date.now();
|
|
145
115
|
if (lastSeq && typeof lastSeq === 'number' && lastSeq > now - 500) {
|
|
146
116
|
return true; // Recent duplicate
|
|
@@ -156,30 +126,20 @@ class StreamingRenderer {
|
|
|
156
126
|
return false;
|
|
157
127
|
}
|
|
158
128
|
|
|
159
|
-
/**
|
|
160
|
-
* Generate deduplication key for event
|
|
161
|
-
* Use sessionId:seq for streaming_progress, fallback to type:id
|
|
162
|
-
*/
|
|
163
129
|
getEventKey(event) {
|
|
164
130
|
if (!event.type) return null;
|
|
165
|
-
// For streaming events, use sessionId as primary key
|
|
166
131
|
if (event.sessionId) {
|
|
167
132
|
return `${event.sessionId}:${event.type}`;
|
|
168
133
|
}
|
|
169
134
|
return `${event.type}:${event.id || ''}`;
|
|
170
135
|
}
|
|
171
136
|
|
|
172
|
-
/**
|
|
173
|
-
* Schedule batch processing
|
|
174
|
-
*/
|
|
175
137
|
scheduleBatchProcess() {
|
|
176
138
|
if (this.isProcessing || this.batchTimer) return;
|
|
177
139
|
|
|
178
140
|
if (this.eventQueue.length >= this.config.batchSize) {
|
|
179
|
-
// Process immediately if batch is full
|
|
180
141
|
this.processBatch();
|
|
181
142
|
} else {
|
|
182
|
-
// Schedule for later
|
|
183
143
|
this.batchTimer = setTimeout(() => {
|
|
184
144
|
this.batchTimer = null;
|
|
185
145
|
if (this.eventQueue.length > 0) {
|
|
@@ -189,9 +149,6 @@ class StreamingRenderer {
|
|
|
189
149
|
}
|
|
190
150
|
}
|
|
191
151
|
|
|
192
|
-
/**
|
|
193
|
-
* Process queued events as a batch
|
|
194
|
-
*/
|
|
195
152
|
processBatch() {
|
|
196
153
|
if (this.isProcessing) return;
|
|
197
154
|
if (this.eventQueue.length === 0) return;
|
|
@@ -204,12 +161,10 @@ class StreamingRenderer {
|
|
|
204
161
|
this.emit('batch:start', { batchSize, queueLength: this.eventQueue.length });
|
|
205
162
|
|
|
206
163
|
try {
|
|
207
|
-
// Process and render batch
|
|
208
164
|
const renderStart = performance.now();
|
|
209
165
|
this.renderBatch(batch);
|
|
210
166
|
const renderTime = performance.now() - renderStart;
|
|
211
167
|
|
|
212
|
-
// Update metrics
|
|
213
168
|
this.performanceMetrics.totalBatches++;
|
|
214
169
|
this.performanceMetrics.totalEvents += batchSize;
|
|
215
170
|
this.performanceMetrics.avgBatchSize = this.performanceMetrics.totalEvents / this.performanceMetrics.totalBatches;
|
|
@@ -221,7 +176,6 @@ class StreamingRenderer {
|
|
|
221
176
|
metrics: this.performanceMetrics
|
|
222
177
|
});
|
|
223
178
|
|
|
224
|
-
// Process more if queue is still full
|
|
225
179
|
if (this.eventQueue.length >= this.config.batchSize) {
|
|
226
180
|
this.isProcessing = false;
|
|
227
181
|
setImmediate(() => this.processBatch());
|
|
@@ -241,9 +195,6 @@ class StreamingRenderer {
|
|
|
241
195
|
this.performanceMetrics.avgProcessTime = this.performanceMetrics.avgProcessTime || processTime;
|
|
242
196
|
}
|
|
243
197
|
|
|
244
|
-
/**
|
|
245
|
-
* Render a batch of events
|
|
246
|
-
*/
|
|
247
198
|
renderBatch(batch) {
|
|
248
199
|
if (!this.outputContainer) return;
|
|
249
200
|
|
|
@@ -251,7 +202,6 @@ class StreamingRenderer {
|
|
|
251
202
|
const renderStart = performance.now();
|
|
252
203
|
|
|
253
204
|
try {
|
|
254
|
-
// Create document fragment for batch
|
|
255
205
|
const fragment = document.createDocumentFragment();
|
|
256
206
|
let nodeCount = 0;
|
|
257
207
|
|
|
@@ -267,16 +217,13 @@ class StreamingRenderer {
|
|
|
267
217
|
}
|
|
268
218
|
}
|
|
269
219
|
|
|
270
|
-
// Append all at once (minimizes reflows)
|
|
271
220
|
if (nodeCount > 0) {
|
|
272
221
|
this.outputContainer.appendChild(fragment);
|
|
273
222
|
this.domNodeCount += nodeCount;
|
|
274
223
|
|
|
275
|
-
// Nest tool result blocks inside their corresponding tool use blocks
|
|
276
224
|
this.nestToolResultsInToolUses();
|
|
277
225
|
}
|
|
278
226
|
|
|
279
|
-
// Auto-scroll to bottom
|
|
280
227
|
this.autoScroll();
|
|
281
228
|
|
|
282
229
|
const renderTime = performance.now() - renderStart;
|
|
@@ -293,14 +240,10 @@ class StreamingRenderer {
|
|
|
293
240
|
}
|
|
294
241
|
}
|
|
295
242
|
|
|
296
|
-
/**
|
|
297
|
-
* Render a single event to DOM element
|
|
298
|
-
*/
|
|
299
243
|
renderEvent(event) {
|
|
300
244
|
if (!event.type) return null;
|
|
301
245
|
|
|
302
246
|
try {
|
|
303
|
-
// Handle block rendering from streaming_progress events
|
|
304
247
|
if (event.type === 'streaming_progress' && event.block) {
|
|
305
248
|
return this.renderBlock(event.block, event);
|
|
306
249
|
}
|
|
@@ -331,8 +274,6 @@ class StreamingRenderer {
|
|
|
331
274
|
case 'code_block':
|
|
332
275
|
return this.renderCode(event);
|
|
333
276
|
case 'thinking_block':
|
|
334
|
-
// Thinking blocks are now rendered immediately via handleStreamingProgress
|
|
335
|
-
// Don't render them here to avoid duplicates
|
|
336
277
|
return null;
|
|
337
278
|
case 'tool_use':
|
|
338
279
|
return this.renderToolUse(event);
|
|
@@ -355,9 +296,6 @@ class StreamingRenderer {
|
|
|
355
296
|
}
|
|
356
297
|
}
|
|
357
298
|
|
|
358
|
-
/**
|
|
359
|
-
* Render Claude message blocks with beautiful styling
|
|
360
|
-
*/
|
|
361
299
|
renderBlock(block, context = {}, targetContainer = null) {
|
|
362
300
|
if (!block || !block.type) return null;
|
|
363
301
|
|
|
@@ -400,9 +338,6 @@ class StreamingRenderer {
|
|
|
400
338
|
}
|
|
401
339
|
}
|
|
402
340
|
|
|
403
|
-
/**
|
|
404
|
-
* Render text block with semantic HTML
|
|
405
|
-
*/
|
|
406
341
|
renderBlockText(block, context, targetContainer = null) {
|
|
407
342
|
const text = block.text || '';
|
|
408
343
|
const isHtml = this.containsHtmlTags(text);
|
|
@@ -457,9 +392,6 @@ class StreamingRenderer {
|
|
|
457
392
|
return cleaned;
|
|
458
393
|
}
|
|
459
394
|
|
|
460
|
-
/**
|
|
461
|
-
* Parse markdown and render links, code, bold, italic
|
|
462
|
-
*/
|
|
463
395
|
parseAndRenderMarkdown(text) {
|
|
464
396
|
const esc = this.escapeHtml.bind(this);
|
|
465
397
|
const lines = text.split('\n');
|
|
@@ -525,9 +457,6 @@ class StreamingRenderer {
|
|
|
525
457
|
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" style="text-decoration:underline;opacity:0.85" target="_blank">$1</a>');
|
|
526
458
|
}
|
|
527
459
|
|
|
528
|
-
/**
|
|
529
|
-
* Render code block with syntax highlighting
|
|
530
|
-
*/
|
|
531
460
|
renderBlockCode(block, context) {
|
|
532
461
|
const div = document.createElement('div');
|
|
533
462
|
div.className = 'block-code';
|
|
@@ -567,9 +496,6 @@ class StreamingRenderer {
|
|
|
567
496
|
return div;
|
|
568
497
|
}
|
|
569
498
|
|
|
570
|
-
/**
|
|
571
|
-
* Render thinking block (expandable), signature-aware
|
|
572
|
-
*/
|
|
573
499
|
renderBlockThinking(block, context) {
|
|
574
500
|
const thinking = block.thinking || '';
|
|
575
501
|
const hasSignature = !!block.signature;
|
|
@@ -596,9 +522,6 @@ class StreamingRenderer {
|
|
|
596
522
|
return div;
|
|
597
523
|
}
|
|
598
524
|
|
|
599
|
-
/**
|
|
600
|
-
* Get a tool-specific icon SVG string
|
|
601
|
-
*/
|
|
602
525
|
getToolIcon(toolName) {
|
|
603
526
|
const icons = {
|
|
604
527
|
Read: '<svg viewBox="0 0 20 20" fill="currentColor"><path d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z"/></svg>',
|
|
@@ -616,9 +539,6 @@ class StreamingRenderer {
|
|
|
616
539
|
return icons[toolName] || '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10.666a1 1 0 11-1.64-1.118L9.687 10H5a1 1 0 01-.82-1.573l7-10.666a1 1 0 011.12-.373z" clip-rule="evenodd"/></svg>';
|
|
617
540
|
}
|
|
618
541
|
|
|
619
|
-
/**
|
|
620
|
-
* Render a file path with icon, directory breadcrumb, and filename
|
|
621
|
-
*/
|
|
622
542
|
renderFilePath(filePath) {
|
|
623
543
|
if (!filePath) return '';
|
|
624
544
|
const parts = pathSplit(filePath);
|
|
@@ -627,9 +547,6 @@ class StreamingRenderer {
|
|
|
627
547
|
return `<div class="tool-param-file"><span class="file-icon">📄</span>${dir ? `<span class="file-dir">${this.escapeHtml(dir)}/</span>` : ''}<span class="file-name">${this.escapeHtml(fileName)}</span></div>`;
|
|
628
548
|
}
|
|
629
549
|
|
|
630
|
-
/**
|
|
631
|
-
* Render smart tool parameters based on tool type
|
|
632
|
-
*/
|
|
633
550
|
renderSmartParams(toolName, input) {
|
|
634
551
|
if (!input || Object.keys(input).length === 0) return '';
|
|
635
552
|
|
|
@@ -707,7 +624,6 @@ class StreamingRenderer {
|
|
|
707
624
|
html += `<div style="margin-bottom:0.5rem;font-size:0.75rem;color:var(--color-text-secondary)"><span style="opacity:0.7">⏱️</span> Timeout: ${Math.round(input.timeout / 1000)}s</div>`;
|
|
708
625
|
}
|
|
709
626
|
|
|
710
|
-
// Render code with syntax highlighting
|
|
711
627
|
if (input.code) {
|
|
712
628
|
const codeLines = input.code.split('\n');
|
|
713
629
|
const lineCount = codeLines.length;
|
|
@@ -717,7 +633,6 @@ class StreamingRenderer {
|
|
|
717
633
|
html += `<div style="margin-top:0.5rem"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.25rem"><span style="font-size:0.7rem;font-weight:600;color:#0891b2;text-transform:uppercase">${this.escapeHtml(lang)}</span><span style="font-size:0.7rem;color:var(--color-text-secondary)">${lineCount} lines</span></div>${StreamingRenderer.renderCodeWithHighlight(displayCode, this.escapeHtml.bind(this), true)}${truncated ? `<div style="font-size:0.7rem;color:var(--color-text-secondary);text-align:center;padding:0.25rem">... ${lineCount - 50} more lines</div>` : ''}</div>`;
|
|
718
634
|
}
|
|
719
635
|
|
|
720
|
-
// Render commands (bash commands)
|
|
721
636
|
if (input.commands) {
|
|
722
637
|
const cmds = Array.isArray(input.commands) ? input.commands : [input.commands];
|
|
723
638
|
cmds.forEach(cmd => {
|
|
@@ -734,9 +649,6 @@ class StreamingRenderer {
|
|
|
734
649
|
}
|
|
735
650
|
}
|
|
736
651
|
|
|
737
|
-
/**
|
|
738
|
-
* Render content preview with truncation
|
|
739
|
-
*/
|
|
740
652
|
renderContentPreview(content, label) {
|
|
741
653
|
const maxLen = 500;
|
|
742
654
|
const truncated = content.length > maxLen;
|
|
@@ -748,16 +660,10 @@ class StreamingRenderer {
|
|
|
748
660
|
return `<div class="tool-param-content-preview" style="margin-top:0.5rem"><div class="preview-header"><span>${this.escapeHtml(label)}</span><span style="font-weight:400">${lineCount} lines${truncated ? ' (truncated)' : ''}</span></div>${codeBody}${truncated ? '<div class="preview-truncated">... ' + (content.length - maxLen) + ' more characters</div>' : ''}</div>`;
|
|
749
661
|
}
|
|
750
662
|
|
|
751
|
-
/**
|
|
752
|
-
* Render params as formatted JSON (default fallback for unknown tools)
|
|
753
|
-
*/
|
|
754
663
|
renderJsonParams(input) {
|
|
755
664
|
return `<div class="tool-params">${this.renderParametersBeautiful(input)}</div>`;
|
|
756
665
|
}
|
|
757
666
|
|
|
758
|
-
/**
|
|
759
|
-
* Render tool use block with smart parameter display
|
|
760
|
-
*/
|
|
761
667
|
getToolUseTitle(toolName, input) {
|
|
762
668
|
const normalizedName = toolName.replace(/^mcp__.*?__/, '');
|
|
763
669
|
if (normalizedName === 'Edit' && input.file_path) {
|
|
@@ -834,9 +740,6 @@ class StreamingRenderer {
|
|
|
834
740
|
return details;
|
|
835
741
|
}
|
|
836
742
|
|
|
837
|
-
/**
|
|
838
|
-
* Render content smartly - detect JSON, images, file lists, markdown
|
|
839
|
-
*/
|
|
840
743
|
renderSmartContent(contentStr) {
|
|
841
744
|
const trimmed = contentStr.trim();
|
|
842
745
|
|
|
@@ -874,9 +777,6 @@ class StreamingRenderer {
|
|
|
874
777
|
return `<div style="padding:0.625rem 1rem;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem;white-space:pre-wrap;word-break:break-all;line-height:1.5">${this.escapeHtml(trimmed)}</div>`;
|
|
875
778
|
}
|
|
876
779
|
|
|
877
|
-
/**
|
|
878
|
-
* Render parsed JSON/object as formatted key-value display
|
|
879
|
-
*/
|
|
880
780
|
renderParametersBeautiful(data, depth = 0) {
|
|
881
781
|
if (data === null || data === undefined) return `<span style="color:var(--color-text-secondary);font-style:italic">null</span>`;
|
|
882
782
|
if (typeof data === 'boolean') return `<span style="color:#d97706;font-weight:600">${data}</span>`;
|
|
@@ -900,7 +800,6 @@ class StreamingRenderer {
|
|
|
900
800
|
if (Array.isArray(data)) {
|
|
901
801
|
if (data.length === 0) return `<span style="color:var(--color-text-secondary)">[]</span>`;
|
|
902
802
|
if (data.every(i => typeof i === 'string') && data.length <= 20) {
|
|
903
|
-
// Render as an itemized list instead of inline badges
|
|
904
803
|
return `<div style="display:flex;flex-direction:column;gap:0.125rem;${depth > 0 ? 'padding-left:1rem' : ''}">${data.map((i, idx) => `<div style="display:flex;align-items:center;gap:0.375rem"><span style="color:var(--color-text-secondary);font-size:0.65rem;opacity:0.5">•</span><span style="font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem">${this.escapeHtml(i)}</span></div>`).join('')}</div>`;
|
|
905
804
|
}
|
|
906
805
|
return `<div style="display:flex;flex-direction:column;gap:0.25rem;${depth > 0 ? 'padding-left:1rem' : ''}">${data.map((item, i) => `<div style="display:flex;gap:0.5rem;align-items:flex-start"><span style="color:var(--color-text-secondary);font-size:0.7rem;min-width:1.5rem;text-align:right;flex-shrink:0">${i}</span><div style="flex:1;min-width:0">${this.renderParametersBeautiful(item, depth + 1)}</div></div>`).join('')}</div>`;
|
|
@@ -915,9 +814,6 @@ class StreamingRenderer {
|
|
|
915
814
|
return `<span>${this.escapeHtml(String(data))}</span>`;
|
|
916
815
|
}
|
|
917
816
|
|
|
918
|
-
/**
|
|
919
|
-
* Static HTML version of smart content rendering for use in string templates
|
|
920
|
-
*/
|
|
921
817
|
static renderSmartContentHTML(contentStr, escapeHtml, flat = false) {
|
|
922
818
|
const trimmed = contentStr.trim();
|
|
923
819
|
const esc = escapeHtml || window._escHtml;
|
|
@@ -926,12 +822,10 @@ class StreamingRenderer {
|
|
|
926
822
|
return `<div style="padding:0.5rem"><img src="${esc(trimmed)}" style="max-width:100%;max-height:24rem;border-radius:0.375rem" loading="lazy"></div>`;
|
|
927
823
|
}
|
|
928
824
|
|
|
929
|
-
// Parse JSON and render as structured content
|
|
930
825
|
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
931
826
|
try {
|
|
932
827
|
const parsed = JSON.parse(trimmed);
|
|
933
828
|
|
|
934
|
-
// Handle Claude content block arrays: [{type:"text", text:"..."}]
|
|
935
829
|
if (Array.isArray(parsed) && parsed.length > 0 && parsed[0] && parsed[0].type === 'text') {
|
|
936
830
|
const textParts = parsed.filter(b => b.type === 'text' && b.text);
|
|
937
831
|
if (textParts.length > 0) {
|
|
@@ -940,7 +834,6 @@ class StreamingRenderer {
|
|
|
940
834
|
}
|
|
941
835
|
}
|
|
942
836
|
|
|
943
|
-
// Handle Claude image content block arrays: [{type:"image", source:{type:"base64", data:"...", media_type:"..."}}]
|
|
944
837
|
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
945
838
|
const imgParts = parsed.filter(b => b.type === 'image' && b.source && b.source.type === 'base64' && b.source.data);
|
|
946
839
|
if (imgParts.length > 0) {
|
|
@@ -951,36 +844,27 @@ class StreamingRenderer {
|
|
|
951
844
|
}
|
|
952
845
|
}
|
|
953
846
|
|
|
954
|
-
// For other JSON, render as itemized key-value structure
|
|
955
847
|
return `<div style="padding:0.5rem 0.75rem">${StreamingRenderer.renderParamsHTML(parsed, 0, esc)}</div>`;
|
|
956
848
|
} catch (e) {
|
|
957
|
-
// Not valid JSON, might be code with braces
|
|
958
849
|
}
|
|
959
850
|
}
|
|
960
851
|
|
|
961
|
-
// Check if this looks like `cat -n` output or grep with line numbers
|
|
962
852
|
const lines = trimmed.split('\n');
|
|
963
853
|
const isCatNOutput = lines.length > 1 && lines[0].match(/^\s*\d+→/);
|
|
964
854
|
const isGrepOutput = lines.length > 1 && lines[0].match(/^\s*\d+-/);
|
|
965
855
|
|
|
966
856
|
if (isCatNOutput || isGrepOutput) {
|
|
967
|
-
// Strip line numbers and arrows/hyphens from output
|
|
968
857
|
const cleanedLines = lines.map(line => {
|
|
969
|
-
// Skip grep context separator lines
|
|
970
858
|
if (line === '--') return null;
|
|
971
859
|
|
|
972
|
-
// Handle both cat -n (→) and grep (-n) formats
|
|
973
|
-
// Also handle grep with colon (:) for matching lines
|
|
974
860
|
const match = line.match(/^\s*\d+[→\-:](.*)/);
|
|
975
861
|
return match ? match[1] : line;
|
|
976
862
|
}).filter(line => line !== null);
|
|
977
863
|
const cleanedContent = cleanedLines.join('\n');
|
|
978
864
|
|
|
979
|
-
// Try to detect and highlight code based on content patterns
|
|
980
865
|
return StreamingRenderer.renderCodeWithHighlight(cleanedContent, esc, flat);
|
|
981
866
|
}
|
|
982
867
|
|
|
983
|
-
// Check for system reminder tags and format them specially
|
|
984
868
|
const systemReminderPattern = /<system-reminder>([\s\S]*?)<\/system-reminder>/g;
|
|
985
869
|
const systemReminders = [];
|
|
986
870
|
let contentWithoutReminders = trimmed;
|
|
@@ -991,10 +875,8 @@ class StreamingRenderer {
|
|
|
991
875
|
contentWithoutReminders = contentWithoutReminders.replace(reminderMatch[0], '');
|
|
992
876
|
}
|
|
993
877
|
|
|
994
|
-
// Clean up the content after removing reminders
|
|
995
878
|
contentWithoutReminders = contentWithoutReminders.trim();
|
|
996
879
|
|
|
997
|
-
// Check if this looks like a tool success message with formatted output
|
|
998
880
|
const successPatterns = [
|
|
999
881
|
/^Success\s+toolu_[\w]+$/m,
|
|
1000
882
|
/^The file .* has been (updated|created|modified)/,
|
|
@@ -1010,12 +892,10 @@ class StreamingRenderer {
|
|
|
1010
892
|
let successEndIndex = -1;
|
|
1011
893
|
let codeStartIndex = -1;
|
|
1012
894
|
|
|
1013
|
-
// Find the success message and where code starts
|
|
1014
895
|
for (let i = 0; i < contentLines.length; i++) {
|
|
1015
896
|
const line = contentLines[i];
|
|
1016
897
|
if (line.match(/^Success\s+toolu_/)) {
|
|
1017
898
|
successEndIndex = i;
|
|
1018
|
-
// Look for the next non-empty line that contains code
|
|
1019
899
|
for (let j = i + 1; j < contentLines.length; j++) {
|
|
1020
900
|
if (contentLines[j].trim() && !contentLines[j].match(/^The file|^Here's the result/)) {
|
|
1021
901
|
codeStartIndex = j;
|
|
@@ -1024,11 +904,8 @@ class StreamingRenderer {
|
|
|
1024
904
|
}
|
|
1025
905
|
break;
|
|
1026
906
|
} else if (line.match(/^The file .* has been|^Applied \d+ edits? to|^Replaced|^Created|^Deleted/)) {
|
|
1027
|
-
// For edit/write operations, code typically starts after the success message
|
|
1028
|
-
// Look for "Here's the result" line or line numbers
|
|
1029
907
|
for (let j = i + 1; j < contentLines.length; j++) {
|
|
1030
908
|
if (contentLines[j].match(/^Here's the result|^\s*\d+→/)) {
|
|
1031
|
-
// If it's "Here's the result", code starts on next line
|
|
1032
909
|
if (contentLines[j].match(/^Here's the result/)) {
|
|
1033
910
|
codeStartIndex = j + 1;
|
|
1034
911
|
} else {
|
|
@@ -1036,13 +913,11 @@ class StreamingRenderer {
|
|
|
1036
913
|
}
|
|
1037
914
|
break;
|
|
1038
915
|
} else if (contentLines[j].trim() && !contentLines[j].match(/^cat -n|^Running/)) {
|
|
1039
|
-
// If we find non-empty content that's not a command, assume it's code
|
|
1040
916
|
codeStartIndex = j;
|
|
1041
917
|
break;
|
|
1042
918
|
}
|
|
1043
919
|
}
|
|
1044
920
|
if (codeStartIndex === -1) {
|
|
1045
|
-
// No line numbers found, treat next content as code
|
|
1046
921
|
codeStartIndex = i + 2;
|
|
1047
922
|
}
|
|
1048
923
|
successEndIndex = codeStartIndex - 1;
|
|
@@ -1054,7 +929,6 @@ class StreamingRenderer {
|
|
|
1054
929
|
const beforeCode = contentLines.slice(0, codeStartIndex).join('\n');
|
|
1055
930
|
let codeContent = contentLines.slice(codeStartIndex).join('\n');
|
|
1056
931
|
|
|
1057
|
-
// Check if code has line numbers and strip them
|
|
1058
932
|
if (codeContent.match(/^\s*\d+→/m)) {
|
|
1059
933
|
const codeLines = codeContent.split('\n');
|
|
1060
934
|
codeContent = codeLines.map(line => {
|
|
@@ -1063,20 +937,16 @@ class StreamingRenderer {
|
|
|
1063
937
|
}).join('\n');
|
|
1064
938
|
}
|
|
1065
939
|
|
|
1066
|
-
// Build the formatted output
|
|
1067
940
|
let html = '';
|
|
1068
941
|
|
|
1069
|
-
// Add success message
|
|
1070
942
|
if (beforeCode.trim()) {
|
|
1071
943
|
html += `<div style="color:var(--color-success);font-weight:600;margin-bottom:0.75rem;font-size:0.9rem">${esc(beforeCode.trim())}</div>`;
|
|
1072
944
|
}
|
|
1073
945
|
|
|
1074
|
-
// Add highlighted code
|
|
1075
946
|
if (codeContent.trim()) {
|
|
1076
947
|
html += StreamingRenderer.renderCodeWithHighlight(codeContent, esc, flat);
|
|
1077
948
|
}
|
|
1078
949
|
|
|
1079
|
-
// Add system reminders if any
|
|
1080
950
|
if (systemReminders.length > 0) {
|
|
1081
951
|
html += StreamingRenderer.renderSystemReminders(systemReminders, esc);
|
|
1082
952
|
}
|
|
@@ -1085,13 +955,10 @@ class StreamingRenderer {
|
|
|
1085
955
|
}
|
|
1086
956
|
}
|
|
1087
957
|
|
|
1088
|
-
// If there are system reminders but no success pattern, render them separately
|
|
1089
958
|
if (systemReminders.length > 0) {
|
|
1090
959
|
let html = '';
|
|
1091
960
|
|
|
1092
|
-
// Render the main content
|
|
1093
961
|
if (contentWithoutReminders) {
|
|
1094
|
-
// Check if remaining content looks like code
|
|
1095
962
|
if (StreamingRenderer.detectCodeContent(contentWithoutReminders)) {
|
|
1096
963
|
html += StreamingRenderer.renderCodeWithHighlight(contentWithoutReminders, esc, flat);
|
|
1097
964
|
} else {
|
|
@@ -1099,7 +966,6 @@ class StreamingRenderer {
|
|
|
1099
966
|
}
|
|
1100
967
|
}
|
|
1101
968
|
|
|
1102
|
-
// Add system reminders
|
|
1103
969
|
html += StreamingRenderer.renderSystemReminders(systemReminders, esc);
|
|
1104
970
|
return html;
|
|
1105
971
|
}
|
|
@@ -1119,7 +985,6 @@ class StreamingRenderer {
|
|
|
1119
985
|
return `<div style="padding:0.625rem 1rem">${fileHtml}</div>`;
|
|
1120
986
|
}
|
|
1121
987
|
|
|
1122
|
-
// Check if this looks like code
|
|
1123
988
|
const looksLikeCode = StreamingRenderer.detectCodeContent(trimmed);
|
|
1124
989
|
if (looksLikeCode) {
|
|
1125
990
|
return StreamingRenderer.renderCodeWithHighlight(trimmed, esc, flat);
|
|
@@ -1129,17 +994,12 @@ class StreamingRenderer {
|
|
|
1129
994
|
return `<pre class="tool-result-pre">${esc(displayContent)}</pre>`;
|
|
1130
995
|
}
|
|
1131
996
|
|
|
1132
|
-
/**
|
|
1133
|
-
* Render system reminders in a clean, formatted way
|
|
1134
|
-
*/
|
|
1135
997
|
static renderSystemReminders(reminders, esc) {
|
|
1136
998
|
if (!reminders || reminders.length === 0) return '';
|
|
1137
999
|
|
|
1138
1000
|
const reminderHtml = reminders.map(reminder => {
|
|
1139
|
-
// Parse reminder content for better formatting
|
|
1140
1001
|
const lines = reminder.split('\n').filter(l => l.trim());
|
|
1141
1002
|
const formattedLines = lines.map(line => {
|
|
1142
|
-
// Make key points stand out
|
|
1143
1003
|
if (line.includes('IMPORTANT:') || line.includes('WARNING:')) {
|
|
1144
1004
|
return `<div style="font-weight:600;color:var(--color-warning);margin:0.25rem 0">${esc(line)}</div>`;
|
|
1145
1005
|
}
|
|
@@ -1160,11 +1020,7 @@ class StreamingRenderer {
|
|
|
1160
1020
|
`;
|
|
1161
1021
|
}
|
|
1162
1022
|
|
|
1163
|
-
/**
|
|
1164
|
-
* Detect if content looks like code
|
|
1165
|
-
*/
|
|
1166
1023
|
static detectCodeContent(content) {
|
|
1167
|
-
// Common code patterns
|
|
1168
1024
|
const codePatterns = [
|
|
1169
1025
|
/^\s*(function|const|let|var|class|import|export|async|await)/m, // JavaScript
|
|
1170
1026
|
/^\s*(def|class|import|from|if __name__|lambda|async def)/m, // Python
|
|
@@ -1178,9 +1034,6 @@ class StreamingRenderer {
|
|
|
1178
1034
|
return codePatterns.some(pattern => pattern.test(content));
|
|
1179
1035
|
}
|
|
1180
1036
|
|
|
1181
|
-
/**
|
|
1182
|
-
* Render code with basic syntax highlighting
|
|
1183
|
-
*/
|
|
1184
1037
|
static renderCodeWithHighlight(code, esc, flat = false) {
|
|
1185
1038
|
const preStyle = "background:var(--color-bg-code);padding:1rem;border-radius:0.375rem;overflow-x:auto;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.875rem;line-height:1.6;color:var(--color-code-text);border:1px solid var(--color-code-border);margin:0";
|
|
1186
1039
|
const codeHtml = `<pre style="${preStyle}"><code class="lazy-hl">${esc(code)}</code></pre>`;
|
|
@@ -1239,9 +1092,6 @@ class StreamingRenderer {
|
|
|
1239
1092
|
return '';
|
|
1240
1093
|
}
|
|
1241
1094
|
|
|
1242
|
-
/**
|
|
1243
|
-
* Static HTML version of parameter rendering
|
|
1244
|
-
*/
|
|
1245
1095
|
static renderParamsHTML(data, depth, esc) {
|
|
1246
1096
|
if (data === null || data === undefined) return `<span style="color:var(--color-text-secondary);font-style:italic">null</span>`;
|
|
1247
1097
|
if (typeof data === 'boolean') return `<span style="color:#d97706;font-weight:600">${data}</span>`;
|
|
@@ -1269,7 +1119,6 @@ class StreamingRenderer {
|
|
|
1269
1119
|
if (Array.isArray(data)) {
|
|
1270
1120
|
if (data.length === 0) return `<span style="color:var(--color-text-secondary)">[]</span>`;
|
|
1271
1121
|
if (data.every(i => typeof i === 'string') && data.length <= 20) {
|
|
1272
|
-
// Render as an itemized list instead of inline badges
|
|
1273
1122
|
return `<div style="display:flex;flex-direction:column;gap:0.125rem;${depth > 0 ? 'padding-left:1rem' : ''}">${data.map((i, idx) => `<div style="display:flex;align-items:center;gap:0.375rem"><span style="color:var(--color-text-secondary);font-size:0.65rem;opacity:0.5">•</span><span style="font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem">${esc(i)}</span></div>`).join('')}</div>`;
|
|
1274
1123
|
}
|
|
1275
1124
|
return `<div style="display:flex;flex-direction:column;gap:0.25rem;${depth > 0 ? 'padding-left:1rem' : ''}">${data.map((item, i) => `<div style="display:flex;gap:0.5rem;align-items:flex-start"><span style="color:var(--color-text-secondary);font-size:0.7rem;min-width:1.5rem;text-align:right;flex-shrink:0">${i}</span><div style="flex:1;min-width:0">${StreamingRenderer.renderParamsHTML(item, depth + 1, esc)}</div></div>`).join('')}</div>`;
|
|
@@ -1284,9 +1133,6 @@ class StreamingRenderer {
|
|
|
1284
1133
|
return `<span>${esc(String(data))}</span>`;
|
|
1285
1134
|
}
|
|
1286
1135
|
|
|
1287
|
-
/**
|
|
1288
|
-
* Render tool result as inline content to be merged into preceding tool_use block
|
|
1289
|
-
*/
|
|
1290
1136
|
renderBlockToolResult(block, context) {
|
|
1291
1137
|
const content = block.content || '';
|
|
1292
1138
|
const toolName = block.tool_name || block.name || '';
|
|
@@ -1311,9 +1157,6 @@ class StreamingRenderer {
|
|
|
1311
1157
|
return container;
|
|
1312
1158
|
}
|
|
1313
1159
|
|
|
1314
|
-
/**
|
|
1315
|
-
* Render image block
|
|
1316
|
-
*/
|
|
1317
1160
|
renderBlockImage(block, context) {
|
|
1318
1161
|
const div = document.createElement('div');
|
|
1319
1162
|
div.className = 'block-image';
|
|
@@ -1322,7 +1165,6 @@ class StreamingRenderer {
|
|
|
1322
1165
|
let src = block.image || block.src || '';
|
|
1323
1166
|
const alt = block.alt || 'Image';
|
|
1324
1167
|
|
|
1325
|
-
// Handle base64 data
|
|
1326
1168
|
if (block.data && block.media_type) {
|
|
1327
1169
|
src = `data:${block.media_type};base64,${block.data}`;
|
|
1328
1170
|
}
|
|
@@ -1335,9 +1177,6 @@ class StreamingRenderer {
|
|
|
1335
1177
|
return div;
|
|
1336
1178
|
}
|
|
1337
1179
|
|
|
1338
|
-
/**
|
|
1339
|
-
* Render bash command block
|
|
1340
|
-
*/
|
|
1341
1180
|
renderBlockBash(block, context) {
|
|
1342
1181
|
const div = document.createElement('div');
|
|
1343
1182
|
div.className = 'block-bash';
|
|
@@ -1346,10 +1185,8 @@ class StreamingRenderer {
|
|
|
1346
1185
|
const command = block.command || block.code || '';
|
|
1347
1186
|
const output = block.output || '';
|
|
1348
1187
|
|
|
1349
|
-
// For the command, use simple escaping
|
|
1350
1188
|
let html = `<div class="bash-command"><span class="prompt">$</span><code>${this.escapeHtml(command)}</code></div>`;
|
|
1351
1189
|
|
|
1352
|
-
// For output, check if it looks like code and use syntax highlighting
|
|
1353
1190
|
if (output) {
|
|
1354
1191
|
if (StreamingRenderer.detectCodeContent(output)) {
|
|
1355
1192
|
html += StreamingRenderer.renderCodeWithHighlight(output, this.escapeHtml.bind(this), true);
|
|
@@ -1362,9 +1199,6 @@ class StreamingRenderer {
|
|
|
1362
1199
|
return div;
|
|
1363
1200
|
}
|
|
1364
1201
|
|
|
1365
|
-
/**
|
|
1366
|
-
* Render system event
|
|
1367
|
-
*/
|
|
1368
1202
|
renderBlockSystem(block, context) {
|
|
1369
1203
|
if (block.subtype === 'compact_boundary') return this.renderCompactBoundary(block);
|
|
1370
1204
|
if (block.subtype === 'turn_duration') return this.renderTurnDuration(block);
|
|
@@ -1396,9 +1230,6 @@ class StreamingRenderer {
|
|
|
1396
1230
|
return details;
|
|
1397
1231
|
}
|
|
1398
1232
|
|
|
1399
|
-
/**
|
|
1400
|
-
* Render result block (execution summary)
|
|
1401
|
-
*/
|
|
1402
1233
|
renderBlockResult(block, context) {
|
|
1403
1234
|
const isError = block.is_error || false;
|
|
1404
1235
|
const duration = block.duration_ms ? (block.duration_ms / 1000).toFixed(1) + 's' : '';
|
|
@@ -1447,9 +1278,6 @@ class StreamingRenderer {
|
|
|
1447
1278
|
return details;
|
|
1448
1279
|
}
|
|
1449
1280
|
|
|
1450
|
-
/**
|
|
1451
|
-
* Render tool status block (ACP in_progress/pending updates)
|
|
1452
|
-
*/
|
|
1453
1281
|
renderBlockToolStatus(block, context) {
|
|
1454
1282
|
const status = block.status || 'pending';
|
|
1455
1283
|
const statusIcons = {
|
|
@@ -1491,9 +1319,6 @@ class StreamingRenderer {
|
|
|
1491
1319
|
return div;
|
|
1492
1320
|
}
|
|
1493
1321
|
|
|
1494
|
-
/**
|
|
1495
|
-
* Render usage block (ACP usage updates)
|
|
1496
|
-
*/
|
|
1497
1322
|
renderBlockUsage(block, context) {
|
|
1498
1323
|
const usage = block.usage || {};
|
|
1499
1324
|
const used = usage.used || 0;
|
|
@@ -1513,9 +1338,6 @@ class StreamingRenderer {
|
|
|
1513
1338
|
return div;
|
|
1514
1339
|
}
|
|
1515
1340
|
|
|
1516
|
-
/**
|
|
1517
|
-
* Render plan block (ACP plan updates)
|
|
1518
|
-
*/
|
|
1519
1341
|
renderBlockPlan(block, context) {
|
|
1520
1342
|
const entries = block.entries || [];
|
|
1521
1343
|
if (entries.length === 0) return null;
|
|
@@ -1573,15 +1395,11 @@ class StreamingRenderer {
|
|
|
1573
1395
|
return div;
|
|
1574
1396
|
}
|
|
1575
1397
|
|
|
1576
|
-
/**
|
|
1577
|
-
* Render generic block with formatted key-value pairs
|
|
1578
|
-
*/
|
|
1579
1398
|
renderBlockGeneric(block, context) {
|
|
1580
1399
|
const div = document.createElement('div');
|
|
1581
1400
|
div.className = 'block-generic';
|
|
1582
1401
|
div.classList.add(this._getBlockTypeClass('generic'));
|
|
1583
1402
|
|
|
1584
|
-
// Show key-value pairs instead of raw JSON
|
|
1585
1403
|
const fieldsHtml = Object.entries(block)
|
|
1586
1404
|
.filter(([key]) => key !== 'type')
|
|
1587
1405
|
.map(([key, value]) => {
|
|
@@ -1605,9 +1423,6 @@ class StreamingRenderer {
|
|
|
1605
1423
|
return div;
|
|
1606
1424
|
}
|
|
1607
1425
|
|
|
1608
|
-
/**
|
|
1609
|
-
* Render block error
|
|
1610
|
-
*/
|
|
1611
1426
|
renderBlockError(block, error) {
|
|
1612
1427
|
const div = document.createElement('div');
|
|
1613
1428
|
div.className = 'block-error';
|
|
@@ -1628,9 +1443,6 @@ class StreamingRenderer {
|
|
|
1628
1443
|
return div;
|
|
1629
1444
|
}
|
|
1630
1445
|
|
|
1631
|
-
/**
|
|
1632
|
-
* Render streaming start event
|
|
1633
|
-
*/
|
|
1634
1446
|
renderStreamingStart(event) {
|
|
1635
1447
|
const div = document.createElement('div');
|
|
1636
1448
|
div.className = 'event-streaming-start card mb-3 p-4 alert alert-info';
|
|
@@ -1653,16 +1465,11 @@ class StreamingRenderer {
|
|
|
1653
1465
|
return div;
|
|
1654
1466
|
}
|
|
1655
1467
|
|
|
1656
|
-
/**
|
|
1657
|
-
* Render streaming progress event
|
|
1658
|
-
*/
|
|
1659
1468
|
renderStreamingProgress(event) {
|
|
1660
|
-
// If there's a block in the progress event, render it beautifully
|
|
1661
1469
|
if (event.block) {
|
|
1662
1470
|
return this.renderBlock(event.block, event);
|
|
1663
1471
|
}
|
|
1664
1472
|
|
|
1665
|
-
// Fallback: simple progress indicator
|
|
1666
1473
|
const div = document.createElement('div');
|
|
1667
1474
|
div.className = 'event-streaming-progress mb-2 p-2';
|
|
1668
1475
|
div.dataset.eventId = event.id || '';
|
|
@@ -1680,9 +1487,6 @@ class StreamingRenderer {
|
|
|
1680
1487
|
return div;
|
|
1681
1488
|
}
|
|
1682
1489
|
|
|
1683
|
-
/**
|
|
1684
|
-
* Render streaming complete event with metadata
|
|
1685
|
-
*/
|
|
1686
1490
|
renderStreamingComplete(event) {
|
|
1687
1491
|
const div = document.createElement('div');
|
|
1688
1492
|
div.className = 'event-streaming-complete card mb-3 p-4 alert alert-success rounded-lg';
|
|
@@ -1716,9 +1520,6 @@ class StreamingRenderer {
|
|
|
1716
1520
|
return div;
|
|
1717
1521
|
}
|
|
1718
1522
|
|
|
1719
|
-
/**
|
|
1720
|
-
* Detect if content is a base64-encoded image
|
|
1721
|
-
*/
|
|
1722
1523
|
detectBase64Image(content) {
|
|
1723
1524
|
if (!content || typeof content !== 'string') return null;
|
|
1724
1525
|
const trimmed = content.trim();
|
|
@@ -1736,9 +1537,6 @@ class StreamingRenderer {
|
|
|
1736
1537
|
return null;
|
|
1737
1538
|
}
|
|
1738
1539
|
|
|
1739
|
-
/**
|
|
1740
|
-
* Render file read event
|
|
1741
|
-
*/
|
|
1742
1540
|
renderFileRead(event) {
|
|
1743
1541
|
const fileName = event.path ? event.path.split('/').pop() : 'unknown';
|
|
1744
1542
|
const details = document.createElement('details');
|
|
@@ -1789,9 +1587,6 @@ class StreamingRenderer {
|
|
|
1789
1587
|
return details;
|
|
1790
1588
|
}
|
|
1791
1589
|
|
|
1792
|
-
/**
|
|
1793
|
-
* Render file write event
|
|
1794
|
-
*/
|
|
1795
1590
|
renderFileWrite(event) {
|
|
1796
1591
|
const fileName = event.path ? event.path.split('/').pop() : 'unknown';
|
|
1797
1592
|
const details = document.createElement('details');
|
|
@@ -1817,9 +1612,6 @@ class StreamingRenderer {
|
|
|
1817
1612
|
return details;
|
|
1818
1613
|
}
|
|
1819
1614
|
|
|
1820
|
-
/**
|
|
1821
|
-
* Render git status event
|
|
1822
|
-
*/
|
|
1823
1615
|
renderGitStatus(event) {
|
|
1824
1616
|
const div = document.createElement('div');
|
|
1825
1617
|
div.className = 'event-git-status card mb-3 p-4';
|
|
@@ -1850,9 +1642,6 @@ class StreamingRenderer {
|
|
|
1850
1642
|
return div;
|
|
1851
1643
|
}
|
|
1852
1644
|
|
|
1853
|
-
/**
|
|
1854
|
-
* Render command execution event
|
|
1855
|
-
*/
|
|
1856
1645
|
renderCommand(event) {
|
|
1857
1646
|
const command = event.command || '';
|
|
1858
1647
|
const output = event.output || '';
|
|
@@ -1888,9 +1677,6 @@ class StreamingRenderer {
|
|
|
1888
1677
|
return details;
|
|
1889
1678
|
}
|
|
1890
1679
|
|
|
1891
|
-
/**
|
|
1892
|
-
* Render error event
|
|
1893
|
-
*/
|
|
1894
1680
|
renderError(event) {
|
|
1895
1681
|
const message = event.message || event.error || 'Unknown error';
|
|
1896
1682
|
const severity = event.severity || 'error';
|
|
@@ -1949,9 +1735,6 @@ class StreamingRenderer {
|
|
|
1949
1735
|
return parts;
|
|
1950
1736
|
}
|
|
1951
1737
|
|
|
1952
|
-
/**
|
|
1953
|
-
* Render text block event - for backward compatibility
|
|
1954
|
-
*/
|
|
1955
1738
|
renderText(event) {
|
|
1956
1739
|
const div = document.createElement('div');
|
|
1957
1740
|
div.className = 'event-text mb-3';
|
|
@@ -1992,7 +1775,6 @@ class StreamingRenderer {
|
|
|
1992
1775
|
});
|
|
1993
1776
|
div.innerHTML = html;
|
|
1994
1777
|
|
|
1995
|
-
// Add copy button functionality
|
|
1996
1778
|
div.querySelectorAll('.copy-code-btn').forEach(btn => {
|
|
1997
1779
|
btn.addEventListener('click', () => {
|
|
1998
1780
|
const codeElement = btn.closest('.mb-3')?.querySelector('code');
|
|
@@ -2010,9 +1792,6 @@ class StreamingRenderer {
|
|
|
2010
1792
|
return div;
|
|
2011
1793
|
}
|
|
2012
1794
|
|
|
2013
|
-
/**
|
|
2014
|
-
* Render code block event
|
|
2015
|
-
*/
|
|
2016
1795
|
renderCode(event) {
|
|
2017
1796
|
const div = document.createElement('div');
|
|
2018
1797
|
div.className = 'event-code mb-3';
|
|
@@ -2022,7 +1801,6 @@ class StreamingRenderer {
|
|
|
2022
1801
|
const code = event.code || event.content || '';
|
|
2023
1802
|
const language = event.language || 'plaintext';
|
|
2024
1803
|
|
|
2025
|
-
// Render HTML code blocks as actual HTML elements
|
|
2026
1804
|
if (language === 'html') {
|
|
2027
1805
|
div.innerHTML = `
|
|
2028
1806
|
<div class="html-rendered-container alert alert-info text-xs mb-2">
|
|
@@ -2044,9 +1822,6 @@ class StreamingRenderer {
|
|
|
2044
1822
|
return div;
|
|
2045
1823
|
}
|
|
2046
1824
|
|
|
2047
|
-
/**
|
|
2048
|
-
* Render thinking block event
|
|
2049
|
-
*/
|
|
2050
1825
|
renderThinking(event) {
|
|
2051
1826
|
const div = document.createElement('div');
|
|
2052
1827
|
div.className = 'event-thinking mb-3 p-4 alert rounded';
|
|
@@ -2063,11 +1838,7 @@ class StreamingRenderer {
|
|
|
2063
1838
|
return div;
|
|
2064
1839
|
}
|
|
2065
1840
|
|
|
2066
|
-
/**
|
|
2067
|
-
* Render tool use event - for backward compatibility
|
|
2068
|
-
*/
|
|
2069
1841
|
renderToolUse(event) {
|
|
2070
|
-
// Use the new block-based renderer for consistency
|
|
2071
1842
|
const block = {
|
|
2072
1843
|
type: 'tool_use',
|
|
2073
1844
|
name: event.toolName || event.tool || 'unknown',
|
|
@@ -2146,11 +1917,7 @@ class StreamingRenderer {
|
|
|
2146
1917
|
return div;
|
|
2147
1918
|
}
|
|
2148
1919
|
|
|
2149
|
-
/**
|
|
2150
|
-
* Render generic event with formatted key-value pairs
|
|
2151
|
-
*/
|
|
2152
1920
|
renderGeneric(event) {
|
|
2153
|
-
// Check if this is actually a file read with base64 image content
|
|
2154
1921
|
if ((event.content?.source?.type === 'base64' || event.content?.type === 'base64') && event.path) {
|
|
2155
1922
|
return this.renderFileRead(event);
|
|
2156
1923
|
}
|
|
@@ -2162,7 +1929,6 @@ class StreamingRenderer {
|
|
|
2162
1929
|
|
|
2163
1930
|
const time = new Date(event.timestamp).toLocaleTimeString();
|
|
2164
1931
|
|
|
2165
|
-
// Format event data as key-value pairs
|
|
2166
1932
|
const fieldsHtml = Object.entries(event)
|
|
2167
1933
|
.filter(([key]) => !['type', 'timestamp'].includes(key))
|
|
2168
1934
|
.map(([key, value]) => {
|
|
@@ -2190,9 +1956,6 @@ class StreamingRenderer {
|
|
|
2190
1956
|
return div;
|
|
2191
1957
|
}
|
|
2192
1958
|
|
|
2193
|
-
/**
|
|
2194
|
-
* Nest tool result blocks inside their corresponding tool use blocks
|
|
2195
|
-
*/
|
|
2196
1959
|
nestToolResultsInToolUses() {
|
|
2197
1960
|
if (!this.outputContainer) return;
|
|
2198
1961
|
|
|
@@ -2238,9 +2001,6 @@ class StreamingRenderer {
|
|
|
2238
2001
|
});
|
|
2239
2002
|
}
|
|
2240
2003
|
|
|
2241
|
-
/**
|
|
2242
|
-
* Auto-scroll to bottom of container
|
|
2243
|
-
*/
|
|
2244
2004
|
mergeResultIntoToolUse(toolUseEl, block) {
|
|
2245
2005
|
const content = block.content || '';
|
|
2246
2006
|
const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
|
|
@@ -2302,23 +2062,14 @@ class StreamingRenderer {
|
|
|
2302
2062
|
this._userScrolledUp = false;
|
|
2303
2063
|
}
|
|
2304
2064
|
|
|
2305
|
-
/**
|
|
2306
|
-
* Update DOM node count for monitoring
|
|
2307
|
-
*/
|
|
2308
2065
|
updateDOMNodeCount() {
|
|
2309
2066
|
this.domNodeCount = this.outputContainer?.querySelectorAll('[data-event-id]').length || 0;
|
|
2310
2067
|
}
|
|
2311
2068
|
|
|
2312
|
-
/**
|
|
2313
|
-
* HTML escape utility
|
|
2314
|
-
*/
|
|
2315
2069
|
escapeHtml(text) {
|
|
2316
2070
|
return window._escHtml(text);
|
|
2317
2071
|
}
|
|
2318
2072
|
|
|
2319
|
-
/**
|
|
2320
|
-
* Format file size for display
|
|
2321
|
-
*/
|
|
2322
2073
|
formatFileSize(bytes) {
|
|
2323
2074
|
if (bytes === 0) return '0 B';
|
|
2324
2075
|
const k = 1024;
|
|
@@ -2327,17 +2078,11 @@ class StreamingRenderer {
|
|
|
2327
2078
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
2328
2079
|
}
|
|
2329
2080
|
|
|
2330
|
-
/**
|
|
2331
|
-
* Truncate content for display
|
|
2332
|
-
*/
|
|
2333
2081
|
truncateContent(content, maxLength = 200) {
|
|
2334
2082
|
if (content.length <= maxLength) return content;
|
|
2335
2083
|
return content.substring(0, maxLength) + '...';
|
|
2336
2084
|
}
|
|
2337
2085
|
|
|
2338
|
-
/**
|
|
2339
|
-
* Clear all rendered events
|
|
2340
|
-
*/
|
|
2341
2086
|
clear() {
|
|
2342
2087
|
if (this.outputContainer) {
|
|
2343
2088
|
this.outputContainer.innerHTML = '';
|
|
@@ -2348,9 +2093,6 @@ class StreamingRenderer {
|
|
|
2348
2093
|
this.dedupMap.clear();
|
|
2349
2094
|
}
|
|
2350
2095
|
|
|
2351
|
-
/**
|
|
2352
|
-
* Get performance metrics
|
|
2353
|
-
*/
|
|
2354
2096
|
getMetrics() {
|
|
2355
2097
|
return {
|
|
2356
2098
|
...this.performanceMetrics,
|
|
@@ -2361,9 +2103,6 @@ class StreamingRenderer {
|
|
|
2361
2103
|
};
|
|
2362
2104
|
}
|
|
2363
2105
|
|
|
2364
|
-
/**
|
|
2365
|
-
* Add event listener
|
|
2366
|
-
*/
|
|
2367
2106
|
on(event, callback) {
|
|
2368
2107
|
if (!this.listeners[event]) {
|
|
2369
2108
|
this.listeners[event] = [];
|
|
@@ -2371,9 +2110,6 @@ class StreamingRenderer {
|
|
|
2371
2110
|
this.listeners[event].push(callback);
|
|
2372
2111
|
}
|
|
2373
2112
|
|
|
2374
|
-
/**
|
|
2375
|
-
* Emit event to listeners
|
|
2376
|
-
*/
|
|
2377
2113
|
emit(event, data) {
|
|
2378
2114
|
if (this.listeners[event]) {
|
|
2379
2115
|
this.listeners[event].forEach(callback => {
|
|
@@ -2386,10 +2122,6 @@ class StreamingRenderer {
|
|
|
2386
2122
|
}
|
|
2387
2123
|
}
|
|
2388
2124
|
|
|
2389
|
-
/**
|
|
2390
|
-
* Render block header with lazy-loading placeholder for body
|
|
2391
|
-
* Returns a <details> element with just the summary, body content deferred
|
|
2392
|
-
*/
|
|
2393
2125
|
renderBlockHeader(block, context = {}) {
|
|
2394
2126
|
if (!block || !block.type) return null;
|
|
2395
2127
|
|
|
@@ -2421,7 +2153,6 @@ class StreamingRenderer {
|
|
|
2421
2153
|
details.open = block.type === 'success' || (block.type === 'tool_result' && !block.is_error);
|
|
2422
2154
|
details.appendChild(summary);
|
|
2423
2155
|
|
|
2424
|
-
// Attach lazy loader on first open
|
|
2425
2156
|
details.addEventListener('toggle', async (e) => {
|
|
2426
2157
|
if (details.open && details.getAttribute('data-lazy-load') === 'pending') {
|
|
2427
2158
|
details.setAttribute('data-lazy-load', 'loading');
|
|
@@ -2441,9 +2172,6 @@ class StreamingRenderer {
|
|
|
2441
2172
|
return details;
|
|
2442
2173
|
}
|
|
2443
2174
|
|
|
2444
|
-
/**
|
|
2445
|
-
* Cleanup resources
|
|
2446
|
-
*/
|
|
2447
2175
|
destroy() {
|
|
2448
2176
|
if (this.observer) {
|
|
2449
2177
|
this.observer.disconnect();
|
|
@@ -2459,7 +2187,6 @@ class StreamingRenderer {
|
|
|
2459
2187
|
}
|
|
2460
2188
|
}
|
|
2461
2189
|
|
|
2462
|
-
// Export for use in browser
|
|
2463
2190
|
if (typeof module !== 'undefined' && module.exports) {
|
|
2464
2191
|
module.exports = StreamingRenderer;
|
|
2465
2192
|
}
|