agentgui 1.0.826 → 1.0.828
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 +3 -0
- package/package.json +2 -2
- package/static/index.html +14 -0
- package/static/js/streaming-renderer-dispatch.js +144 -0
- package/static/js/streaming-renderer-events.js +163 -0
- package/static/js/streaming-renderer-events2.js +125 -0
- package/static/js/streaming-renderer-params.js +38 -0
- package/static/js/streaming-renderer-render-misc.js +107 -0
- package/static/js/streaming-renderer-render.js +181 -0
- package/static/js/streaming-renderer-render2.js +149 -0
- package/static/js/streaming-renderer-render3.js +142 -0
- package/static/js/streaming-renderer-static.js +181 -0
- package/static/js/streaming-renderer-static2.js +140 -0
- package/static/js/streaming-renderer-stream.js +170 -0
- package/static/js/streaming-renderer-text.js +185 -0
- package/static/js/streaming-renderer-tools.js +189 -0
- package/static/js/streaming-renderer-tools2.js +92 -0
- package/static/js/streaming-renderer.js +4 -1996
|
@@ -194,1999 +194,7 @@ class StreamingRenderer {
|
|
|
194
194
|
const processTime = performance.now() - processStart;
|
|
195
195
|
this.performanceMetrics.avgProcessTime = this.performanceMetrics.avgProcessTime || processTime;
|
|
196
196
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
this.emit('render:start', { eventCount: batch.length });
|
|
202
|
-
const renderStart = performance.now();
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
const fragment = document.createDocumentFragment();
|
|
206
|
-
let nodeCount = 0;
|
|
207
|
-
|
|
208
|
-
for (const event of batch) {
|
|
209
|
-
try {
|
|
210
|
-
const element = this.renderEvent(event);
|
|
211
|
-
if (element) {
|
|
212
|
-
fragment.appendChild(element);
|
|
213
|
-
nodeCount++;
|
|
214
|
-
}
|
|
215
|
-
} catch (error) {
|
|
216
|
-
console.error('Event render error:', error, event);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (nodeCount > 0) {
|
|
221
|
-
this.outputContainer.appendChild(fragment);
|
|
222
|
-
this.domNodeCount += nodeCount;
|
|
223
|
-
|
|
224
|
-
this.nestToolResultsInToolUses();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
this.autoScroll();
|
|
228
|
-
|
|
229
|
-
const renderTime = performance.now() - renderStart;
|
|
230
|
-
this.lastRenderTime = renderTime;
|
|
231
|
-
|
|
232
|
-
this.emit('render:complete', {
|
|
233
|
-
eventCount: batch.length,
|
|
234
|
-
nodeCount,
|
|
235
|
-
renderTime
|
|
236
|
-
});
|
|
237
|
-
} catch (error) {
|
|
238
|
-
console.error('Batch render error:', error);
|
|
239
|
-
this.emit('error:render', { error, batch });
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
renderEvent(event) {
|
|
244
|
-
if (!event.type) return null;
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
if (event.type === 'streaming_progress' && event.block) {
|
|
248
|
-
return this.renderBlock(event.block, event);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (event.type === 'streaming_error' && event.isPrematureEnd) {
|
|
252
|
-
return this.renderBlockPremature({ type: 'premature', error: event.error, exitCode: event.exitCode });
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
switch (event.type) {
|
|
256
|
-
case 'streaming_start':
|
|
257
|
-
return this.renderStreamingStart(event);
|
|
258
|
-
case 'streaming_progress':
|
|
259
|
-
return this.renderStreamingProgress(event);
|
|
260
|
-
case 'streaming_complete':
|
|
261
|
-
return this.renderStreamingComplete(event);
|
|
262
|
-
case 'file_read':
|
|
263
|
-
return this.renderFileRead(event);
|
|
264
|
-
case 'file_write':
|
|
265
|
-
return this.renderFileWrite(event);
|
|
266
|
-
case 'git_status':
|
|
267
|
-
return this.renderGitStatus(event);
|
|
268
|
-
case 'command_execute':
|
|
269
|
-
return this.renderCommand(event);
|
|
270
|
-
case 'error':
|
|
271
|
-
return this.renderError(event);
|
|
272
|
-
case 'text_block':
|
|
273
|
-
return this.renderText(event);
|
|
274
|
-
case 'code_block':
|
|
275
|
-
return this.renderCode(event);
|
|
276
|
-
case 'thinking_block':
|
|
277
|
-
return null;
|
|
278
|
-
case 'tool_use':
|
|
279
|
-
return this.renderToolUse(event);
|
|
280
|
-
case 'compact_boundary':
|
|
281
|
-
return this.renderCompactBoundary(event);
|
|
282
|
-
case 'compact_summary':
|
|
283
|
-
return this.renderCompactBoundary(event);
|
|
284
|
-
case 'turn_duration':
|
|
285
|
-
return this.renderTurnDuration(event);
|
|
286
|
-
case 'agent_progress':
|
|
287
|
-
return this.renderAgentProgress(event);
|
|
288
|
-
case 'mcp_progress':
|
|
289
|
-
return this.renderMcpProgress(event);
|
|
290
|
-
default:
|
|
291
|
-
return this.renderGeneric(event);
|
|
292
|
-
}
|
|
293
|
-
} catch (error) {
|
|
294
|
-
console.error('Event render error:', error, event);
|
|
295
|
-
return this.renderError({ message: error.message, event });
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
renderBlock(block, context = {}, targetContainer = null) {
|
|
300
|
-
if (!block || !block.type) return null;
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
switch (block.type) {
|
|
304
|
-
case 'text':
|
|
305
|
-
return this.renderBlockText(block, context, targetContainer);
|
|
306
|
-
case 'code':
|
|
307
|
-
return this.renderBlockCode(block, context);
|
|
308
|
-
case 'thinking':
|
|
309
|
-
return this.renderBlockThinking(block, context);
|
|
310
|
-
case 'tool_use':
|
|
311
|
-
return this.renderBlockToolUse(block, context);
|
|
312
|
-
case 'tool_result':
|
|
313
|
-
return this.renderBlockToolResult(block, context);
|
|
314
|
-
case 'image':
|
|
315
|
-
return this.renderBlockImage(block, context);
|
|
316
|
-
case 'bash':
|
|
317
|
-
return this.renderBlockBash(block, context);
|
|
318
|
-
case 'system':
|
|
319
|
-
return this.renderBlockSystem(block, context);
|
|
320
|
-
case 'result':
|
|
321
|
-
return this.renderBlockResult(block, context);
|
|
322
|
-
case 'tool_status':
|
|
323
|
-
return this.renderBlockToolStatus(block, context);
|
|
324
|
-
case 'usage':
|
|
325
|
-
return this.renderBlockUsage(block, context);
|
|
326
|
-
case 'plan':
|
|
327
|
-
return this.renderBlockPlan(block, context);
|
|
328
|
-
case 'premature':
|
|
329
|
-
return this.renderBlockPremature(block, context);
|
|
330
|
-
case 'hook_progress':
|
|
331
|
-
return this.renderBlockHookProgress(block, context);
|
|
332
|
-
default:
|
|
333
|
-
return this.renderBlockGeneric(block, context);
|
|
334
|
-
}
|
|
335
|
-
} catch (error) {
|
|
336
|
-
console.error('Block render error:', error, block);
|
|
337
|
-
return this.renderBlockError(block, error);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
renderBlockText(block, context, targetContainer = null) {
|
|
342
|
-
const text = block.text || '';
|
|
343
|
-
const isHtml = this.containsHtmlTags(text);
|
|
344
|
-
const cached = this.renderCache.get(text);
|
|
345
|
-
const html = cached || (isHtml ? this.sanitizeHtml(text) : this.parseAndRenderMarkdown(text));
|
|
346
|
-
|
|
347
|
-
if (!cached && this.renderCache.size < 2000) {
|
|
348
|
-
this.renderCache.set(text, html);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const container = targetContainer || this.outputContainer;
|
|
352
|
-
const lastChild = container && container.lastElementChild;
|
|
353
|
-
if (lastChild && lastChild.classList.contains('block-text') && !isHtml && !lastChild.classList.contains('html-content')) {
|
|
354
|
-
lastChild.innerHTML += html;
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const div = document.createElement('div');
|
|
359
|
-
div.className = 'block-text';
|
|
360
|
-
if (isHtml) div.classList.add('html-content');
|
|
361
|
-
div.innerHTML = html;
|
|
362
|
-
div.classList.add(this._getBlockTypeClass('text'));
|
|
363
|
-
return div;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
_getBlockTypeClass(blockType) {
|
|
367
|
-
const validTypes = ['text','tool_use','tool_result','code','thinking','bash','system','result','error','image','plan','usage','premature','tool_status','generic'];
|
|
368
|
-
return validTypes.includes(blockType) ? `block-type-${blockType}` : 'block-type-generic';
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
_getToolColorClass(toolName) {
|
|
372
|
-
const n = (toolName || '').replace(/^mcp__.*?__/, '').toLowerCase();
|
|
373
|
-
const map = {
|
|
374
|
-
read: 'read', write: 'write', edit: 'edit', bash: 'bash', glob: 'glob', grep: 'grep',
|
|
375
|
-
webfetch: 'web', websearch: 'web', todowrite: 'todo', task: 'task', notebookedit: 'edit',
|
|
376
|
-
execute: 'execute', sleep: 'sleep', search: 'search', skill: 'skill'
|
|
377
|
-
};
|
|
378
|
-
return `tool-color-${map[n] || 'default'}`;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
containsHtmlTags(text) {
|
|
382
|
-
const htmlPattern = /<(?:div|table|section|article|ul|ol|dl|nav|header|footer|main|aside|figure|details|summary|h[1-6]|p|blockquote|pre|code|span|strong|em|a|img|br|hr|li|td|tr|th|thead|tbody|tfoot)\b[^>]*>/i;
|
|
383
|
-
return htmlPattern.test(text);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
sanitizeHtml(html) {
|
|
387
|
-
const dangerous = /<\s*\/?\s*(script|iframe|object|embed|applet|form|input|button|select|textarea)\b[^>]*>/gi;
|
|
388
|
-
let cleaned = html.replace(dangerous, '');
|
|
389
|
-
cleaned = cleaned.replace(/\s+on\w+\s*=\s*["'][^"']*["']/gi, '');
|
|
390
|
-
cleaned = cleaned.replace(/\s+on\w+\s*=\s*[^\s>]+/gi, '');
|
|
391
|
-
cleaned = cleaned.replace(/javascript\s*:/gi, '');
|
|
392
|
-
return cleaned;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
parseAndRenderMarkdown(text) {
|
|
396
|
-
const esc = this.escapeHtml.bind(this);
|
|
397
|
-
const lines = text.split('\n');
|
|
398
|
-
const out = [];
|
|
399
|
-
let i = 0;
|
|
400
|
-
while (i < lines.length) {
|
|
401
|
-
const line = lines[i];
|
|
402
|
-
const trimmed = line.trim();
|
|
403
|
-
if (trimmed.startsWith('### ')) {
|
|
404
|
-
out.push(`<h3 style="margin:0.4rem 0 0.2rem;font-size:0.95rem;font-weight:700">${this._mdInline(esc(trimmed.slice(4)))}</h3>`);
|
|
405
|
-
i++; continue;
|
|
406
|
-
}
|
|
407
|
-
if (trimmed.startsWith('## ')) {
|
|
408
|
-
out.push(`<h2 style="margin:0.5rem 0 0.25rem;font-size:1rem;font-weight:700">${this._mdInline(esc(trimmed.slice(3)))}</h2>`);
|
|
409
|
-
i++; continue;
|
|
410
|
-
}
|
|
411
|
-
if (trimmed.startsWith('# ')) {
|
|
412
|
-
out.push(`<h2 style="margin:0.5rem 0 0.25rem;font-size:1.05rem;font-weight:700">${this._mdInline(esc(trimmed.slice(2)))}</h2>`);
|
|
413
|
-
i++; continue;
|
|
414
|
-
}
|
|
415
|
-
if (trimmed.startsWith('> ')) {
|
|
416
|
-
out.push(`<blockquote style="margin:0.25rem 0;padding:0.2rem 0.75rem;border-left:3px solid currentColor;opacity:0.75">${this._mdInline(esc(trimmed.slice(2)))}</blockquote>`);
|
|
417
|
-
i++; continue;
|
|
418
|
-
}
|
|
419
|
-
if (trimmed === '---' || trimmed === '***' || trimmed === '___') {
|
|
420
|
-
out.push('<hr style="border:none;border-top:1px solid currentColor;opacity:0.2;margin:0.5rem 0">');
|
|
421
|
-
i++; continue;
|
|
422
|
-
}
|
|
423
|
-
if (/^[-*+] /.test(trimmed)) {
|
|
424
|
-
const items = [];
|
|
425
|
-
while (i < lines.length && /^[-*+] /.test(lines[i].trim())) {
|
|
426
|
-
items.push(`<li style="margin:0.1rem 0">${this._mdInline(esc(lines[i].trim().slice(2)))}</li>`);
|
|
427
|
-
i++;
|
|
428
|
-
}
|
|
429
|
-
out.push(`<ul style="margin:0.25rem 0;padding-left:1.25rem">${items.join('')}</ul>`);
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
if (/^\d+\. /.test(trimmed)) {
|
|
433
|
-
const items = [];
|
|
434
|
-
while (i < lines.length && /^\d+\. /.test(lines[i].trim())) {
|
|
435
|
-
items.push(`<li style="margin:0.1rem 0">${this._mdInline(esc(lines[i].trim().replace(/^\d+\. /, '')))}</li>`);
|
|
436
|
-
i++;
|
|
437
|
-
}
|
|
438
|
-
out.push(`<ol style="margin:0.25rem 0;padding-left:1.25rem">${items.join('')}</ol>`);
|
|
439
|
-
continue;
|
|
440
|
-
}
|
|
441
|
-
if (trimmed === '') {
|
|
442
|
-
out.push('<br>');
|
|
443
|
-
i++; continue;
|
|
444
|
-
}
|
|
445
|
-
out.push(this._mdInline(esc(line)));
|
|
446
|
-
i++;
|
|
447
|
-
}
|
|
448
|
-
return out.join('\n');
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
_mdInline(html) {
|
|
452
|
-
return html
|
|
453
|
-
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
454
|
-
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
|
|
455
|
-
.replace(/_([^_]+)_/g, '<em>$1</em>')
|
|
456
|
-
.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>')
|
|
457
|
-
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" style="text-decoration:underline;opacity:0.85" target="_blank">$1</a>');
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
renderBlockCode(block, context) {
|
|
461
|
-
const div = document.createElement('div');
|
|
462
|
-
div.className = 'block-code';
|
|
463
|
-
div.classList.add(this._getBlockTypeClass('code'));
|
|
464
|
-
|
|
465
|
-
const code = block.code || '';
|
|
466
|
-
const language = (block.language || 'plaintext').toLowerCase();
|
|
467
|
-
const lineCount = code.split('\n').length;
|
|
468
|
-
|
|
469
|
-
const header = document.createElement('div');
|
|
470
|
-
header.className = 'code-block-header';
|
|
471
|
-
header.innerHTML = `
|
|
472
|
-
<span class="collapsible-code-label">${this.escapeHtml(language)} - ${lineCount} line${lineCount !== 1 ? 's' : ''}</span>
|
|
473
|
-
<button class="copy-code-btn" title="Copy code">
|
|
474
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
|
475
|
-
</button>
|
|
476
|
-
`;
|
|
477
|
-
|
|
478
|
-
const copyBtn = header.querySelector('.copy-code-btn');
|
|
479
|
-
copyBtn.addEventListener('click', (e) => {
|
|
480
|
-
e.preventDefault();
|
|
481
|
-
e.stopPropagation();
|
|
482
|
-
navigator.clipboard.writeText(code).then(() => {
|
|
483
|
-
const orig = copyBtn.innerHTML;
|
|
484
|
-
copyBtn.innerHTML = '<svg viewBox="0 0 20 20" fill="#34d399"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>';
|
|
485
|
-
setTimeout(() => { copyBtn.innerHTML = orig; }, 2000);
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
const preStyle = "background:var(--color-bg-code);padding:1rem;border-radius:0 0 0.375rem 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);border-top:none;margin:0";
|
|
490
|
-
const codeContainer = document.createElement('div');
|
|
491
|
-
codeContainer.innerHTML = `<pre style="${preStyle}"><code class="lazy-hl">${this.escapeHtml(code)}</code></pre>`;
|
|
492
|
-
|
|
493
|
-
div.appendChild(header);
|
|
494
|
-
div.appendChild(codeContainer);
|
|
495
|
-
|
|
496
|
-
return div;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
renderBlockThinking(block, context) {
|
|
500
|
-
const thinking = block.thinking || '';
|
|
501
|
-
const hasSignature = !!block.signature;
|
|
502
|
-
const div = document.createElement('div');
|
|
503
|
-
div.className = 'block-thinking';
|
|
504
|
-
div.classList.add(this._getBlockTypeClass('thinking'));
|
|
505
|
-
|
|
506
|
-
if (!thinking) {
|
|
507
|
-
div.style.cssText = 'display:flex;align-items:center;gap:0.375rem;padding:0.25rem 0;font-size:0.7rem;color:var(--color-text-secondary);opacity:0.6';
|
|
508
|
-
div.innerHTML = '<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.875rem;height:0.875rem"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/></svg>'
|
|
509
|
-
+ '<span>Thinking</span>'
|
|
510
|
-
+ (hasSignature ? '<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.75rem;height:0.75rem;color:#3b82f6"><path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>' : '');
|
|
511
|
-
return div;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
div.innerHTML = '<details>'
|
|
515
|
-
+ '<summary>'
|
|
516
|
-
+ '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>'
|
|
517
|
-
+ '<span>Thinking Process</span>'
|
|
518
|
-
+ (hasSignature ? '<span style="margin-left:0.5rem;font-size:0.65rem;opacity:0.5;font-weight:400">verified</span>' : '')
|
|
519
|
-
+ '</summary>'
|
|
520
|
-
+ '<div class="thinking-content">' + this.parseAndRenderMarkdown(thinking) + '</div>'
|
|
521
|
-
+ '</details>';
|
|
522
|
-
return div;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
getToolIcon(toolName) {
|
|
526
|
-
const icons = {
|
|
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>',
|
|
528
|
-
Write: '<svg viewBox="0 0 20 20" fill="currentColor"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/></svg>',
|
|
529
|
-
Edit: '<svg viewBox="0 0 20 20" fill="currentColor"><path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"/><path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd"/></svg>',
|
|
530
|
-
Bash: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M2 5a2 2 0 012-2h12a2 2 0 012 2v10a2 2 0 01-2 2H4a2 2 0 01-2-2V5zm3.293 1.293a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 01-1.414-1.414L7.586 10 5.293 7.707a1 1 0 010-1.414zM11 12a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"/></svg>',
|
|
531
|
-
Glob: '<svg viewBox="0 0 20 20" fill="currentColor"><path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"/></svg>',
|
|
532
|
-
Grep: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/></svg>',
|
|
533
|
-
WebFetch: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM4.332 8.027a6.012 6.012 0 011.912-2.706C6.512 5.73 6.974 6 7.5 6A1.5 1.5 0 019 7.5V8a2 2 0 004 0 2 2 0 012 2v1a2 2 0 01-2 2 2 2 0 01-2 2v.5a6.003 6.003 0 01-6.668-7.473z" clip-rule="evenodd"/></svg>',
|
|
534
|
-
WebSearch: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/></svg>',
|
|
535
|
-
TodoWrite: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 011 1v3.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 111.414-1.414L6 11.586V8a1 1 0 011-1z" clip-rule="evenodd"/></svg>',
|
|
536
|
-
Task: '<svg viewBox="0 0 20 20" fill="currentColor"><path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/><path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"/></svg>',
|
|
537
|
-
NotebookEdit: '<svg viewBox="0 0 20 20" fill="currentColor"><path d="M9 4.804A7.968 7.968 0 005.5 4c-1.255 0-2.443.29-3.5.804v10A7.969 7.969 0 015.5 14c1.669 0 3.218.51 4.5 1.385A7.962 7.962 0 0114.5 14c1.255 0 2.443.29 3.5.804v-10A7.968 7.968 0 0014.5 4c-1.255 0-2.443.29-3.5.804V12a1 1 0 11-2 0V4.804z"/></svg>'
|
|
538
|
-
};
|
|
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>';
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
renderFilePath(filePath) {
|
|
543
|
-
if (!filePath) return '';
|
|
544
|
-
const parts = pathSplit(filePath);
|
|
545
|
-
const fileName = parts.pop();
|
|
546
|
-
const dir = parts.join('/');
|
|
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>`;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
renderSmartParams(toolName, input) {
|
|
551
|
-
if (!input || Object.keys(input).length === 0) return '';
|
|
552
|
-
|
|
553
|
-
const normalizedName = toolName.replace(/^mcp__.*?__/, '');
|
|
554
|
-
|
|
555
|
-
switch (normalizedName) {
|
|
556
|
-
case 'Read':
|
|
557
|
-
return `<div class="tool-params">${this.renderFilePath(input.file_path)}${input.offset ? `<div style="margin-top:0.375rem;font-size:0.75rem;color:var(--color-text-secondary)">Lines ${input.offset}${input.limit ? '–' + (input.offset + input.limit) : '+'}</div>` : ''}</div>`;
|
|
558
|
-
|
|
559
|
-
case 'Write':
|
|
560
|
-
return `<div class="tool-params">${this.renderFilePath(input.file_path)}${input.content ? this.renderContentPreview(input.content, 'Content') : ''}</div>`;
|
|
561
|
-
|
|
562
|
-
case 'Edit': {
|
|
563
|
-
let html = `<div class="tool-params">${this.renderFilePath(input.file_path)}`;
|
|
564
|
-
if (input.old_string || input.new_string) {
|
|
565
|
-
html += `<div class="tool-param-diff" style="margin-top:0.5rem">`;
|
|
566
|
-
if (input.old_string) {
|
|
567
|
-
html += `<div class="diff-header">Remove</div><div class="diff-old">${this.escapeHtml(this.truncateContent(input.old_string, 500))}</div>`;
|
|
568
|
-
}
|
|
569
|
-
if (input.new_string) {
|
|
570
|
-
html += `<div class="diff-header">Add</div><div class="diff-new">${this.escapeHtml(this.truncateContent(input.new_string, 500))}</div>`;
|
|
571
|
-
}
|
|
572
|
-
html += '</div>';
|
|
573
|
-
}
|
|
574
|
-
return html + '</div>';
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
case 'Bash': {
|
|
578
|
-
const cmd = input.command || input.commands || '';
|
|
579
|
-
const cmdText = typeof cmd === 'string' ? cmd : JSON.stringify(cmd);
|
|
580
|
-
let html = `<div class="tool-params"><div class="tool-param-command"><span class="prompt-char">$</span><span class="command-text">${this.escapeHtml(cmdText)}</span></div>`;
|
|
581
|
-
if (input.description) html += `<div style="margin-top:0.375rem;font-size:0.75rem;color:var(--color-text-secondary)">${this.escapeHtml(input.description)}</div>`;
|
|
582
|
-
return html + '</div>';
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
case 'Glob':
|
|
586
|
-
return `<div class="tool-params"><div class="tool-param-query"><span class="query-icon">📁</span><code style="font-size:0.85rem">${this.escapeHtml(input.pattern || '')}</code></div>${input.path ? `<div style="margin-top:0.25rem;font-size:0.75rem;color:var(--color-text-secondary)">in ${this.escapeHtml(input.path)}</div>` : ''}</div>`;
|
|
587
|
-
|
|
588
|
-
case 'Grep':
|
|
589
|
-
return `<div class="tool-params"><div class="tool-param-query"><span class="query-icon">🔍</span><code style="font-size:0.85rem">${this.escapeHtml(input.pattern || '')}</code></div>${input.path ? `<div style="margin-top:0.25rem;font-size:0.75rem;color:var(--color-text-secondary)">in ${this.escapeHtml(input.path)}</div>` : ''}${input.glob ? `<div style="margin-top:0.125rem;font-size:0.7rem;color:var(--color-text-secondary)">files: ${this.escapeHtml(input.glob)}</div>` : ''}</div>`;
|
|
590
|
-
|
|
591
|
-
case 'WebFetch':
|
|
592
|
-
return `<div class="tool-params"><div class="tool-param-url"><span class="url-icon">🌐</span>${this.escapeHtml(input.url || '')}</div>${input.prompt ? `<div style="margin-top:0.375rem;font-size:0.8rem;color:var(--color-text-secondary)">${this.escapeHtml(this.truncateContent(input.prompt, 150))}</div>` : ''}</div>`;
|
|
593
|
-
|
|
594
|
-
case 'WebSearch':
|
|
595
|
-
return `<div class="tool-params"><div class="tool-param-query"><span class="query-icon">🔍</span><strong style="font-size:0.85rem">${this.escapeHtml(input.query || '')}</strong></div></div>`;
|
|
596
|
-
|
|
597
|
-
case 'TodoWrite':
|
|
598
|
-
if (input.todos && Array.isArray(input.todos)) {
|
|
599
|
-
const statusIcons = { completed: '✅', in_progress: '⚙', pending: '☐' };
|
|
600
|
-
const completedCount = input.todos.filter(t => t.status === 'completed').length;
|
|
601
|
-
const totalCount = input.todos.length;
|
|
602
|
-
const items = input.todos.map(t => `<div class="todo-item"><span class="todo-status">${statusIcons[t.status] || '☐'}</span><span class="todo-text">${this.escapeHtml(t.content || '')}</span></div>`).join('');
|
|
603
|
-
const summary = `<summary class="folded-tool-bar" style="cursor:pointer;padding:0.5rem;background:var(--color-bg-secondary);border-radius:0.25rem;user-select:none"><span style="font-weight:600;font-size:0.9rem">📋 Tasks</span><span style="margin-left:0.5rem;font-size:0.8rem;color:var(--color-text-secondary)">${completedCount}/${totalCount} complete</span></summary>`;
|
|
604
|
-
return `<details class="folded-tool" open>${summary}<div class="folded-tool-body tool-param-todos" style="padding:0.75rem">${items}</div></details>`;
|
|
605
|
-
}
|
|
606
|
-
return this.renderJsonParams(input);
|
|
607
|
-
|
|
608
|
-
case 'Task':
|
|
609
|
-
return `<div class="tool-params">${input.description ? `<div style="font-weight:600;font-size:0.85rem;margin-bottom:0.375rem">${this.escapeHtml(input.description)}</div>` : ''}${input.prompt ? `<div style="font-size:0.8rem;color:var(--color-text-secondary);max-height:100px;overflow-y:auto;white-space:pre-wrap;word-break:break-word">${this.escapeHtml(this.truncateContent(input.prompt, 300))}</div>` : ''}${input.subagent_type ? `<div style="margin-top:0.375rem;font-size:0.7rem"><code style="background:var(--color-bg-secondary);padding:0.125rem 0.375rem;border-radius:0.25rem">${this.escapeHtml(input.subagent_type)}</code></div>` : ''}</div>`;
|
|
610
|
-
|
|
611
|
-
case 'NotebookEdit':
|
|
612
|
-
return `<div class="tool-params">${this.renderFilePath(input.notebook_path)}${input.new_source ? this.renderContentPreview(input.new_source, 'Cell content') : ''}</div>`;
|
|
613
|
-
|
|
614
|
-
case 'dev__execute':
|
|
615
|
-
case 'dev_execute':
|
|
616
|
-
case 'execute': {
|
|
617
|
-
let html = '<div class="tool-params">';
|
|
618
|
-
|
|
619
|
-
if (input.workingDirectory) {
|
|
620
|
-
html += `<div style="margin-bottom:0.5rem;font-size:0.75rem;color:var(--color-text-secondary)"><span style="opacity:0.7">📁</span> ${this.escapeHtml(input.workingDirectory)}</div>`;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
if (input.timeout) {
|
|
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>`;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
if (input.code) {
|
|
628
|
-
const codeLines = input.code.split('\n');
|
|
629
|
-
const lineCount = codeLines.length;
|
|
630
|
-
const truncated = lineCount > 50;
|
|
631
|
-
const displayCode = truncated ? codeLines.slice(0, 50).join('\n') : input.code;
|
|
632
|
-
const lang = input.runtime || 'javascript';
|
|
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>`;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
if (input.commands) {
|
|
637
|
-
const cmds = Array.isArray(input.commands) ? input.commands : [input.commands];
|
|
638
|
-
cmds.forEach(cmd => {
|
|
639
|
-
html += `<div style="margin-top:0.375rem"><div class="tool-param-command"><span class="prompt-char">$</span><span class="command-text">${this.escapeHtml(typeof cmd === 'string' ? cmd : JSON.stringify(cmd))}</span></div></div>`;
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
html += '</div>';
|
|
644
|
-
return html;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
default:
|
|
648
|
-
return this.renderJsonParams(input);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
renderContentPreview(content, label) {
|
|
653
|
-
const maxLen = 500;
|
|
654
|
-
const truncated = content.length > maxLen;
|
|
655
|
-
const displayContent = truncated ? content.substring(0, maxLen) : content;
|
|
656
|
-
const lineCount = content.split('\n').length;
|
|
657
|
-
const codeBody = StreamingRenderer.detectCodeContent(displayContent)
|
|
658
|
-
? StreamingRenderer.renderCodeWithHighlight(displayContent, this.escapeHtml.bind(this), true)
|
|
659
|
-
: `<div class="preview-body">${this.escapeHtml(displayContent)}</div>`;
|
|
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>`;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
renderJsonParams(input) {
|
|
664
|
-
return `<div class="tool-params">${this.renderParametersBeautiful(input)}</div>`;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
getToolUseTitle(toolName, input) {
|
|
668
|
-
const normalizedName = toolName.replace(/^mcp__.*?__/, '');
|
|
669
|
-
if (normalizedName === 'Edit' && input.file_path) {
|
|
670
|
-
const parts = pathSplit(input.file_path);
|
|
671
|
-
const fileName = parts.pop();
|
|
672
|
-
const dir = parts.slice(-2).join('/');
|
|
673
|
-
return dir ? `${dir}/${fileName}` : fileName;
|
|
674
|
-
}
|
|
675
|
-
if (normalizedName === 'Read' && input.file_path) {
|
|
676
|
-
return pathBasename(input.file_path);
|
|
677
|
-
}
|
|
678
|
-
if (normalizedName === 'Write' && input.file_path) {
|
|
679
|
-
return pathBasename(input.file_path);
|
|
680
|
-
}
|
|
681
|
-
if (normalizedName === 'Bash' || normalizedName === 'bash') {
|
|
682
|
-
const cmd = input.command || input.commands || '';
|
|
683
|
-
const cmdText = typeof cmd === 'string' ? cmd : JSON.stringify(cmd);
|
|
684
|
-
return cmdText.length > 60 ? cmdText.substring(0, 57) + '...' : cmdText;
|
|
685
|
-
}
|
|
686
|
-
if (normalizedName === 'Glob' && input.pattern) return input.pattern;
|
|
687
|
-
if (normalizedName === 'Grep' && input.pattern) return input.pattern;
|
|
688
|
-
if (normalizedName === 'WebFetch' && input.url) {
|
|
689
|
-
try { return new URL(input.url).hostname; } catch (e) { return input.url.substring(0, 40); }
|
|
690
|
-
}
|
|
691
|
-
if (normalizedName === 'WebSearch' && input.query) return input.query.substring(0, 50);
|
|
692
|
-
if (input.file_path) return pathBasename(input.file_path);
|
|
693
|
-
if (input.command) {
|
|
694
|
-
const c = typeof input.command === 'string' ? input.command : JSON.stringify(input.command);
|
|
695
|
-
return c.length > 50 ? c.substring(0, 47) + '...' : c;
|
|
696
|
-
}
|
|
697
|
-
if (input.query) return input.query.substring(0, 50);
|
|
698
|
-
return '';
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
getToolUseDisplayName(toolName) {
|
|
702
|
-
const normalized = toolName.replace(/^mcp__.*?__/, '');
|
|
703
|
-
const knownTools = ['Read','Write','Edit','Bash','Glob','Grep','WebFetch','WebSearch','TodoWrite','Task','NotebookEdit'];
|
|
704
|
-
if (knownTools.includes(normalized)) return normalized;
|
|
705
|
-
if (toolName.startsWith('mcp__')) {
|
|
706
|
-
const parts = toolName.split('__');
|
|
707
|
-
return parts.length >= 3 ? parts[2] : parts[parts.length - 1];
|
|
708
|
-
}
|
|
709
|
-
return normalized || toolName;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
renderBlockToolUse(block, context) {
|
|
713
|
-
const toolName = block.name || 'unknown';
|
|
714
|
-
const input = block.input || {};
|
|
715
|
-
|
|
716
|
-
const details = document.createElement('details');
|
|
717
|
-
details.className = 'block-tool-use folded-tool';
|
|
718
|
-
if (block.id) details.dataset.toolUseId = block.id;
|
|
719
|
-
details.dataset.startedAt = Date.now();
|
|
720
|
-
details.classList.add(this._getBlockTypeClass('tool_use'));
|
|
721
|
-
details.classList.add(this._getToolColorClass(toolName));
|
|
722
|
-
const normalizedForOpen = toolName.replace(/^mcp__.*?__/, '');
|
|
723
|
-
if (normalizedForOpen === 'TodoWrite') details.open = true;
|
|
724
|
-
const summary = document.createElement('summary');
|
|
725
|
-
summary.className = 'folded-tool-bar';
|
|
726
|
-
const displayName = this.getToolUseDisplayName(toolName);
|
|
727
|
-
const titleInfo = this.getToolUseTitle(toolName, input);
|
|
728
|
-
summary.innerHTML = `
|
|
729
|
-
<span class="folded-tool-icon">${this.getToolIcon(toolName)}</span>
|
|
730
|
-
<span class="folded-tool-name">${this.escapeHtml(displayName)}</span>
|
|
731
|
-
${titleInfo ? `<span class="folded-tool-desc">${this.escapeHtml(titleInfo)}</span>` : ''}
|
|
732
|
-
`;
|
|
733
|
-
details.appendChild(summary);
|
|
734
|
-
if (Object.keys(input).length > 0) {
|
|
735
|
-
const paramsDiv = document.createElement('div');
|
|
736
|
-
paramsDiv.className = 'folded-tool-body';
|
|
737
|
-
paramsDiv.innerHTML = this.renderSmartParams(toolName, input);
|
|
738
|
-
details.appendChild(paramsDiv);
|
|
739
|
-
}
|
|
740
|
-
return details;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
renderSmartContent(contentStr) {
|
|
744
|
-
const trimmed = contentStr.trim();
|
|
745
|
-
|
|
746
|
-
if (trimmed.startsWith('data:image/')) {
|
|
747
|
-
return `<div style="padding:0.5rem"><img src="${this.escapeHtml(trimmed)}" style="max-width:100%;max-height:24rem;border-radius:0.375rem" loading="lazy"></div>`;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
751
|
-
try {
|
|
752
|
-
const parsed = JSON.parse(trimmed);
|
|
753
|
-
return `<div style="padding:0.625rem 1rem">${this.renderParametersBeautiful(parsed)}</div>`;
|
|
754
|
-
} catch (e) {}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
const lines = trimmed.split('\n');
|
|
758
|
-
const allFilePaths = lines.length > 1 && lines.every(l => {
|
|
759
|
-
const t = l.trim();
|
|
760
|
-
return t === '' || t.startsWith('/') || /^[A-Za-z]:[\\\/]/.test(t);
|
|
761
|
-
});
|
|
762
|
-
if (allFilePaths && lines.filter(l => l.trim()).length > 0) {
|
|
763
|
-
const fileHtml = lines.filter(l => l.trim()).map(l => {
|
|
764
|
-
const p = l.trim();
|
|
765
|
-
const parts = pathSplit(p);
|
|
766
|
-
const name = parts.pop();
|
|
767
|
-
const dir = parts.join('/');
|
|
768
|
-
return `<div style="display:flex;align-items:center;gap:0.375rem;padding:0.1875rem 0;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem"><span style="opacity:0.5">📄</span><span style="color:var(--color-text-secondary)">${this.escapeHtml(dir)}/</span><span style="font-weight:600">${this.escapeHtml(name)}</span></div>`;
|
|
769
|
-
}).join('');
|
|
770
|
-
return `<div style="padding:0.625rem 1rem">${fileHtml}</div>`;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
if (trimmed.length > 1500) {
|
|
774
|
-
return `<div class="result-body collapsed" 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><button class="expand-btn" onclick="this.previousElementSibling.classList.toggle('collapsed');this.textContent=this.textContent==='Show more'?'Show less':'Show more'">Show more</button>`;
|
|
775
|
-
}
|
|
776
|
-
|
|
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>`;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
renderParametersBeautiful(data, depth = 0) {
|
|
781
|
-
if (data === null || data === undefined) return `<span style="color:var(--color-text-secondary);font-style:italic">null</span>`;
|
|
782
|
-
if (typeof data === 'boolean') return `<span style="color:#d97706;font-weight:600">${data}</span>`;
|
|
783
|
-
if (typeof data === 'number') return `<span style="color:#7c3aed;font-weight:600">${data}</span>`;
|
|
784
|
-
|
|
785
|
-
if (typeof data === 'string') {
|
|
786
|
-
if (data.length > 200 && StreamingRenderer.detectCodeContent(data)) {
|
|
787
|
-
const displayData = data.length > 1000 ? data.substring(0, 1000) : data;
|
|
788
|
-
const suffix = data.length > 1000 ? `<div style="font-size:0.7rem;color:var(--color-text-secondary);text-align:center;padding:0.25rem">... ${data.length - 1000} more characters</div>` : '';
|
|
789
|
-
return `<div style="max-height:200px;overflow-y:auto">${StreamingRenderer.renderCodeWithHighlight(displayData, this.escapeHtml.bind(this), true)}${suffix}</div>`;
|
|
790
|
-
}
|
|
791
|
-
if (data.length > 500) {
|
|
792
|
-
const lines = data.split('\n').length;
|
|
793
|
-
return `<div style="font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem;white-space:pre-wrap;word-break:break-all;max-height:200px;overflow-y:auto;background:var(--color-bg-code);color:var(--color-code-text);padding:0.5rem;border-radius:0.375rem;line-height:1.5">${this.escapeHtml(data.substring(0, 1000))}${data.length > 1000 ? '\n... (' + (data.length - 1000) + ' more chars, ' + lines + ' lines)' : ''}</div>`;
|
|
794
|
-
}
|
|
795
|
-
const looksLikePath = data.startsWith('/') || /^[A-Za-z]:[\\\/]/.test(data);
|
|
796
|
-
if (looksLikePath && !data.includes(' ') && data.includes('.')) return this.renderFilePath(data);
|
|
797
|
-
return `<span style="color:var(--color-text-primary)">${this.escapeHtml(data)}</span>`;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
if (Array.isArray(data)) {
|
|
801
|
-
if (data.length === 0) return `<span style="color:var(--color-text-secondary)">[]</span>`;
|
|
802
|
-
if (data.every(i => typeof i === 'string') && data.length <= 20) {
|
|
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>`;
|
|
804
|
-
}
|
|
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>`;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
if (typeof data === 'object') {
|
|
809
|
-
const entries = Object.entries(data);
|
|
810
|
-
if (entries.length === 0) return `<span style="color:var(--color-text-secondary)">{}</span>`;
|
|
811
|
-
return `<div style="display:flex;flex-direction:column;gap:0.375rem;${depth > 0 ? 'padding-left:1rem' : ''}">${entries.map(([k, v]) => `<div style="display:flex;gap:0.5rem;align-items:flex-start"><span style="font-weight:600;font-size:0.75rem;color:#0891b2;flex-shrink:0;min-width:fit-content;font-family:'Monaco','Menlo','Ubuntu Mono',monospace">${this.escapeHtml(k)}</span><div style="flex:1;min-width:0;font-size:0.8rem">${this.renderParametersBeautiful(v, depth + 1)}</div></div>`).join('')}</div>`;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
return `<span>${this.escapeHtml(String(data))}</span>`;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
static renderSmartContentHTML(contentStr, escapeHtml, flat = false) {
|
|
818
|
-
const trimmed = contentStr.trim();
|
|
819
|
-
const esc = escapeHtml || window._escHtml;
|
|
820
|
-
|
|
821
|
-
if (trimmed.startsWith('data:image/')) {
|
|
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>`;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
826
|
-
try {
|
|
827
|
-
const parsed = JSON.parse(trimmed);
|
|
828
|
-
|
|
829
|
-
if (Array.isArray(parsed) && parsed.length > 0 && parsed[0] && parsed[0].type === 'text') {
|
|
830
|
-
const textParts = parsed.filter(b => b.type === 'text' && b.text);
|
|
831
|
-
if (textParts.length > 0) {
|
|
832
|
-
const combined = textParts.map(b => b.text).join('\n');
|
|
833
|
-
return StreamingRenderer.renderSmartContentHTML(combined, esc, flat);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
838
|
-
const imgParts = parsed.filter(b => b.type === 'image' && b.source && b.source.type === 'base64' && b.source.data);
|
|
839
|
-
if (imgParts.length > 0) {
|
|
840
|
-
return imgParts.map(b => {
|
|
841
|
-
const mime = b.source.media_type || 'image/png';
|
|
842
|
-
return `<div style="padding:0.5rem"><img src="data:${esc(mime)};base64,${b.source.data}" style="max-width:100%;max-height:24rem;border-radius:0.375rem"></div>`;
|
|
843
|
-
}).join('');
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
return `<div style="padding:0.5rem 0.75rem">${StreamingRenderer.renderParamsHTML(parsed, 0, esc)}</div>`;
|
|
848
|
-
} catch (e) {
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
const lines = trimmed.split('\n');
|
|
853
|
-
const isCatNOutput = lines.length > 1 && lines[0].match(/^\s*\d+→/);
|
|
854
|
-
const isGrepOutput = lines.length > 1 && lines[0].match(/^\s*\d+-/);
|
|
855
|
-
|
|
856
|
-
if (isCatNOutput || isGrepOutput) {
|
|
857
|
-
const cleanedLines = lines.map(line => {
|
|
858
|
-
if (line === '--') return null;
|
|
859
|
-
|
|
860
|
-
const match = line.match(/^\s*\d+[→\-:](.*)/);
|
|
861
|
-
return match ? match[1] : line;
|
|
862
|
-
}).filter(line => line !== null);
|
|
863
|
-
const cleanedContent = cleanedLines.join('\n');
|
|
864
|
-
|
|
865
|
-
return StreamingRenderer.renderCodeWithHighlight(cleanedContent, esc, flat);
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
const systemReminderPattern = /<system-reminder>([\s\S]*?)<\/system-reminder>/g;
|
|
869
|
-
const systemReminders = [];
|
|
870
|
-
let contentWithoutReminders = trimmed;
|
|
871
|
-
|
|
872
|
-
let reminderMatch;
|
|
873
|
-
while ((reminderMatch = systemReminderPattern.exec(trimmed)) !== null) {
|
|
874
|
-
systemReminders.push(reminderMatch[1].trim());
|
|
875
|
-
contentWithoutReminders = contentWithoutReminders.replace(reminderMatch[0], '');
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
contentWithoutReminders = contentWithoutReminders.trim();
|
|
879
|
-
|
|
880
|
-
const successPatterns = [
|
|
881
|
-
/^Success\s+toolu_[\w]+$/m,
|
|
882
|
-
/^The file .* has been (updated|created|modified)/,
|
|
883
|
-
/^Here's the result of running `cat -n`/,
|
|
884
|
-
/^Applied \d+ edits? to/,
|
|
885
|
-
/^\w+ tool completed successfully/
|
|
886
|
-
];
|
|
887
|
-
|
|
888
|
-
const hasSuccessPattern = successPatterns.some(pattern => pattern.test(contentWithoutReminders));
|
|
889
|
-
|
|
890
|
-
if (hasSuccessPattern) {
|
|
891
|
-
const contentLines = contentWithoutReminders.split('\n');
|
|
892
|
-
let successEndIndex = -1;
|
|
893
|
-
let codeStartIndex = -1;
|
|
894
|
-
|
|
895
|
-
for (let i = 0; i < contentLines.length; i++) {
|
|
896
|
-
const line = contentLines[i];
|
|
897
|
-
if (line.match(/^Success\s+toolu_/)) {
|
|
898
|
-
successEndIndex = i;
|
|
899
|
-
for (let j = i + 1; j < contentLines.length; j++) {
|
|
900
|
-
if (contentLines[j].trim() && !contentLines[j].match(/^The file|^Here's the result/)) {
|
|
901
|
-
codeStartIndex = j;
|
|
902
|
-
break;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
break;
|
|
906
|
-
} else if (line.match(/^The file .* has been|^Applied \d+ edits? to|^Replaced|^Created|^Deleted/)) {
|
|
907
|
-
for (let j = i + 1; j < contentLines.length; j++) {
|
|
908
|
-
if (contentLines[j].match(/^Here's the result|^\s*\d+→/)) {
|
|
909
|
-
if (contentLines[j].match(/^Here's the result/)) {
|
|
910
|
-
codeStartIndex = j + 1;
|
|
911
|
-
} else {
|
|
912
|
-
codeStartIndex = j;
|
|
913
|
-
}
|
|
914
|
-
break;
|
|
915
|
-
} else if (contentLines[j].trim() && !contentLines[j].match(/^cat -n|^Running/)) {
|
|
916
|
-
codeStartIndex = j;
|
|
917
|
-
break;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
if (codeStartIndex === -1) {
|
|
921
|
-
codeStartIndex = i + 2;
|
|
922
|
-
}
|
|
923
|
-
successEndIndex = codeStartIndex - 1;
|
|
924
|
-
break;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
if (codeStartIndex > 0 && codeStartIndex < contentLines.length) {
|
|
929
|
-
const beforeCode = contentLines.slice(0, codeStartIndex).join('\n');
|
|
930
|
-
let codeContent = contentLines.slice(codeStartIndex).join('\n');
|
|
931
|
-
|
|
932
|
-
if (codeContent.match(/^\s*\d+→/m)) {
|
|
933
|
-
const codeLines = codeContent.split('\n');
|
|
934
|
-
codeContent = codeLines.map(line => {
|
|
935
|
-
const match = line.match(/^\s*\d+→(.*)/);
|
|
936
|
-
return match ? match[1] : line;
|
|
937
|
-
}).join('\n');
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
let html = '';
|
|
941
|
-
|
|
942
|
-
if (beforeCode.trim()) {
|
|
943
|
-
html += `<div style="color:var(--color-success);font-weight:600;margin-bottom:0.75rem;font-size:0.9rem">${esc(beforeCode.trim())}</div>`;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
if (codeContent.trim()) {
|
|
947
|
-
html += StreamingRenderer.renderCodeWithHighlight(codeContent, esc, flat);
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
if (systemReminders.length > 0) {
|
|
951
|
-
html += StreamingRenderer.renderSystemReminders(systemReminders, esc);
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
return html;
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
if (systemReminders.length > 0) {
|
|
959
|
-
let html = '';
|
|
960
|
-
|
|
961
|
-
if (contentWithoutReminders) {
|
|
962
|
-
if (StreamingRenderer.detectCodeContent(contentWithoutReminders)) {
|
|
963
|
-
html += StreamingRenderer.renderCodeWithHighlight(contentWithoutReminders, esc, flat);
|
|
964
|
-
} else {
|
|
965
|
-
html += `<pre class="tool-result-pre">${esc(contentWithoutReminders)}</pre>`;
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
html += StreamingRenderer.renderSystemReminders(systemReminders, esc);
|
|
970
|
-
return html;
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
const allFilePaths = lines.length > 1 && lines.every(l => {
|
|
974
|
-
const t = l.trim();
|
|
975
|
-
return t === '' || t.startsWith('/') || /^[A-Za-z]:[\\\/]/.test(t);
|
|
976
|
-
});
|
|
977
|
-
if (allFilePaths && lines.filter(l => l.trim()).length > 0) {
|
|
978
|
-
const fileHtml = lines.filter(l => l.trim()).map(l => {
|
|
979
|
-
const p = l.trim();
|
|
980
|
-
const parts = pathSplit(p);
|
|
981
|
-
const name = parts.pop();
|
|
982
|
-
const dir = parts.join('/');
|
|
983
|
-
return `<div style="display:flex;align-items:center;gap:0.375rem;padding:0.1875rem 0;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem"><span style="opacity:0.5">📄</span><span style="color:var(--color-text-secondary)">${esc(dir)}/</span><span style="font-weight:600">${esc(name)}</span></div>`;
|
|
984
|
-
}).join('');
|
|
985
|
-
return `<div style="padding:0.625rem 1rem">${fileHtml}</div>`;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
const looksLikeCode = StreamingRenderer.detectCodeContent(trimmed);
|
|
989
|
-
if (looksLikeCode) {
|
|
990
|
-
return StreamingRenderer.renderCodeWithHighlight(trimmed, esc, flat);
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
const displayContent = trimmed.length > 2000 ? trimmed.substring(0, 2000) + '\n... (truncated)' : trimmed;
|
|
994
|
-
return `<pre class="tool-result-pre">${esc(displayContent)}</pre>`;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
static renderSystemReminders(reminders, esc) {
|
|
998
|
-
if (!reminders || reminders.length === 0) return '';
|
|
999
|
-
|
|
1000
|
-
const reminderHtml = reminders.map(reminder => {
|
|
1001
|
-
const lines = reminder.split('\n').filter(l => l.trim());
|
|
1002
|
-
const formattedLines = lines.map(line => {
|
|
1003
|
-
if (line.includes('IMPORTANT:') || line.includes('WARNING:')) {
|
|
1004
|
-
return `<div style="font-weight:600;color:var(--color-warning);margin:0.25rem 0">${esc(line)}</div>`;
|
|
1005
|
-
}
|
|
1006
|
-
return `<div style="margin:0.125rem 0">${esc(line)}</div>`;
|
|
1007
|
-
}).join('');
|
|
1008
|
-
|
|
1009
|
-
return formattedLines;
|
|
1010
|
-
}).join('');
|
|
1011
|
-
|
|
1012
|
-
return `
|
|
1013
|
-
<div style="margin-top:1rem;padding:0.75rem;background:var(--color-bg-secondary);border-left:3px solid var(--color-info);border-radius:0.25rem;font-size:0.8rem;color:var(--color-text-secondary)">
|
|
1014
|
-
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.5rem">
|
|
1015
|
-
<span style="color:var(--color-info)">ℹ</span>
|
|
1016
|
-
<span style="font-weight:600;font-size:0.85rem;color:var(--color-text-primary)">System Reminder</span>
|
|
1017
|
-
</div>
|
|
1018
|
-
${reminderHtml}
|
|
1019
|
-
</div>
|
|
1020
|
-
`;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
static detectCodeContent(content) {
|
|
1024
|
-
const codePatterns = [
|
|
1025
|
-
/^\s*(function|const|let|var|class|import|export|async|await)/m, // JavaScript
|
|
1026
|
-
/^\s*(def|class|import|from|if __name__|lambda|async def)/m, // Python
|
|
1027
|
-
/^\s*(public|private|protected|class|interface|package|import)/m, // Java/TypeScript
|
|
1028
|
-
/^\s*(<\?php|namespace|use|trait)/m, // PHP
|
|
1029
|
-
/^\s*(#include|int main|void|struct|typedef)/m, // C/C++
|
|
1030
|
-
/[{}\[\];()]/, // Brackets and semicolons
|
|
1031
|
-
/=>|->|::/, // Arrow functions, pointers
|
|
1032
|
-
];
|
|
1033
|
-
|
|
1034
|
-
return codePatterns.some(pattern => pattern.test(content));
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
static renderCodeWithHighlight(code, esc, flat = false) {
|
|
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";
|
|
1039
|
-
const codeHtml = `<pre style="${preStyle}"><code class="lazy-hl">${esc(code)}</code></pre>`;
|
|
1040
|
-
if (flat) return codeHtml;
|
|
1041
|
-
const lineCount = code.split('\n').length;
|
|
1042
|
-
const summaryLabel = `code - ${lineCount} line${lineCount !== 1 ? 's' : ''}`;
|
|
1043
|
-
return `<details class="collapsible-code"><summary class="collapsible-code-summary">${summaryLabel}</summary>${codeHtml}</details>`;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
static _setupGlobalLazyHL() {
|
|
1047
|
-
if (StreamingRenderer._lazyHLSetup) return;
|
|
1048
|
-
StreamingRenderer._lazyHLSetup = true;
|
|
1049
|
-
const root = document.getElementById('output-scroll') || document.body;
|
|
1050
|
-
root.addEventListener('toggle', (e) => {
|
|
1051
|
-
const details = e.target;
|
|
1052
|
-
if (!details.open || details.tagName !== 'DETAILS') return;
|
|
1053
|
-
const codeEls = details.querySelectorAll('code.lazy-hl');
|
|
1054
|
-
if (codeEls.length === 0) return;
|
|
1055
|
-
if (typeof hljs === 'undefined') return;
|
|
1056
|
-
for (const el of codeEls) {
|
|
1057
|
-
try {
|
|
1058
|
-
const raw = el.textContent;
|
|
1059
|
-
const result = hljs.highlightAuto(raw);
|
|
1060
|
-
el.classList.remove('lazy-hl');
|
|
1061
|
-
el.classList.add('hljs');
|
|
1062
|
-
el.innerHTML = result.value;
|
|
1063
|
-
} catch (_) {}
|
|
1064
|
-
}
|
|
1065
|
-
}, true);
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
static getToolDisplayName(toolName) {
|
|
1069
|
-
const normalized = toolName.replace(/^mcp__.*?__/, '');
|
|
1070
|
-
const knownTools = ['Read','Write','Edit','Bash','Glob','Grep','WebFetch','WebSearch','TodoWrite','Task','NotebookEdit'];
|
|
1071
|
-
if (knownTools.includes(normalized)) return normalized;
|
|
1072
|
-
if (toolName.startsWith('mcp__')) {
|
|
1073
|
-
const parts = toolName.split('__');
|
|
1074
|
-
return parts.length >= 3 ? parts[2] : parts[parts.length - 1];
|
|
1075
|
-
}
|
|
1076
|
-
return normalized || toolName;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
static getToolTitle(toolName, input) {
|
|
1080
|
-
const n = toolName.replace(/^mcp__.*?__/, '');
|
|
1081
|
-
if (n === 'Edit' && input.file_path) { const p = pathSplit(input.file_path); const f = p.pop(); const d = p.slice(-2).join('/'); return d ? d+'/'+f : f; }
|
|
1082
|
-
if (n === 'Read' && input.file_path) return pathBasename(input.file_path);
|
|
1083
|
-
if (n === 'Write' && input.file_path) return pathBasename(input.file_path);
|
|
1084
|
-
if ((n === 'Bash' || n === 'bash') && (input.command || input.commands)) { const c = typeof (input.command||input.commands) === 'string' ? (input.command||input.commands) : JSON.stringify(input.command||input.commands); return c.length > 60 ? c.substring(0,57)+'...' : c; }
|
|
1085
|
-
if (n === 'Glob' && input.pattern) return input.pattern;
|
|
1086
|
-
if (n === 'Grep' && input.pattern) return input.pattern;
|
|
1087
|
-
if (n === 'WebFetch' && input.url) { try { return new URL(input.url).hostname; } catch(e) { return input.url.substring(0,40); } }
|
|
1088
|
-
if (n === 'WebSearch' && input.query) return input.query.substring(0,50);
|
|
1089
|
-
if (input.file_path) return pathBasename(input.file_path);
|
|
1090
|
-
if (input.command) { const c = typeof input.command === 'string' ? input.command : JSON.stringify(input.command); return c.length > 50 ? c.substring(0,47)+'...' : c; }
|
|
1091
|
-
if (input.query) return input.query.substring(0,50);
|
|
1092
|
-
return '';
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
static renderParamsHTML(data, depth, esc) {
|
|
1096
|
-
if (data === null || data === undefined) return `<span style="color:var(--color-text-secondary);font-style:italic">null</span>`;
|
|
1097
|
-
if (typeof data === 'boolean') return `<span style="color:#d97706;font-weight:600">${data}</span>`;
|
|
1098
|
-
if (typeof data === 'number') return `<span style="color:#7c3aed;font-weight:600">${data}</span>`;
|
|
1099
|
-
|
|
1100
|
-
if (typeof data === 'string') {
|
|
1101
|
-
if (data.length > 200 && StreamingRenderer.detectCodeContent(data)) {
|
|
1102
|
-
const displayData = data.length > 1000 ? data.substring(0, 1000) : data;
|
|
1103
|
-
const suffix = data.length > 1000 ? `<div style="font-size:0.7rem;color:var(--color-text-secondary);text-align:center;padding:0.25rem">... ${data.length - 1000} more characters</div>` : '';
|
|
1104
|
-
return `<div style="max-height:200px;overflow-y:auto">${StreamingRenderer.renderCodeWithHighlight(displayData, esc, true)}${suffix}</div>`;
|
|
1105
|
-
}
|
|
1106
|
-
if (data.length > 500) {
|
|
1107
|
-
return `<div style="font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem;white-space:pre-wrap;word-break:break-all;max-height:200px;overflow-y:auto;background:var(--color-bg-code);color:var(--color-code-text);padding:0.5rem;border-radius:0.375rem;line-height:1.5">${esc(data.substring(0, 1000))}${data.length > 1000 ? '\n... (' + (data.length - 1000) + ' more chars)' : ''}</div>`;
|
|
1108
|
-
}
|
|
1109
|
-
const looksLikePath = /^[A-Za-z]:[\\\/]/.test(data) || data.startsWith('/');
|
|
1110
|
-
if (looksLikePath && !data.includes(' ') && data.includes('.')) {
|
|
1111
|
-
const parts = pathSplit(data);
|
|
1112
|
-
const name = parts.pop();
|
|
1113
|
-
const dir = parts.join('/');
|
|
1114
|
-
return `<div style="display:flex;align-items:center;gap:0.375rem;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.8rem"><span style="opacity:0.5">📄</span><span style="color:var(--color-text-secondary)">${esc(dir)}/</span><span style="font-weight:600">${esc(name)}</span></div>`;
|
|
1115
|
-
}
|
|
1116
|
-
return `<span style="color:var(--color-text-primary)">${esc(data)}</span>`;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
if (Array.isArray(data)) {
|
|
1120
|
-
if (data.length === 0) return `<span style="color:var(--color-text-secondary)">[]</span>`;
|
|
1121
|
-
if (data.every(i => typeof i === 'string') && data.length <= 20) {
|
|
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>`;
|
|
1123
|
-
}
|
|
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>`;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
if (typeof data === 'object') {
|
|
1128
|
-
const entries = Object.entries(data);
|
|
1129
|
-
if (entries.length === 0) return `<span style="color:var(--color-text-secondary)">{}</span>`;
|
|
1130
|
-
return `<div style="display:flex;flex-direction:column;gap:0.375rem;${depth > 0 ? 'padding-left:1rem' : ''}">${entries.map(([k, v]) => `<div style="display:flex;gap:0.5rem;align-items:flex-start"><span style="font-weight:600;font-size:0.75rem;color:#0891b2;flex-shrink:0;min-width:fit-content;font-family:'Monaco','Menlo','Ubuntu Mono',monospace">${esc(k)}</span><div style="flex:1;min-width:0;font-size:0.8rem">${StreamingRenderer.renderParamsHTML(v, depth + 1, esc)}</div></div>`).join('')}</div>`;
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
return `<span>${esc(String(data))}</span>`;
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
renderBlockToolResult(block, context) {
|
|
1137
|
-
const content = block.content || '';
|
|
1138
|
-
const toolName = block.tool_name || block.name || '';
|
|
1139
|
-
|
|
1140
|
-
if (toolName.includes('TodoWrite') || (typeof content === 'object' && Array.isArray(content?.todos))) {
|
|
1141
|
-
return null;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
|
|
1145
|
-
const isError = (block.is_error || false) && !contentStr.trimStart().startsWith('exec ran successfully.');
|
|
1146
|
-
|
|
1147
|
-
const container = document.createElement('div');
|
|
1148
|
-
container.className = 'tool-result-pending';
|
|
1149
|
-
container.dataset.eventType = 'tool_result';
|
|
1150
|
-
container.dataset.isError = isError ? '1' : '0';
|
|
1151
|
-
if (block.tool_use_id) container.dataset.toolUseId = block.tool_use_id;
|
|
1152
|
-
container.style.display = 'none';
|
|
1153
|
-
|
|
1154
|
-
const renderedContent = StreamingRenderer.renderSmartContentHTML(contentStr, this.escapeHtml.bind(this), true);
|
|
1155
|
-
container.innerHTML = renderedContent;
|
|
1156
|
-
|
|
1157
|
-
return container;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
renderBlockImage(block, context) {
|
|
1161
|
-
const div = document.createElement('div');
|
|
1162
|
-
div.className = 'block-image';
|
|
1163
|
-
div.classList.add(this._getBlockTypeClass('image'));
|
|
1164
|
-
|
|
1165
|
-
let src = block.image || block.src || '';
|
|
1166
|
-
const alt = block.alt || 'Image';
|
|
1167
|
-
|
|
1168
|
-
if (block.data && block.media_type) {
|
|
1169
|
-
src = `data:${block.media_type};base64,${block.data}`;
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
div.innerHTML = `
|
|
1173
|
-
<img src="${this.escapeHtml(src)}" alt="${this.escapeHtml(alt)}" loading="lazy">
|
|
1174
|
-
${block.alt ? `<div class="image-caption">${this.escapeHtml(alt)}</div>` : ''}
|
|
1175
|
-
`;
|
|
1176
|
-
|
|
1177
|
-
return div;
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
renderBlockBash(block, context) {
|
|
1181
|
-
const div = document.createElement('div');
|
|
1182
|
-
div.className = 'block-bash';
|
|
1183
|
-
div.classList.add(this._getBlockTypeClass('bash'));
|
|
1184
|
-
|
|
1185
|
-
const command = block.command || block.code || '';
|
|
1186
|
-
const output = block.output || '';
|
|
1187
|
-
|
|
1188
|
-
let html = `<div class="bash-command"><span class="prompt">$</span><code>${this.escapeHtml(command)}</code></div>`;
|
|
1189
|
-
|
|
1190
|
-
if (output) {
|
|
1191
|
-
if (StreamingRenderer.detectCodeContent(output)) {
|
|
1192
|
-
html += StreamingRenderer.renderCodeWithHighlight(output, this.escapeHtml.bind(this), true);
|
|
1193
|
-
} else {
|
|
1194
|
-
html += `<pre class="bash-output"><code>${this.escapeHtml(output)}</code></pre>`;
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
div.innerHTML = html;
|
|
1199
|
-
return div;
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
renderBlockSystem(block, context) {
|
|
1203
|
-
if (block.subtype === 'compact_boundary') return this.renderCompactBoundary(block);
|
|
1204
|
-
if (block.subtype === 'turn_duration') return this.renderTurnDuration(block);
|
|
1205
|
-
if (!block.model && !block.cwd && !block.tools) return null;
|
|
1206
|
-
const details = document.createElement('details');
|
|
1207
|
-
details.className = 'folded-tool folded-tool-info permanently-expanded';
|
|
1208
|
-
details.dataset.eventType = 'system';
|
|
1209
|
-
details.classList.add(this._getBlockTypeClass('system'));
|
|
1210
|
-
const desc = block.model ? this.escapeHtml(block.model) : 'Session';
|
|
1211
|
-
const summary = document.createElement('summary');
|
|
1212
|
-
summary.className = 'folded-tool-bar';
|
|
1213
|
-
summary.innerHTML = `
|
|
1214
|
-
<span class="folded-tool-icon"><svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/></svg></span>
|
|
1215
|
-
<span class="folded-tool-name">Session</span>
|
|
1216
|
-
<span class="folded-tool-desc">${desc}</span>
|
|
1217
|
-
`;
|
|
1218
|
-
details.appendChild(summary);
|
|
1219
|
-
const body = document.createElement('div');
|
|
1220
|
-
body.className = 'folded-tool-body block-system';
|
|
1221
|
-
body.innerHTML = `
|
|
1222
|
-
<div class="system-body">
|
|
1223
|
-
${block.model ? `<div class="sys-field"><span class="sys-label">Model</span><span class="sys-value"><code>${this.escapeHtml(block.model)}</code></span></div>` : ''}
|
|
1224
|
-
${block.cwd ? `<div class="sys-field"><span class="sys-label">Directory</span><span class="sys-value"><code>${this.escapeHtml(block.cwd)}</code></span></div>` : ''}
|
|
1225
|
-
${block.session_id ? `<div class="sys-field"><span class="sys-label">Session</span><span class="sys-value"><code>${this.escapeHtml(block.session_id)}</code></span></div>` : ''}
|
|
1226
|
-
${block.tools && Array.isArray(block.tools) ? `<div class="sys-field" style="flex-direction:column;gap:0.375rem"><span class="sys-label">Tools (${block.tools.length})</span><div class="tools-list">${block.tools.map(t => `<span class="tool-badge">${this.escapeHtml(t)}</span>`).join('')}</div></div>` : ''}
|
|
1227
|
-
</div>
|
|
1228
|
-
`;
|
|
1229
|
-
details.appendChild(body);
|
|
1230
|
-
return details;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
renderBlockResult(block, context) {
|
|
1234
|
-
const isError = block.is_error || false;
|
|
1235
|
-
const duration = block.duration_ms ? (block.duration_ms / 1000).toFixed(1) + 's' : '';
|
|
1236
|
-
const cost = block.total_cost_usd ? '$' + block.total_cost_usd.toFixed(4) : '';
|
|
1237
|
-
const turns = block.num_turns || '';
|
|
1238
|
-
const statsDesc = [duration, cost, turns ? turns + ' turns' : ''].filter(Boolean).join(' / ');
|
|
1239
|
-
|
|
1240
|
-
const details = document.createElement('details');
|
|
1241
|
-
details.className = isError ? 'folded-tool folded-tool-error permanently-expanded' : 'folded-tool permanently-expanded';
|
|
1242
|
-
details.dataset.eventType = 'result';
|
|
1243
|
-
details.classList.add(this._getBlockTypeClass(isError ? 'error' : 'result'));
|
|
1244
|
-
|
|
1245
|
-
const iconSvg = isError
|
|
1246
|
-
? '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>'
|
|
1247
|
-
: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>';
|
|
1248
|
-
|
|
1249
|
-
const summary = document.createElement('summary');
|
|
1250
|
-
summary.className = 'folded-tool-bar';
|
|
1251
|
-
summary.innerHTML = `
|
|
1252
|
-
<span class="folded-tool-icon">${iconSvg}</span>
|
|
1253
|
-
<span class="folded-tool-name">${isError ? 'Failed' : 'Complete'}</span>
|
|
1254
|
-
<span class="folded-tool-desc">${this.escapeHtml(statsDesc)}</span>
|
|
1255
|
-
`;
|
|
1256
|
-
details.appendChild(summary);
|
|
1257
|
-
|
|
1258
|
-
if (block.result || duration || cost || turns) {
|
|
1259
|
-
const body = document.createElement('div');
|
|
1260
|
-
body.className = 'folded-tool-body';
|
|
1261
|
-
let bodyHtml = '';
|
|
1262
|
-
if (duration || cost || turns) {
|
|
1263
|
-
bodyHtml += `<div class="block-result"><div class="result-stats">
|
|
1264
|
-
${duration ? `<div class="result-stat"><span class="stat-icon">⏲</span><span class="stat-value">${this.escapeHtml(duration)}</span><span class="stat-label">duration</span></div>` : ''}
|
|
1265
|
-
${cost ? `<div class="result-stat"><span class="stat-icon">💰</span><span class="stat-value">${this.escapeHtml(cost)}</span><span class="stat-label">cost</span></div>` : ''}
|
|
1266
|
-
${turns ? `<div class="result-stat"><span class="stat-icon">🔄</span><span class="stat-value">${this.escapeHtml(String(turns))}</span><span class="stat-label">turns</span></div>` : ''}
|
|
1267
|
-
</div></div>`;
|
|
1268
|
-
}
|
|
1269
|
-
if (block.result) {
|
|
1270
|
-
const r = typeof block.result === 'string' ? block.result : JSON.stringify(block.result, null, 2);
|
|
1271
|
-
const rendered = this.containsHtmlTags(r) ? '<div class="html-content">' + this.sanitizeHtml(r) + '</div>' : `<div style="font-size:0.8rem;white-space:pre-wrap;word-break:break-word;line-height:1.5">${this.escapeHtml(r)}</div>`;
|
|
1272
|
-
bodyHtml += rendered;
|
|
1273
|
-
}
|
|
1274
|
-
body.innerHTML = bodyHtml;
|
|
1275
|
-
details.appendChild(body);
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
return details;
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
renderBlockToolStatus(block, context) {
|
|
1282
|
-
const status = block.status || 'pending';
|
|
1283
|
-
const statusIcons = {
|
|
1284
|
-
pending: '<svg viewBox="0 0 20 20" fill="currentColor" style="color:var(--color-text-secondary)"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/></svg>',
|
|
1285
|
-
in_progress: '<svg viewBox="0 0 20 20" fill="currentColor" class="animate-spin" style="color:var(--color-info)"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/></svg>'
|
|
1286
|
-
};
|
|
1287
|
-
const statusLabels = {
|
|
1288
|
-
pending: 'Pending',
|
|
1289
|
-
in_progress: 'Running...'
|
|
1290
|
-
};
|
|
1291
|
-
|
|
1292
|
-
const div = document.createElement('div');
|
|
1293
|
-
div.className = 'block-tool-status';
|
|
1294
|
-
div.dataset.toolUseId = block.tool_use_id || '';
|
|
1295
|
-
div.classList.add(this._getBlockTypeClass('tool_status'));
|
|
1296
|
-
div.innerHTML = `
|
|
1297
|
-
<div style="display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0.5rem;font-size:0.75rem;color:var(--color-text-secondary)">
|
|
1298
|
-
${statusIcons[status] || statusIcons.pending}
|
|
1299
|
-
<span>${statusLabels[status] || this.escapeHtml(status)}</span>
|
|
1300
|
-
</div>
|
|
1301
|
-
`;
|
|
1302
|
-
return div;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
renderBlockHookProgress(block, context) {
|
|
1306
|
-
const hookEvent = block.hookEvent || block.hookName || '';
|
|
1307
|
-
const hookIcons = {
|
|
1308
|
-
PreToolUse: '<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.75rem;height:0.75rem"><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>',
|
|
1309
|
-
PostToolUse: '<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.75rem;height:0.75rem"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>',
|
|
1310
|
-
Stop: '<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.75rem;height:0.75rem"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V8a1 1 0 00-1-1H8z" clip-rule="evenodd"/></svg>'
|
|
1311
|
-
};
|
|
1312
|
-
const icon = hookIcons[hookEvent.split(':')[0]] || hookIcons.PreToolUse;
|
|
1313
|
-
const label = hookEvent.includes(':') ? hookEvent.split(':').pop() : hookEvent;
|
|
1314
|
-
const div = document.createElement('div');
|
|
1315
|
-
div.className = 'block-hook-progress';
|
|
1316
|
-
div.dataset.hookEvent = hookEvent;
|
|
1317
|
-
div.style.cssText = 'display:none';
|
|
1318
|
-
div.innerHTML = '<span class="hook-badge" style="display:inline-flex;align-items:center;gap:0.25rem;padding:0.125rem 0.5rem;font-size:0.65rem;color:var(--color-text-secondary);opacity:0.5">' + icon + '<span>' + this.escapeHtml(label) + '</span></span>';
|
|
1319
|
-
return div;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
renderBlockUsage(block, context) {
|
|
1323
|
-
const usage = block.usage || {};
|
|
1324
|
-
const used = usage.used || 0;
|
|
1325
|
-
const size = usage.size || 0;
|
|
1326
|
-
const cost = usage.cost ? '$' + usage.cost.toFixed(4) : '';
|
|
1327
|
-
|
|
1328
|
-
const div = document.createElement('div');
|
|
1329
|
-
div.className = 'block-usage';
|
|
1330
|
-
div.classList.add(this._getBlockTypeClass('usage'));
|
|
1331
|
-
div.innerHTML = `
|
|
1332
|
-
<div style="display:flex;gap:1rem;padding:0.25rem 0.5rem;font-size:0.7rem;color:var(--color-text-secondary);background:var(--color-bg-secondary);border-radius:0.25rem">
|
|
1333
|
-
${used ? `<span><strong>Used:</strong> ${used.toLocaleString()}</span>` : ''}
|
|
1334
|
-
${size ? `<span><strong>Context:</strong> ${size.toLocaleString()}</span>` : ''}
|
|
1335
|
-
${cost ? `<span><strong>Cost:</strong> ${cost}</span>` : ''}
|
|
1336
|
-
</div>
|
|
1337
|
-
`;
|
|
1338
|
-
return div;
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
renderBlockPlan(block, context) {
|
|
1342
|
-
const entries = block.entries || [];
|
|
1343
|
-
if (entries.length === 0) return null;
|
|
1344
|
-
|
|
1345
|
-
const priorityColors = {
|
|
1346
|
-
high: '#ef4444',
|
|
1347
|
-
medium: '#f59e0b',
|
|
1348
|
-
low: '#6b7280'
|
|
1349
|
-
};
|
|
1350
|
-
const statusIcons = {
|
|
1351
|
-
pending: '○',
|
|
1352
|
-
in_progress: '◐',
|
|
1353
|
-
completed: '●'
|
|
1354
|
-
};
|
|
1355
|
-
|
|
1356
|
-
const div = document.createElement('div');
|
|
1357
|
-
div.className = 'block-plan';
|
|
1358
|
-
div.classList.add(this._getBlockTypeClass('plan'));
|
|
1359
|
-
div.innerHTML = `
|
|
1360
|
-
<details class="folded-tool folded-tool-info">
|
|
1361
|
-
<summary class="folded-tool-bar">
|
|
1362
|
-
<span class="folded-tool-icon"><svg viewBox="0 0 20 20" fill="currentColor"><path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/><path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"/></svg></span>
|
|
1363
|
-
<span class="folded-tool-name">Plan</span>
|
|
1364
|
-
<span class="folded-tool-desc">${entries.length} tasks</span>
|
|
1365
|
-
</summary>
|
|
1366
|
-
<div class="folded-tool-body">
|
|
1367
|
-
<div style="display:flex;flex-direction:column;gap:0.375rem">
|
|
1368
|
-
${entries.map(e => `
|
|
1369
|
-
<div style="display:flex;align-items:center;gap:0.5rem;font-size:0.8rem">
|
|
1370
|
-
<span style="color:${priorityColors[e.priority] || priorityColors.low}">${statusIcons[e.status] || statusIcons.pending}</span>
|
|
1371
|
-
<span style="${e.status === 'completed' ? 'text-decoration:line-through;opacity:0.6' : ''}">${this.escapeHtml(e.content || '')}</span>
|
|
1372
|
-
</div>
|
|
1373
|
-
`).join('')}
|
|
1374
|
-
</div>
|
|
1375
|
-
</div>
|
|
1376
|
-
</details>
|
|
1377
|
-
`;
|
|
1378
|
-
return div;
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
renderBlockPremature(block, context) {
|
|
1382
|
-
const div = document.createElement('div');
|
|
1383
|
-
div.className = 'folded-tool folded-tool-error block-premature';
|
|
1384
|
-
div.classList.add(this._getBlockTypeClass('premature'));
|
|
1385
|
-
const code = block.exitCode != null ? ` (exit ${block.exitCode})` : '';
|
|
1386
|
-
const stderrDisplay = block.stderrText ? `<div class="folded-tool-content" style="margin-top:8px;padding:8px;background:rgba(0,0,0,0.05);border-radius:4px;font-family:monospace;font-size:0.9em;white-space:pre-wrap;">${this.escapeHtml(block.stderrText)}</div>` : '';
|
|
1387
|
-
div.innerHTML = `
|
|
1388
|
-
<div class="folded-tool-bar" style="background:rgba(245,158,11,0.1)">
|
|
1389
|
-
<span class="folded-tool-icon" style="color:#f59e0b"><svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg></span>
|
|
1390
|
-
<span class="folded-tool-name" style="color:#f59e0b">ACP Ended Prematurely${this.escapeHtml(code)}</span>
|
|
1391
|
-
<span class="folded-tool-desc">${this.escapeHtml(block.error || 'Process exited without output')}</span>
|
|
1392
|
-
</div>
|
|
1393
|
-
${stderrDisplay}
|
|
1394
|
-
`;
|
|
1395
|
-
return div;
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
renderBlockGeneric(block, context) {
|
|
1399
|
-
const div = document.createElement('div');
|
|
1400
|
-
div.className = 'block-generic';
|
|
1401
|
-
div.classList.add(this._getBlockTypeClass('generic'));
|
|
1402
|
-
|
|
1403
|
-
const fieldsHtml = Object.entries(block)
|
|
1404
|
-
.filter(([key]) => key !== 'type')
|
|
1405
|
-
.map(([key, value]) => {
|
|
1406
|
-
let displayValue;
|
|
1407
|
-
if (typeof value === 'string') {
|
|
1408
|
-
displayValue = value.length > 200 ? value.substring(0, 200) + '...' : value;
|
|
1409
|
-
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
1410
|
-
displayValue = String(value);
|
|
1411
|
-
} else {
|
|
1412
|
-
displayValue = JSON.stringify(value, null, 2);
|
|
1413
|
-
if (displayValue.length > 200) displayValue = displayValue.substring(0, 200) + '...';
|
|
1414
|
-
}
|
|
1415
|
-
return `<div class="generic-field"><span class="field-key">${this.escapeHtml(key)}:</span><span class="field-value">${this.escapeHtml(displayValue)}</span></div>`;
|
|
1416
|
-
}).join('');
|
|
1417
|
-
|
|
1418
|
-
div.innerHTML = `
|
|
1419
|
-
<div class="generic-type">${this.escapeHtml(block.type)}</div>
|
|
1420
|
-
<div class="generic-fields">${fieldsHtml}</div>
|
|
1421
|
-
`;
|
|
1422
|
-
|
|
1423
|
-
return div;
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
renderBlockError(block, error) {
|
|
1427
|
-
const div = document.createElement('div');
|
|
1428
|
-
div.className = 'block-error';
|
|
1429
|
-
div.classList.add(this._getBlockTypeClass('error'));
|
|
1430
|
-
|
|
1431
|
-
div.innerHTML = `
|
|
1432
|
-
<div style="display:flex;align-items:flex-start;gap:0.625rem">
|
|
1433
|
-
<svg viewBox="0 0 20 20" fill="currentColor" style="color:#ef4444;flex-shrink:0;margin-top:0.125rem">
|
|
1434
|
-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
1435
|
-
</svg>
|
|
1436
|
-
<div>
|
|
1437
|
-
<div style="font-weight:600;color:#991b1b">Render Error</div>
|
|
1438
|
-
<div style="font-size:0.85rem;color:#7f1d1d;margin-top:0.25rem">${this.escapeHtml(error.message)}</div>
|
|
1439
|
-
</div>
|
|
1440
|
-
</div>
|
|
1441
|
-
`;
|
|
1442
|
-
|
|
1443
|
-
return div;
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
renderStreamingStart(event) {
|
|
1447
|
-
const div = document.createElement('div');
|
|
1448
|
-
div.className = 'event-streaming-start card mb-3 p-4 alert alert-info';
|
|
1449
|
-
div.dataset.eventId = event.id || event.sessionId || '';
|
|
1450
|
-
div.dataset.eventType = 'streaming_start';
|
|
1451
|
-
|
|
1452
|
-
const time = new Date(event.timestamp).toLocaleTimeString();
|
|
1453
|
-
div.innerHTML = `
|
|
1454
|
-
<div class="flex items-center gap-2">
|
|
1455
|
-
<svg class="w-5 h-5 text-info animate-spin" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1456
|
-
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" opacity="0.25"></circle>
|
|
1457
|
-
<path d="M4 12a8 8 0 018-8" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
|
|
1458
|
-
</svg>
|
|
1459
|
-
<div class="flex-1">
|
|
1460
|
-
<h4 class="font-semibold text-info-content">Streaming Started</h4>
|
|
1461
|
-
<p class="text-sm text-info-content/70">${time}</p>
|
|
1462
|
-
</div>
|
|
1463
|
-
</div>
|
|
1464
|
-
`;
|
|
1465
|
-
return div;
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
renderStreamingProgress(event) {
|
|
1469
|
-
if (event.block) {
|
|
1470
|
-
return this.renderBlock(event.block, event);
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
const div = document.createElement('div');
|
|
1474
|
-
div.className = 'event-streaming-progress mb-2 p-2';
|
|
1475
|
-
div.dataset.eventId = event.id || '';
|
|
1476
|
-
div.dataset.eventType = 'streaming_progress';
|
|
1477
|
-
|
|
1478
|
-
const percentage = event.progress || 0;
|
|
1479
|
-
div.innerHTML = `
|
|
1480
|
-
<div class="flex items-center gap-2 text-sm">
|
|
1481
|
-
<span class="text-secondary">${percentage}%</span>
|
|
1482
|
-
<div class="progress progress-primary flex-1">
|
|
1483
|
-
<div class="progress-bar" style="width: ${percentage}%"></div>
|
|
1484
|
-
</div>
|
|
1485
|
-
</div>
|
|
1486
|
-
`;
|
|
1487
|
-
return div;
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
renderStreamingComplete(event) {
|
|
1491
|
-
const div = document.createElement('div');
|
|
1492
|
-
div.className = 'event-streaming-complete card mb-3 p-4 alert alert-success rounded-lg';
|
|
1493
|
-
div.dataset.eventId = event.id || event.sessionId || '';
|
|
1494
|
-
div.dataset.eventType = 'streaming_complete';
|
|
1495
|
-
|
|
1496
|
-
const time = new Date(event.timestamp).toLocaleTimeString();
|
|
1497
|
-
const eventCount = event.eventCount || 0;
|
|
1498
|
-
|
|
1499
|
-
div.innerHTML = `
|
|
1500
|
-
<div class="flex items-start gap-3">
|
|
1501
|
-
<div class="flex-shrink-0 mt-0.5">
|
|
1502
|
-
<svg class="w-6 h-6 text-success animate-bounce" fill="currentColor" viewBox="0 0 20 20">
|
|
1503
|
-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
|
1504
|
-
</svg>
|
|
1505
|
-
</div>
|
|
1506
|
-
<div class="flex-1">
|
|
1507
|
-
<h4 class="font-bold text-lg text-success-content">✨ Execution Complete</h4>
|
|
1508
|
-
<div class="mt-2 grid grid-cols-2 gap-3 text-sm">
|
|
1509
|
-
<div>
|
|
1510
|
-
<span class="text-success font-semibold">${eventCount}</span>
|
|
1511
|
-
<span class="text-success/70">events processed</span>
|
|
1512
|
-
</div>
|
|
1513
|
-
<div class="text-right">
|
|
1514
|
-
<span class="text-success/70">${time}</span>
|
|
1515
|
-
</div>
|
|
1516
|
-
</div>
|
|
1517
|
-
</div>
|
|
1518
|
-
</div>
|
|
1519
|
-
`;
|
|
1520
|
-
return div;
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
detectBase64Image(content) {
|
|
1524
|
-
if (!content || typeof content !== 'string') return null;
|
|
1525
|
-
const trimmed = content.trim();
|
|
1526
|
-
const signatures = {
|
|
1527
|
-
'png': /^iVBORw0KGgo/,
|
|
1528
|
-
'jpeg': /^\/9j\/4AAQ/,
|
|
1529
|
-
'webp': /^UklGRi/,
|
|
1530
|
-
'gif': /^R0lGODlh/
|
|
1531
|
-
};
|
|
1532
|
-
for (const [type, pattern] of Object.entries(signatures)) {
|
|
1533
|
-
if (pattern.test(trimmed)) {
|
|
1534
|
-
return { type, isBase64: true, data: trimmed };
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
return null;
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
renderFileRead(event) {
|
|
1541
|
-
const fileName = event.path ? event.path.split('/').pop() : 'unknown';
|
|
1542
|
-
const details = document.createElement('details');
|
|
1543
|
-
details.className = 'block-tool-use folded-tool';
|
|
1544
|
-
details.classList.add(this._getBlockTypeClass('tool_use'));
|
|
1545
|
-
details.classList.add(this._getToolColorClass('Read'));
|
|
1546
|
-
details.dataset.eventId = event.id || '';
|
|
1547
|
-
details.dataset.eventType = 'file_read';
|
|
1548
|
-
const summary = document.createElement('summary');
|
|
1549
|
-
summary.className = 'folded-tool-bar';
|
|
1550
|
-
summary.innerHTML = `
|
|
1551
|
-
<span class="folded-tool-icon">${this.getToolIcon('Read')}</span>
|
|
1552
|
-
<span class="folded-tool-name">Read</span>
|
|
1553
|
-
<span class="folded-tool-desc">${this.escapeHtml(fileName)}</span>
|
|
1554
|
-
`;
|
|
1555
|
-
details.appendChild(summary);
|
|
1556
|
-
if (event.path || event.content) {
|
|
1557
|
-
const body = document.createElement('div');
|
|
1558
|
-
body.className = 'folded-tool-body';
|
|
1559
|
-
let html = '';
|
|
1560
|
-
if (event.path) html += this.renderFilePath(event.path);
|
|
1561
|
-
if (event.content) {
|
|
1562
|
-
let base64Data = null;
|
|
1563
|
-
let mimeType = event.media_type || 'application/octet-stream';
|
|
1564
|
-
if (typeof event.content === 'string') {
|
|
1565
|
-
const imageInfo = this.detectBase64Image(event.content);
|
|
1566
|
-
if (imageInfo) {
|
|
1567
|
-
base64Data = imageInfo.data;
|
|
1568
|
-
mimeType = imageInfo.type === 'jpeg' ? 'image/jpeg' : `image/${imageInfo.type}`;
|
|
1569
|
-
}
|
|
1570
|
-
} else if (typeof event.content === 'object' && event.content !== null) {
|
|
1571
|
-
if (event.content.source?.type === 'base64' && event.content.source?.data) {
|
|
1572
|
-
base64Data = event.content.source.data;
|
|
1573
|
-
} else if (event.content.type === 'base64' && event.content.data) {
|
|
1574
|
-
base64Data = event.content.data;
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
if (base64Data) {
|
|
1578
|
-
html += `<div style="padding:0.5rem;display:flex;flex-direction:column;gap:0.5rem"><img src="data:${mimeType};base64,${this.escapeHtml(base64Data)}" style="max-width:100%;max-height:600px;border-radius:0.375rem;border:1px solid var(--color-code-border)" loading="lazy"><div style="font-size:0.7rem;color:var(--color-text-secondary);font-family:'Monaco','Menlo','Ubuntu Mono',monospace;word-break:break-all">${this.escapeHtml(event.path)}</div></div>`;
|
|
1579
|
-
} else {
|
|
1580
|
-
const contentStr = typeof event.content === 'string' ? event.content : JSON.stringify(event.content, null, 2);
|
|
1581
|
-
html += `<pre style="background:var(--color-bg-code);padding:0.75rem;border-radius:0.375rem;overflow-x:auto;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem;line-height:1.5;color:var(--color-code-text);margin:0.5rem 0 0 0"><code class="lazy-hl">${this.escapeHtml(this.truncateContent(contentStr, 2000))}</code></pre>`;
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
body.innerHTML = html;
|
|
1585
|
-
details.appendChild(body);
|
|
1586
|
-
}
|
|
1587
|
-
return details;
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
renderFileWrite(event) {
|
|
1591
|
-
const fileName = event.path ? event.path.split('/').pop() : 'unknown';
|
|
1592
|
-
const details = document.createElement('details');
|
|
1593
|
-
details.className = 'block-tool-use folded-tool';
|
|
1594
|
-
details.classList.add(this._getBlockTypeClass('tool_use'));
|
|
1595
|
-
details.classList.add(this._getToolColorClass('Write'));
|
|
1596
|
-
details.dataset.eventId = event.id || '';
|
|
1597
|
-
details.dataset.eventType = 'file_write';
|
|
1598
|
-
const summary = document.createElement('summary');
|
|
1599
|
-
summary.className = 'folded-tool-bar';
|
|
1600
|
-
summary.innerHTML = `
|
|
1601
|
-
<span class="folded-tool-icon">${this.getToolIcon('Write')}</span>
|
|
1602
|
-
<span class="folded-tool-name">Write</span>
|
|
1603
|
-
<span class="folded-tool-desc">${this.escapeHtml(fileName)}</span>
|
|
1604
|
-
`;
|
|
1605
|
-
details.appendChild(summary);
|
|
1606
|
-
if (event.path) {
|
|
1607
|
-
const body = document.createElement('div');
|
|
1608
|
-
body.className = 'folded-tool-body';
|
|
1609
|
-
body.innerHTML = this.renderFilePath(event.path);
|
|
1610
|
-
details.appendChild(body);
|
|
1611
|
-
}
|
|
1612
|
-
return details;
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
renderGitStatus(event) {
|
|
1616
|
-
const div = document.createElement('div');
|
|
1617
|
-
div.className = 'event-git-status card mb-3 p-4';
|
|
1618
|
-
div.dataset.eventId = event.id || '';
|
|
1619
|
-
div.dataset.eventType = 'git_status';
|
|
1620
|
-
|
|
1621
|
-
const branch = event.branch || 'unknown';
|
|
1622
|
-
const changes = event.changes || {};
|
|
1623
|
-
const total = (changes.added || 0) + (changes.modified || 0) + (changes.deleted || 0);
|
|
1624
|
-
|
|
1625
|
-
div.innerHTML = `
|
|
1626
|
-
<div class="flex items-center gap-3 mb-2">
|
|
1627
|
-
<svg class="w-4 h-4 text-warning" fill="currentColor" viewBox="0 0 20 20">
|
|
1628
|
-
<path fill-rule="evenodd" d="M9.243 3.03a1 1 0 01.727 1.155L9.53 6h2.94l.56-2.243a1 1 0 111.94.486L14.53 6H17a1 1 0 110 2h-2.97l-.5 2H17a1 1 0 110 2h-3.03l-.56 2.243a1 1 0 11-1.94-.486L12.47 14H9.53l-.56 2.243a1 1 0 11-1.94-.486L7.47 14H4a1 1 0 110-2h3.03l.5-2H4a1 1 0 110-2h2.97l.56-2.243a1 1 0 011.155-.727zM9.03 8l.5 2h2.94l-.5-2H9.03z" clip-rule="evenodd"></path>
|
|
1629
|
-
</svg>
|
|
1630
|
-
<div class="flex-1">
|
|
1631
|
-
<h4 class="font-semibold text-sm">Git Status</h4>
|
|
1632
|
-
<p class="text-xs text-secondary">Branch: ${this.escapeHtml(branch)}</p>
|
|
1633
|
-
</div>
|
|
1634
|
-
</div>
|
|
1635
|
-
<div class="flex gap-4 text-xs">
|
|
1636
|
-
${changes.added ? `<span class="text-success">+${changes.added}</span>` : ''}
|
|
1637
|
-
${changes.modified ? `<span class="text-info">~${changes.modified}</span>` : ''}
|
|
1638
|
-
${changes.deleted ? `<span class="text-error">-${changes.deleted}</span>` : ''}
|
|
1639
|
-
${total === 0 ? '<span class="text-secondary">no changes</span>' : ''}
|
|
1640
|
-
</div>
|
|
1641
|
-
`;
|
|
1642
|
-
return div;
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
renderCommand(event) {
|
|
1646
|
-
const command = event.command || '';
|
|
1647
|
-
const output = event.output || '';
|
|
1648
|
-
const exitCode = event.exitCode !== undefined ? event.exitCode : null;
|
|
1649
|
-
const cmdPreview = command.length > 60 ? command.substring(0, 57) + '...' : command;
|
|
1650
|
-
|
|
1651
|
-
const details = document.createElement('details');
|
|
1652
|
-
details.className = 'block-tool-use folded-tool';
|
|
1653
|
-
details.classList.add(this._getBlockTypeClass('tool_use'));
|
|
1654
|
-
details.classList.add(this._getToolColorClass('Bash'));
|
|
1655
|
-
details.dataset.eventId = event.id || '';
|
|
1656
|
-
details.dataset.eventType = 'command_execute';
|
|
1657
|
-
const summary = document.createElement('summary');
|
|
1658
|
-
summary.className = 'folded-tool-bar';
|
|
1659
|
-
summary.innerHTML = `
|
|
1660
|
-
<span class="folded-tool-icon">${this.getToolIcon('Bash')}</span>
|
|
1661
|
-
<span class="folded-tool-name">Bash</span>
|
|
1662
|
-
<span class="folded-tool-desc">${this.escapeHtml(cmdPreview)}</span>
|
|
1663
|
-
`;
|
|
1664
|
-
details.appendChild(summary);
|
|
1665
|
-
|
|
1666
|
-
const body = document.createElement('div');
|
|
1667
|
-
body.className = 'folded-tool-body';
|
|
1668
|
-
let html = `<div class="tool-param-command"><span class="prompt-char">$</span><span class="command-text">${this.escapeHtml(command)}</span></div>`;
|
|
1669
|
-
if (output) {
|
|
1670
|
-
html += `<pre style="background:var(--color-bg-code);padding:0.75rem;border-radius:0.375rem;overflow-x:auto;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem;line-height:1.5;color:var(--color-code-text);margin:0.5rem 0 0 0"><code class="lazy-hl">${this.escapeHtml(this.truncateContent(output, 2000))}</code></pre>`;
|
|
1671
|
-
}
|
|
1672
|
-
if (exitCode !== null && exitCode !== 0) {
|
|
1673
|
-
html += `<div style="margin-top:0.375rem;font-size:0.75rem;color:#ef4444;font-weight:600">Exit code: ${exitCode}</div>`;
|
|
1674
|
-
}
|
|
1675
|
-
body.innerHTML = html;
|
|
1676
|
-
details.appendChild(body);
|
|
1677
|
-
return details;
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
renderError(event) {
|
|
1681
|
-
const message = event.message || event.error || 'Unknown error';
|
|
1682
|
-
const severity = event.severity || 'error';
|
|
1683
|
-
const msgPreview = message.length > 80 ? message.substring(0, 77) + '...' : message;
|
|
1684
|
-
|
|
1685
|
-
const details = document.createElement('details');
|
|
1686
|
-
details.className = 'folded-tool folded-tool-error permanently-expanded';
|
|
1687
|
-
details.dataset.eventId = event.id || '';
|
|
1688
|
-
details.dataset.eventType = 'error';
|
|
1689
|
-
const summary = document.createElement('summary');
|
|
1690
|
-
summary.className = 'folded-tool-bar';
|
|
1691
|
-
summary.innerHTML = `
|
|
1692
|
-
<span class="folded-tool-icon"><svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg></span>
|
|
1693
|
-
<span class="folded-tool-name">Error</span>
|
|
1694
|
-
<span class="folded-tool-desc">${this.escapeHtml(msgPreview)}</span>
|
|
1695
|
-
`;
|
|
1696
|
-
details.appendChild(summary);
|
|
1697
|
-
|
|
1698
|
-
const body = document.createElement('div');
|
|
1699
|
-
body.className = 'folded-tool-body';
|
|
1700
|
-
body.innerHTML = `<div style="font-size:0.8rem;white-space:pre-wrap;word-break:break-word;line-height:1.5">${this.escapeHtml(message)}</div>`;
|
|
1701
|
-
details.appendChild(body);
|
|
1702
|
-
return details;
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
isHtmlContent(text) {
|
|
1706
|
-
const openTag = /<(?:div|table|section|article|form|ul|ol|dl|nav|header|footer|main|aside|figure|details|summary|h[1-6])\b[^>]*>/i;
|
|
1707
|
-
const closeTag = /<\/(?:div|table|section|article|form|ul|ol|dl|nav|header|footer|main|aside|figure|details|summary|h[1-6])>/i;
|
|
1708
|
-
return openTag.test(text) && closeTag.test(text);
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
parseMarkdownCodeBlocks(text) {
|
|
1712
|
-
const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
|
|
1713
|
-
const parts = [];
|
|
1714
|
-
let lastIndex = 0;
|
|
1715
|
-
let match;
|
|
1716
|
-
|
|
1717
|
-
while ((match = codeBlockRegex.exec(text)) !== null) {
|
|
1718
|
-
if (match.index > lastIndex) {
|
|
1719
|
-
const segment = text.substring(lastIndex, match.index);
|
|
1720
|
-
parts.push({ type: this.isHtmlContent(segment) ? 'html' : 'text', content: segment });
|
|
1721
|
-
}
|
|
1722
|
-
parts.push({ type: 'code', language: match[1] || 'plain', code: match[2] });
|
|
1723
|
-
lastIndex = codeBlockRegex.lastIndex;
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
if (lastIndex < text.length) {
|
|
1727
|
-
const segment = text.substring(lastIndex);
|
|
1728
|
-
parts.push({ type: this.isHtmlContent(segment) ? 'html' : 'text', content: segment });
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
|
-
if (parts.length === 0) {
|
|
1732
|
-
return [{ type: this.isHtmlContent(text) ? 'html' : 'text', content: text }];
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
return parts;
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
renderText(event) {
|
|
1739
|
-
const div = document.createElement('div');
|
|
1740
|
-
div.className = 'event-text mb-3';
|
|
1741
|
-
div.dataset.eventId = event.id || '';
|
|
1742
|
-
div.dataset.eventType = 'text_block';
|
|
1743
|
-
|
|
1744
|
-
const text = event.text || event.content || '';
|
|
1745
|
-
const parts = this.parseMarkdownCodeBlocks(text);
|
|
1746
|
-
let html = '';
|
|
1747
|
-
parts.forEach(part => {
|
|
1748
|
-
if (part.type === 'html') {
|
|
1749
|
-
html += `<div class="html-content mb-3">${part.content}</div>`;
|
|
1750
|
-
} else if (part.type === 'text') {
|
|
1751
|
-
html += `<div class="card card-body mb-3 leading-relaxed text-sm">${this.parseAndRenderMarkdown(part.content)}</div>`;
|
|
1752
|
-
} else if (part.type === 'code') {
|
|
1753
|
-
if (part.language.toLowerCase() === 'html') {
|
|
1754
|
-
html += `<div class="html-rendered-container mb-3">
|
|
1755
|
-
<div class="html-rendered-label">Rendered HTML</div>
|
|
1756
|
-
<div class="html-content bg-base-100 p-4 overflow-x-auto">${part.code}</div>
|
|
1757
|
-
</div>`;
|
|
1758
|
-
} else {
|
|
1759
|
-
const partLineCount = part.code.split('\n').length;
|
|
1760
|
-
html += `<div class="card mb-3 rounded-lg overflow-hidden">
|
|
1761
|
-
<details class="collapsible-code">
|
|
1762
|
-
<summary class="collapsible-code-summary">
|
|
1763
|
-
<span>${this.escapeHtml(part.language)} - ${partLineCount} line${partLineCount !== 1 ? 's' : ''}</span>
|
|
1764
|
-
<button class="copy-code-btn" title="Copy code" onclick="event.preventDefault();event.stopPropagation();navigator.clipboard.writeText(this.closest('.collapsible-code').querySelector('code').textContent)">
|
|
1765
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1766
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
1767
|
-
</svg>
|
|
1768
|
-
</button>
|
|
1769
|
-
</summary>
|
|
1770
|
-
<pre class="bg-base-300 text-base-content p-4 overflow-x-auto" style="margin:0;border-radius:0 0 0.375rem 0.375rem"><code class="language-${this.escapeHtml(part.language)}">${this.escapeHtml(part.code)}</code></pre>
|
|
1771
|
-
</details>
|
|
1772
|
-
</div>`;
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
});
|
|
1776
|
-
div.innerHTML = html;
|
|
1777
|
-
|
|
1778
|
-
div.querySelectorAll('.copy-code-btn').forEach(btn => {
|
|
1779
|
-
btn.addEventListener('click', () => {
|
|
1780
|
-
const codeElement = btn.closest('.mb-3')?.querySelector('code');
|
|
1781
|
-
if (codeElement) {
|
|
1782
|
-
const code = codeElement.textContent;
|
|
1783
|
-
navigator.clipboard.writeText(code).then(() => {
|
|
1784
|
-
const originalText = btn.innerHTML;
|
|
1785
|
-
btn.innerHTML = '<svg class="w-4 h-4 text-green-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>';
|
|
1786
|
-
setTimeout(() => { btn.innerHTML = originalText; }, 2000);
|
|
1787
|
-
});
|
|
1788
|
-
}
|
|
1789
|
-
});
|
|
1790
|
-
});
|
|
1791
|
-
|
|
1792
|
-
return div;
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
renderCode(event) {
|
|
1796
|
-
const div = document.createElement('div');
|
|
1797
|
-
div.className = 'event-code mb-3';
|
|
1798
|
-
div.dataset.eventId = event.id || '';
|
|
1799
|
-
div.dataset.eventType = 'code_block';
|
|
1800
|
-
|
|
1801
|
-
const code = event.code || event.content || '';
|
|
1802
|
-
const language = event.language || 'plaintext';
|
|
1803
|
-
|
|
1804
|
-
if (language === 'html') {
|
|
1805
|
-
div.innerHTML = `
|
|
1806
|
-
<div class="html-rendered-container alert alert-info text-xs mb-2">
|
|
1807
|
-
Rendered HTML
|
|
1808
|
-
</div>
|
|
1809
|
-
<div class="html-content">
|
|
1810
|
-
${code}
|
|
1811
|
-
</div>
|
|
1812
|
-
`;
|
|
1813
|
-
} else {
|
|
1814
|
-
const codeLineCount = code.split('\n').length;
|
|
1815
|
-
div.innerHTML = `
|
|
1816
|
-
<details class="collapsible-code">
|
|
1817
|
-
<summary class="collapsible-code-summary">${this.escapeHtml(language)} - ${codeLineCount} line${codeLineCount !== 1 ? 's' : ''}</summary>
|
|
1818
|
-
<pre class="bg-base-300 text-base-content p-4 overflow-x-auto" style="margin:0;border-radius:0 0 0.375rem 0.375rem"><code class="language-${this.escapeHtml(language)}">${this.escapeHtml(code)}</code></pre>
|
|
1819
|
-
</details>
|
|
1820
|
-
`;
|
|
1821
|
-
}
|
|
1822
|
-
return div;
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
renderThinking(event) {
|
|
1826
|
-
const div = document.createElement('div');
|
|
1827
|
-
div.className = 'event-thinking mb-3 p-4 alert rounded';
|
|
1828
|
-
div.dataset.eventId = event.id || '';
|
|
1829
|
-
div.dataset.eventType = 'thinking_block';
|
|
1830
|
-
|
|
1831
|
-
const text = event.thinking || event.content || '';
|
|
1832
|
-
div.innerHTML = `
|
|
1833
|
-
<details>
|
|
1834
|
-
<summary class="cursor-pointer font-semibold text-base-content">Thinking</summary>
|
|
1835
|
-
<p class="mt-3 text-sm text-base-content/80 whitespace-pre-wrap">${this.escapeHtml(text)}</p>
|
|
1836
|
-
</details>
|
|
1837
|
-
`;
|
|
1838
|
-
return div;
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
renderToolUse(event) {
|
|
1842
|
-
const block = {
|
|
1843
|
-
type: 'tool_use',
|
|
1844
|
-
name: event.toolName || event.tool || 'unknown',
|
|
1845
|
-
input: event.input || {}
|
|
1846
|
-
};
|
|
1847
|
-
const div = this.renderBlockToolUse(block, event);
|
|
1848
|
-
div.className = 'event-tool-use mb-3';
|
|
1849
|
-
div.dataset.eventId = event.id || '';
|
|
1850
|
-
div.dataset.eventType = 'tool_use';
|
|
1851
|
-
return div;
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
renderCompactBoundary(event) {
|
|
1857
|
-
const preTokens = event._preTokens || event.compactMetadata?.preTokens;
|
|
1858
|
-
const div = document.createElement('div');
|
|
1859
|
-
div.className = 'event-compact-boundary';
|
|
1860
|
-
div.dataset.eventType = 'compact_boundary';
|
|
1861
|
-
div.innerHTML = `
|
|
1862
|
-
<div style="display:flex;align-items:center;gap:0.75rem;padding:0.5rem 0.75rem;margin:0.5rem 0;background:var(--color-bg-secondary);border-radius:0.375rem;font-size:0.75rem;color:var(--color-text-secondary)">
|
|
1863
|
-
<svg viewBox="0 0 20 20" fill="currentColor" style="width:1rem;height:1rem;flex-shrink:0;opacity:0.6"><path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h6a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"/></svg>
|
|
1864
|
-
<span>Conversation compacted${preTokens ? ` (${preTokens.toLocaleString()} tokens before)` : ''}</span>
|
|
1865
|
-
</div>
|
|
1866
|
-
`;
|
|
1867
|
-
return div;
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
renderTurnDuration(event) {
|
|
1871
|
-
const ms = event._turnDurationMs || event.durationMs;
|
|
1872
|
-
if (!ms) return null;
|
|
1873
|
-
const sec = (ms / 1000).toFixed(1);
|
|
1874
|
-
const div = document.createElement('div');
|
|
1875
|
-
div.className = 'event-turn-duration';
|
|
1876
|
-
div.dataset.eventType = 'turn_duration';
|
|
1877
|
-
div.innerHTML = `
|
|
1878
|
-
<div style="display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0.75rem;font-size:0.7rem;color:var(--color-text-secondary);opacity:0.7">
|
|
1879
|
-
<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.75rem;height:0.75rem;flex-shrink:0"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/></svg>
|
|
1880
|
-
<span>${sec}s</span>
|
|
1881
|
-
</div>
|
|
1882
|
-
`;
|
|
1883
|
-
return div;
|
|
1884
|
-
}
|
|
1885
|
-
|
|
1886
|
-
renderAgentProgress(event) {
|
|
1887
|
-
const msg = event.data?.message || event.message || '';
|
|
1888
|
-
if (!msg) return null;
|
|
1889
|
-
const div = document.createElement('div');
|
|
1890
|
-
div.className = 'event-agent-progress';
|
|
1891
|
-
div.dataset.eventType = 'agent_progress';
|
|
1892
|
-
div.innerHTML = `
|
|
1893
|
-
<div style="display:flex;align-items:flex-start;gap:0.5rem;padding:0.375rem 0.75rem;margin:0.25rem 0;background:var(--color-bg-secondary);border-left:2px solid #7c3aed;border-radius:0 0.25rem 0.25rem 0;font-size:0.75rem;color:var(--color-text-secondary)">
|
|
1894
|
-
<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.875rem;height:0.875rem;flex-shrink:0;margin-top:0.125rem;color:#7c3aed"><path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/><path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5z" clip-rule="evenodd"/></svg>
|
|
1895
|
-
<span>${this.escapeHtml(msg)}</span>
|
|
1896
|
-
</div>
|
|
1897
|
-
`;
|
|
1898
|
-
return div;
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
renderMcpProgress(event) {
|
|
1902
|
-
const status = event.status || 'running';
|
|
1903
|
-
const name = event.tool || event.name || 'MCP tool';
|
|
1904
|
-
const statusColors = { running: '#0891b2', completed: '#059669', failed: '#dc2626' };
|
|
1905
|
-
const color = statusColors[status] || statusColors.running;
|
|
1906
|
-
const div = document.createElement('div');
|
|
1907
|
-
div.className = 'event-mcp-progress';
|
|
1908
|
-
div.dataset.eventType = 'mcp_progress';
|
|
1909
|
-
div.innerHTML = `
|
|
1910
|
-
<div style="display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0.75rem;font-size:0.75rem;color:var(--color-text-secondary)">
|
|
1911
|
-
<span style="width:0.5rem;height:0.5rem;border-radius:50%;background:${color};flex-shrink:0"></span>
|
|
1912
|
-
<span style="color:${color};font-weight:600">MCP</span>
|
|
1913
|
-
<span>${this.escapeHtml(name)}</span>
|
|
1914
|
-
<span style="opacity:0.6">${this.escapeHtml(status)}</span>
|
|
1915
|
-
</div>
|
|
1916
|
-
`;
|
|
1917
|
-
return div;
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
|
-
renderGeneric(event) {
|
|
1921
|
-
if ((event.content?.source?.type === 'base64' || event.content?.type === 'base64') && event.path) {
|
|
1922
|
-
return this.renderFileRead(event);
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
const div = document.createElement('div');
|
|
1926
|
-
div.className = 'event-generic mb-3 p-3 bg-base-200 rounded text-sm';
|
|
1927
|
-
div.dataset.eventId = event.id || '';
|
|
1928
|
-
div.dataset.eventType = event.type;
|
|
1929
|
-
|
|
1930
|
-
const time = new Date(event.timestamp).toLocaleTimeString();
|
|
1931
|
-
|
|
1932
|
-
const fieldsHtml = Object.entries(event)
|
|
1933
|
-
.filter(([key]) => !['type', 'timestamp'].includes(key))
|
|
1934
|
-
.map(([key, value]) => {
|
|
1935
|
-
let displayValue;
|
|
1936
|
-
if (typeof value === 'string') {
|
|
1937
|
-
displayValue = value.length > 100 ? value.substring(0, 100) + '...' : value;
|
|
1938
|
-
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
1939
|
-
displayValue = String(value);
|
|
1940
|
-
} else if (value === null) {
|
|
1941
|
-
displayValue = 'null';
|
|
1942
|
-
} else {
|
|
1943
|
-
displayValue = JSON.stringify(value);
|
|
1944
|
-
if (displayValue.length > 100) displayValue = displayValue.substring(0, 100) + '...';
|
|
1945
|
-
}
|
|
1946
|
-
return `<div style="font-size:0.75rem;margin-bottom:0.25rem"><span style="font-weight:600;color:var(--color-text-secondary)">${this.escapeHtml(key)}:</span> <span style="font-family:'Monaco','Menlo','Ubuntu Mono',monospace">${this.escapeHtml(displayValue)}</span></div>`;
|
|
1947
|
-
}).join('');
|
|
1948
|
-
|
|
1949
|
-
div.innerHTML = `
|
|
1950
|
-
<div style="display:flex;justify-content:space-between;margin-bottom:0.5rem">
|
|
1951
|
-
<span style="font-weight:600;color:var(--color-text-primary)">${this.escapeHtml(event.type)}</span>
|
|
1952
|
-
<span style="font-size:0.75rem;color:var(--color-text-secondary)">${time}</span>
|
|
1953
|
-
</div>
|
|
1954
|
-
<div>${fieldsHtml || '<span style="color:var(--color-text-secondary);font-size:0.75rem">No additional data</span>'}</div>
|
|
1955
|
-
`;
|
|
1956
|
-
return div;
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
nestToolResultsInToolUses() {
|
|
1960
|
-
if (!this.outputContainer) return;
|
|
1961
|
-
|
|
1962
|
-
const toolUseBlocks = this.outputContainer.querySelectorAll('details.block-tool-use[data-tool-use-id]');
|
|
1963
|
-
const toolResultBlocks = this.outputContainer.querySelectorAll('.tool-result-pending[data-tool-use-id]');
|
|
1964
|
-
|
|
1965
|
-
toolResultBlocks.forEach(resultBlock => {
|
|
1966
|
-
const toolUseId = resultBlock.dataset.toolUseId;
|
|
1967
|
-
const isError = resultBlock.dataset.isError === '1';
|
|
1968
|
-
const toolUseBlock = Array.from(toolUseBlocks).find(b => b.dataset.toolUseId === toolUseId);
|
|
1969
|
-
|
|
1970
|
-
if (toolUseBlock) {
|
|
1971
|
-
const toolUseSummary = toolUseBlock.querySelector(':scope > summary');
|
|
1972
|
-
|
|
1973
|
-
if (toolUseSummary && !toolUseSummary.querySelector('.folded-tool-status')) {
|
|
1974
|
-
const statusSvg = isError
|
|
1975
|
-
? '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>'
|
|
1976
|
-
: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>';
|
|
1977
|
-
const statusSpan = document.createElement('span');
|
|
1978
|
-
statusSpan.className = 'folded-tool-status';
|
|
1979
|
-
statusSpan.innerHTML = statusSvg;
|
|
1980
|
-
toolUseSummary.appendChild(statusSpan);
|
|
1981
|
-
toolUseBlock.classList.add(isError ? 'tool-result-error' : 'tool-result-success');
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
if (resultBlock.innerHTML.trim()) {
|
|
1985
|
-
let toolUseBody = toolUseBlock.querySelector(':scope > .folded-tool-body');
|
|
1986
|
-
if (!toolUseBody) {
|
|
1987
|
-
toolUseBody = document.createElement('div');
|
|
1988
|
-
toolUseBody.className = 'folded-tool-body';
|
|
1989
|
-
toolUseBlock.appendChild(toolUseBody);
|
|
1990
|
-
}
|
|
1991
|
-
const resultContent = document.createElement('div');
|
|
1992
|
-
resultContent.className = 'folded-tool-result-content';
|
|
1993
|
-
resultContent.innerHTML = resultBlock.innerHTML;
|
|
1994
|
-
toolUseBody.appendChild(resultContent);
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
resultBlock.remove();
|
|
1998
|
-
} else {
|
|
1999
|
-
resultBlock.remove();
|
|
2000
|
-
}
|
|
2001
|
-
});
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
mergeResultIntoToolUse(toolUseEl, block) {
|
|
2005
|
-
const content = block.content || '';
|
|
2006
|
-
const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
|
|
2007
|
-
const isError = (block.is_error || false) && !contentStr.trimStart().startsWith('exec ran successfully.');
|
|
2008
|
-
|
|
2009
|
-
toolUseEl.classList.remove('has-success', 'has-error', 'tool-result-success', 'tool-result-error');
|
|
2010
|
-
toolUseEl.classList.add(isError ? 'has-error' : 'has-success', isError ? 'tool-result-error' : 'tool-result-success');
|
|
2011
|
-
|
|
2012
|
-
const summary = toolUseEl.querySelector(':scope > summary');
|
|
2013
|
-
if (summary && !summary.querySelector('.folded-tool-status')) {
|
|
2014
|
-
const statusSvg = isError
|
|
2015
|
-
? '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>'
|
|
2016
|
-
: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>';
|
|
2017
|
-
const statusSpan = document.createElement('span');
|
|
2018
|
-
statusSpan.className = 'folded-tool-status';
|
|
2019
|
-
statusSpan.innerHTML = statusSvg;
|
|
2020
|
-
summary.appendChild(statusSpan);
|
|
2021
|
-
const startedAt = parseInt(toolUseEl.dataset.startedAt);
|
|
2022
|
-
if (startedAt > 0) {
|
|
2023
|
-
const ms = Date.now() - startedAt;
|
|
2024
|
-
const label = ms < 1000 ? ms + 'ms' : (ms / 1000).toFixed(1) + 's';
|
|
2025
|
-
const dur = document.createElement('span');
|
|
2026
|
-
dur.className = 'folded-tool-duration';
|
|
2027
|
-
dur.style.cssText = 'margin-left:0.375rem;font-size:0.6rem;opacity:0.45;font-weight:400';
|
|
2028
|
-
dur.textContent = label;
|
|
2029
|
-
summary.appendChild(dur);
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
|
|
2033
|
-
const renderedContent = StreamingRenderer.renderSmartContentHTML(contentStr, this.escapeHtml.bind(this), true);
|
|
2034
|
-
if (renderedContent && renderedContent.trim()) {
|
|
2035
|
-
let toolUseBody = toolUseEl.querySelector(':scope > .folded-tool-body');
|
|
2036
|
-
if (!toolUseBody) {
|
|
2037
|
-
toolUseBody = document.createElement('div');
|
|
2038
|
-
toolUseBody.className = 'folded-tool-body';
|
|
2039
|
-
toolUseEl.appendChild(toolUseBody);
|
|
2040
|
-
}
|
|
2041
|
-
const resultContent = document.createElement('div');
|
|
2042
|
-
resultContent.className = 'folded-tool-result-content';
|
|
2043
|
-
resultContent.innerHTML = renderedContent;
|
|
2044
|
-
toolUseBody.appendChild(resultContent);
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
autoScroll() {
|
|
2049
|
-
if (this._scrollRafPending || this._userScrolledUp) return;
|
|
2050
|
-
this._scrollRafPending = true;
|
|
2051
|
-
requestAnimationFrame(() => {
|
|
2052
|
-
this._scrollRafPending = false;
|
|
2053
|
-
if (this.scrollContainer) {
|
|
2054
|
-
this._programmaticScroll = true;
|
|
2055
|
-
try { this.scrollContainer.scrollTop = this.scrollContainer.scrollHeight; } catch (_) {}
|
|
2056
|
-
this._programmaticScroll = false;
|
|
2057
|
-
}
|
|
2058
|
-
});
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
resetScrollState() {
|
|
2062
|
-
this._userScrolledUp = false;
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
updateDOMNodeCount() {
|
|
2066
|
-
this.domNodeCount = this.outputContainer?.querySelectorAll('[data-event-id]').length || 0;
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
escapeHtml(text) {
|
|
2070
|
-
return window._escHtml(text);
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
formatFileSize(bytes) {
|
|
2074
|
-
if (bytes === 0) return '0 B';
|
|
2075
|
-
const k = 1024;
|
|
2076
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
2077
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
2078
|
-
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
truncateContent(content, maxLength = 200) {
|
|
2082
|
-
if (content.length <= maxLength) return content;
|
|
2083
|
-
return content.substring(0, maxLength) + '...';
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
clear() {
|
|
2087
|
-
if (this.outputContainer) {
|
|
2088
|
-
this.outputContainer.innerHTML = '';
|
|
2089
|
-
}
|
|
2090
|
-
this.eventQueue = [];
|
|
2091
|
-
this.eventHistory = [];
|
|
2092
|
-
this.domNodeCount = 0;
|
|
2093
|
-
this.dedupMap.clear();
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
getMetrics() {
|
|
2097
|
-
return {
|
|
2098
|
-
...this.performanceMetrics,
|
|
2099
|
-
domNodeCount: this.domNodeCount,
|
|
2100
|
-
queueLength: this.eventQueue.length,
|
|
2101
|
-
historyLength: this.eventHistory.length,
|
|
2102
|
-
lastRenderTime: this.lastRenderTime
|
|
2103
|
-
};
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
on(event, callback) {
|
|
2107
|
-
if (!this.listeners[event]) {
|
|
2108
|
-
this.listeners[event] = [];
|
|
2109
|
-
}
|
|
2110
|
-
this.listeners[event].push(callback);
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
emit(event, data) {
|
|
2114
|
-
if (this.listeners[event]) {
|
|
2115
|
-
this.listeners[event].forEach(callback => {
|
|
2116
|
-
try {
|
|
2117
|
-
callback(data);
|
|
2118
|
-
} catch (e) {
|
|
2119
|
-
console.error('Listener error:', e);
|
|
2120
|
-
}
|
|
2121
|
-
});
|
|
2122
|
-
}
|
|
2123
|
-
}
|
|
2124
|
-
|
|
2125
|
-
renderBlockHeader(block, context = {}) {
|
|
2126
|
-
if (!block || !block.type) return null;
|
|
2127
|
-
|
|
2128
|
-
const typeLabel = block.type.charAt(0).toUpperCase() + block.type.slice(1).replace(/_/g, ' ');
|
|
2129
|
-
const summary = document.createElement('summary');
|
|
2130
|
-
summary.style.cursor = 'pointer';
|
|
2131
|
-
summary.style.userSelect = 'none';
|
|
2132
|
-
summary.className = 'block-header-summary';
|
|
2133
|
-
|
|
2134
|
-
let summaryText = typeLabel;
|
|
2135
|
-
if (block.type === 'code' && block.language) {
|
|
2136
|
-
summaryText += ` (${block.language})`;
|
|
2137
|
-
} else if (block.type === 'bash' && block.source) {
|
|
2138
|
-
summaryText += ` - ${block.source}`;
|
|
2139
|
-
} else if (block.type === 'tool_use' && block.name) {
|
|
2140
|
-
summaryText += ` - ${block.name}`;
|
|
2141
|
-
} else if (block.type === 'text' && block.text) {
|
|
2142
|
-
const preview = block.text.substring(0, 60).replace(/\n/g, ' ');
|
|
2143
|
-
summaryText = preview + (block.text.length > 60 ? '...' : '');
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
summary.textContent = summaryText;
|
|
2147
|
-
|
|
2148
|
-
const details = document.createElement('details');
|
|
2149
|
-
details.className = `block-${block.type}`;
|
|
2150
|
-
details.classList.add(this._getBlockTypeClass(block.type));
|
|
2151
|
-
details.setAttribute('data-block-type', block.type);
|
|
2152
|
-
details.setAttribute('data-lazy-load', 'pending');
|
|
2153
|
-
details.open = block.type === 'success' || (block.type === 'tool_result' && !block.is_error);
|
|
2154
|
-
details.appendChild(summary);
|
|
2155
|
-
|
|
2156
|
-
details.addEventListener('toggle', async (e) => {
|
|
2157
|
-
if (details.open && details.getAttribute('data-lazy-load') === 'pending') {
|
|
2158
|
-
details.setAttribute('data-lazy-load', 'loading');
|
|
2159
|
-
try {
|
|
2160
|
-
const body = this.renderBlock(block, context);
|
|
2161
|
-
if (body && body !== summary) {
|
|
2162
|
-
details.appendChild(body);
|
|
2163
|
-
}
|
|
2164
|
-
details.setAttribute('data-lazy-load', 'loaded');
|
|
2165
|
-
} catch (err) {
|
|
2166
|
-
console.error('Failed to lazy-load block:', err);
|
|
2167
|
-
details.setAttribute('data-lazy-load', 'failed');
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
}, { once: false });
|
|
2171
|
-
|
|
2172
|
-
return details;
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
destroy() {
|
|
2176
|
-
if (this.observer) {
|
|
2177
|
-
this.observer.disconnect();
|
|
2178
|
-
}
|
|
2179
|
-
if (this.resizeObserver) {
|
|
2180
|
-
this.resizeObserver.disconnect();
|
|
2181
|
-
}
|
|
2182
|
-
if (this.batchTimer) {
|
|
2183
|
-
clearTimeout(this.batchTimer);
|
|
2184
|
-
}
|
|
2185
|
-
this.listeners = {};
|
|
2186
|
-
this.clear();
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
|
|
2190
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
2191
|
-
module.exports = StreamingRenderer;
|
|
2192
|
-
}
|
|
197
|
+
}
|
|
198
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
199
|
+
module.exports = StreamingRenderer;
|
|
200
|
+
}
|