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.
@@ -0,0 +1,181 @@
1
+ Object.assign(StreamingRenderer.prototype, {
2
+ renderGitStatus(event) {
3
+ const div = document.createElement('div');
4
+ div.className = 'event-git-status card mb-3 p-4';
5
+ div.dataset.eventId = event.id || '';
6
+ div.dataset.eventType = 'git_status';
7
+
8
+ const branch = event.branch || 'unknown';
9
+ const changes = event.changes || {};
10
+ const total = (changes.added || 0) + (changes.modified || 0) + (changes.deleted || 0);
11
+
12
+ div.innerHTML = `
13
+ <div class="flex items-center gap-3 mb-2">
14
+ <svg class="w-4 h-4 text-warning" fill="currentColor" viewBox="0 0 20 20">
15
+ <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>
16
+ </svg>
17
+ <div class="flex-1">
18
+ <h4 class="font-semibold text-sm">Git Status</h4>
19
+ <p class="text-xs text-secondary">Branch: ${this.escapeHtml(branch)}</p>
20
+ </div>
21
+ </div>
22
+ <div class="flex gap-4 text-xs">
23
+ ${changes.added ? `<span class="text-success">+${changes.added}</span>` : ''}
24
+ ${changes.modified ? `<span class="text-info">~${changes.modified}</span>` : ''}
25
+ ${changes.deleted ? `<span class="text-error">-${changes.deleted}</span>` : ''}
26
+ ${total === 0 ? '<span class="text-secondary">no changes</span>' : ''}
27
+ </div>
28
+ `;
29
+ return div;
30
+ },
31
+
32
+ renderCommand(event) {
33
+ const command = event.command || '';
34
+ const output = event.output || '';
35
+ const exitCode = event.exitCode !== undefined ? event.exitCode : null;
36
+ const cmdPreview = command.length > 60 ? command.substring(0, 57) + '...' : command;
37
+
38
+ const details = document.createElement('details');
39
+ details.className = 'block-tool-use folded-tool';
40
+ details.classList.add(this._getBlockTypeClass('tool_use'));
41
+ details.classList.add(this._getToolColorClass('Bash'));
42
+ details.dataset.eventId = event.id || '';
43
+ details.dataset.eventType = 'command_execute';
44
+ const summary = document.createElement('summary');
45
+ summary.className = 'folded-tool-bar';
46
+ summary.innerHTML = `
47
+ <span class="folded-tool-icon">${this.getToolIcon('Bash')}</span>
48
+ <span class="folded-tool-name">Bash</span>
49
+ <span class="folded-tool-desc">${this.escapeHtml(cmdPreview)}</span>
50
+ `;
51
+ details.appendChild(summary);
52
+
53
+ const body = document.createElement('div');
54
+ body.className = 'folded-tool-body';
55
+ let html = `<div class="tool-param-command"><span class="prompt-char">$</span><span class="command-text">${this.escapeHtml(command)}</span></div>`;
56
+ if (output) {
57
+ 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>`;
58
+ }
59
+ if (exitCode !== null && exitCode !== 0) {
60
+ html += `<div style="margin-top:0.375rem;font-size:0.75rem;color:#ef4444;font-weight:600">Exit code: ${exitCode}</div>`;
61
+ }
62
+ body.innerHTML = html;
63
+ details.appendChild(body);
64
+ return details;
65
+ },
66
+
67
+ renderError(event) {
68
+ const message = event.message || event.error || 'Unknown error';
69
+ const severity = event.severity || 'error';
70
+ const msgPreview = message.length > 80 ? message.substring(0, 77) + '...' : message;
71
+
72
+ const details = document.createElement('details');
73
+ details.className = 'folded-tool folded-tool-error permanently-expanded';
74
+ details.dataset.eventId = event.id || '';
75
+ details.dataset.eventType = 'error';
76
+ const summary = document.createElement('summary');
77
+ summary.className = 'folded-tool-bar';
78
+ summary.innerHTML = `
79
+ <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>
80
+ <span class="folded-tool-name">Error</span>
81
+ <span class="folded-tool-desc">${this.escapeHtml(msgPreview)}</span>
82
+ `;
83
+ details.appendChild(summary);
84
+
85
+ const body = document.createElement('div');
86
+ body.className = 'folded-tool-body';
87
+ body.innerHTML = `<div style="font-size:0.8rem;white-space:pre-wrap;word-break:break-word;line-height:1.5">${this.escapeHtml(message)}</div>`;
88
+ details.appendChild(body);
89
+ return details;
90
+ },
91
+
92
+ isHtmlContent(text) {
93
+ const openTag = /<(?:div|table|section|article|form|ul|ol|dl|nav|header|footer|main|aside|figure|details|summary|h[1-6])\b[^>]*>/i;
94
+ const closeTag = /<\/(?:div|table|section|article|form|ul|ol|dl|nav|header|footer|main|aside|figure|details|summary|h[1-6])>/i;
95
+ return openTag.test(text) && closeTag.test(text);
96
+ },
97
+
98
+ parseMarkdownCodeBlocks(text) {
99
+ const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
100
+ const parts = [];
101
+ let lastIndex = 0;
102
+ let match;
103
+
104
+ while ((match = codeBlockRegex.exec(text)) !== null) {
105
+ if (match.index > lastIndex) {
106
+ const segment = text.substring(lastIndex, match.index);
107
+ parts.push({ type: this.isHtmlContent(segment) ? 'html' : 'text', content: segment });
108
+ }
109
+ parts.push({ type: 'code', language: match[1] || 'plain', code: match[2] });
110
+ lastIndex = codeBlockRegex.lastIndex;
111
+ }
112
+
113
+ if (lastIndex < text.length) {
114
+ const segment = text.substring(lastIndex);
115
+ parts.push({ type: this.isHtmlContent(segment) ? 'html' : 'text', content: segment });
116
+ }
117
+
118
+ if (parts.length === 0) {
119
+ return [{ type: this.isHtmlContent(text) ? 'html' : 'text', content: text }];
120
+ }
121
+
122
+ return parts;
123
+ },
124
+
125
+ renderText(event) {
126
+ const div = document.createElement('div');
127
+ div.className = 'event-text mb-3';
128
+ div.dataset.eventId = event.id || '';
129
+ div.dataset.eventType = 'text_block';
130
+
131
+ const text = event.text || event.content || '';
132
+ const parts = this.parseMarkdownCodeBlocks(text);
133
+ let html = '';
134
+ parts.forEach(part => {
135
+ if (part.type === 'html') {
136
+ html += `<div class="html-content mb-3">${part.content}</div>`;
137
+ } else if (part.type === 'text') {
138
+ html += `<div class="card card-body mb-3 leading-relaxed text-sm">${this.parseAndRenderMarkdown(part.content)}</div>`;
139
+ } else if (part.type === 'code') {
140
+ if (part.language.toLowerCase() === 'html') {
141
+ html += `<div class="html-rendered-container mb-3">
142
+ <div class="html-rendered-label">Rendered HTML</div>
143
+ <div class="html-content bg-base-100 p-4 overflow-x-auto">${part.code}</div>
144
+ </div>`;
145
+ } else {
146
+ const partLineCount = part.code.split('\n').length;
147
+ html += `<div class="card mb-3 rounded-lg overflow-hidden">
148
+ <details class="collapsible-code">
149
+ <summary class="collapsible-code-summary">
150
+ <span>${this.escapeHtml(part.language)} - ${partLineCount} line${partLineCount !== 1 ? 's' : ''}</span>
151
+ <button class="copy-code-btn" title="Copy code" onclick="event.preventDefault();event.stopPropagation();navigator.clipboard.writeText(this.closest('.collapsible-code').querySelector('code').textContent)">
152
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
153
+ <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>
154
+ </svg>
155
+ </button>
156
+ </summary>
157
+ <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>
158
+ </details>
159
+ </div>`;
160
+ }
161
+ }
162
+ });
163
+ div.innerHTML = html;
164
+
165
+ div.querySelectorAll('.copy-code-btn').forEach(btn => {
166
+ btn.addEventListener('click', () => {
167
+ const codeElement = btn.closest('.mb-3')?.querySelector('code');
168
+ if (codeElement) {
169
+ const code = codeElement.textContent;
170
+ navigator.clipboard.writeText(code).then(() => {
171
+ const originalText = btn.innerHTML;
172
+ 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>';
173
+ setTimeout(() => { btn.innerHTML = originalText; }, 2000);
174
+ });
175
+ }
176
+ });
177
+ });
178
+
179
+ return div;
180
+ }
181
+ });
@@ -0,0 +1,149 @@
1
+ Object.assign(StreamingRenderer.prototype, {
2
+ renderMcpProgress(event) {
3
+ const status = event.status || 'running';
4
+ const name = event.tool || event.name || 'MCP tool';
5
+ const statusColors = { running: '#0891b2', completed: '#059669', failed: '#dc2626' };
6
+ const color = statusColors[status] || statusColors.running;
7
+ const div = document.createElement('div');
8
+ div.className = 'event-mcp-progress';
9
+ div.dataset.eventType = 'mcp_progress';
10
+ div.innerHTML = `
11
+ <div style="display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0.75rem;font-size:0.75rem;color:var(--color-text-secondary)">
12
+ <span style="width:0.5rem;height:0.5rem;border-radius:50%;background:${color};flex-shrink:0"></span>
13
+ <span style="color:${color};font-weight:600">MCP</span>
14
+ <span>${this.escapeHtml(name)}</span>
15
+ <span style="opacity:0.6">${this.escapeHtml(status)}</span>
16
+ </div>
17
+ `;
18
+ return div;
19
+ },
20
+
21
+ renderGeneric(event) {
22
+ if ((event.content?.source?.type === 'base64' || event.content?.type === 'base64') && event.path) {
23
+ return this.renderFileRead(event);
24
+ }
25
+
26
+ const div = document.createElement('div');
27
+ div.className = 'event-generic mb-3 p-3 bg-base-200 rounded text-sm';
28
+ div.dataset.eventId = event.id || '';
29
+ div.dataset.eventType = event.type;
30
+
31
+ const time = new Date(event.timestamp).toLocaleTimeString();
32
+
33
+ const fieldsHtml = Object.entries(event)
34
+ .filter(([key]) => !['type', 'timestamp'].includes(key))
35
+ .map(([key, value]) => {
36
+ let displayValue;
37
+ if (typeof value === 'string') {
38
+ displayValue = value.length > 100 ? value.substring(0, 100) + '...' : value;
39
+ } else if (typeof value === 'number' || typeof value === 'boolean') {
40
+ displayValue = String(value);
41
+ } else if (value === null) {
42
+ displayValue = 'null';
43
+ } else {
44
+ displayValue = JSON.stringify(value);
45
+ if (displayValue.length > 100) displayValue = displayValue.substring(0, 100) + '...';
46
+ }
47
+ 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>`;
48
+ }).join('');
49
+
50
+ div.innerHTML = `
51
+ <div style="display:flex;justify-content:space-between;margin-bottom:0.5rem">
52
+ <span style="font-weight:600;color:var(--color-text-primary)">${this.escapeHtml(event.type)}</span>
53
+ <span style="font-size:0.75rem;color:var(--color-text-secondary)">${time}</span>
54
+ </div>
55
+ <div>${fieldsHtml || '<span style="color:var(--color-text-secondary);font-size:0.75rem">No additional data</span>'}</div>
56
+ `;
57
+ return div;
58
+ },
59
+
60
+ nestToolResultsInToolUses() {
61
+ if (!this.outputContainer) return;
62
+
63
+ const toolUseBlocks = this.outputContainer.querySelectorAll('details.block-tool-use[data-tool-use-id]');
64
+ const toolResultBlocks = this.outputContainer.querySelectorAll('.tool-result-pending[data-tool-use-id]');
65
+
66
+ toolResultBlocks.forEach(resultBlock => {
67
+ const toolUseId = resultBlock.dataset.toolUseId;
68
+ const isError = resultBlock.dataset.isError === '1';
69
+ const toolUseBlock = Array.from(toolUseBlocks).find(b => b.dataset.toolUseId === toolUseId);
70
+
71
+ if (toolUseBlock) {
72
+ const toolUseSummary = toolUseBlock.querySelector(':scope > summary');
73
+
74
+ if (toolUseSummary && !toolUseSummary.querySelector('.folded-tool-status')) {
75
+ const statusSvg = isError
76
+ ? '<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>'
77
+ : '<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>';
78
+ const statusSpan = document.createElement('span');
79
+ statusSpan.className = 'folded-tool-status';
80
+ statusSpan.innerHTML = statusSvg;
81
+ toolUseSummary.appendChild(statusSpan);
82
+ toolUseBlock.classList.add(isError ? 'tool-result-error' : 'tool-result-success');
83
+ }
84
+
85
+ if (resultBlock.innerHTML.trim()) {
86
+ let toolUseBody = toolUseBlock.querySelector(':scope > .folded-tool-body');
87
+ if (!toolUseBody) {
88
+ toolUseBody = document.createElement('div');
89
+ toolUseBody.className = 'folded-tool-body';
90
+ toolUseBlock.appendChild(toolUseBody);
91
+ }
92
+ const resultContent = document.createElement('div');
93
+ resultContent.className = 'folded-tool-result-content';
94
+ resultContent.innerHTML = resultBlock.innerHTML;
95
+ toolUseBody.appendChild(resultContent);
96
+ }
97
+
98
+ resultBlock.remove();
99
+ } else {
100
+ resultBlock.remove();
101
+ }
102
+ });
103
+ },
104
+
105
+ mergeResultIntoToolUse(toolUseEl, block) {
106
+ const content = block.content || '';
107
+ const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
108
+ const isError = (block.is_error || false) && !contentStr.trimStart().startsWith('exec ran successfully.');
109
+
110
+ toolUseEl.classList.remove('has-success', 'has-error', 'tool-result-success', 'tool-result-error');
111
+ toolUseEl.classList.add(isError ? 'has-error' : 'has-success', isError ? 'tool-result-error' : 'tool-result-success');
112
+
113
+ const summary = toolUseEl.querySelector(':scope > summary');
114
+ if (summary && !summary.querySelector('.folded-tool-status')) {
115
+ const statusSvg = isError
116
+ ? '<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>'
117
+ : '<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>';
118
+ const statusSpan = document.createElement('span');
119
+ statusSpan.className = 'folded-tool-status';
120
+ statusSpan.innerHTML = statusSvg;
121
+ summary.appendChild(statusSpan);
122
+ const startedAt = parseInt(toolUseEl.dataset.startedAt);
123
+ if (startedAt > 0) {
124
+ const ms = Date.now() - startedAt;
125
+ const label = ms < 1000 ? ms + 'ms' : (ms / 1000).toFixed(1) + 's';
126
+ const dur = document.createElement('span');
127
+ dur.className = 'folded-tool-duration';
128
+ dur.style.cssText = 'margin-left:0.375rem;font-size:0.6rem;opacity:0.45;font-weight:400';
129
+ dur.textContent = label;
130
+ summary.appendChild(dur);
131
+ }
132
+ }
133
+
134
+ const renderedContent = StreamingRenderer.renderSmartContentHTML(contentStr, this.escapeHtml.bind(this), true);
135
+ if (renderedContent && renderedContent.trim()) {
136
+ let toolUseBody = toolUseEl.querySelector(':scope > .folded-tool-body');
137
+ if (!toolUseBody) {
138
+ toolUseBody = document.createElement('div');
139
+ toolUseBody.className = 'folded-tool-body';
140
+ toolUseEl.appendChild(toolUseBody);
141
+ }
142
+ const resultContent = document.createElement('div');
143
+ resultContent.className = 'folded-tool-result-content';
144
+ resultContent.innerHTML = renderedContent;
145
+ toolUseBody.appendChild(resultContent);
146
+ }
147
+ }
148
+
149
+ });
@@ -0,0 +1,142 @@
1
+ Object.assign(StreamingRenderer.prototype, {
2
+ autoScroll() {
3
+ if (this._scrollRafPending || this._userScrolledUp) return;
4
+ this._scrollRafPending = true;
5
+ requestAnimationFrame(() => {
6
+ this._scrollRafPending = false;
7
+ if (this.scrollContainer) {
8
+ this._programmaticScroll = true;
9
+ try { this.scrollContainer.scrollTop = this.scrollContainer.scrollHeight; } catch (_) {}
10
+ this._programmaticScroll = false;
11
+ }
12
+ });
13
+ },
14
+
15
+ resetScrollState() {
16
+ this._userScrolledUp = false;
17
+ },
18
+
19
+ updateDOMNodeCount() {
20
+ this.domNodeCount = this.outputContainer?.querySelectorAll('[data-event-id]').length || 0;
21
+ },
22
+
23
+ escapeHtml(text) {
24
+ return window._escHtml(text);
25
+ },
26
+
27
+ formatFileSize(bytes) {
28
+ if (bytes === 0) return '0 B';
29
+ const k = 1024;
30
+ const sizes = ['B', 'KB', 'MB', 'GB'];
31
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
32
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
33
+ },
34
+
35
+ truncateContent(content, maxLength = 200) {
36
+ if (content.length <= maxLength) return content;
37
+ return content.substring(0, maxLength) + '...';
38
+ },
39
+
40
+ clear() {
41
+ if (this.outputContainer) {
42
+ this.outputContainer.innerHTML = '';
43
+ }
44
+ this.eventQueue = [];
45
+ this.eventHistory = [];
46
+ this.domNodeCount = 0;
47
+ this.dedupMap.clear();
48
+ },
49
+
50
+ getMetrics() {
51
+ return {
52
+ ...this.performanceMetrics,
53
+ domNodeCount: this.domNodeCount,
54
+ queueLength: this.eventQueue.length,
55
+ historyLength: this.eventHistory.length,
56
+ lastRenderTime: this.lastRenderTime
57
+ };
58
+ },
59
+
60
+ on(event, callback) {
61
+ if (!this.listeners[event]) {
62
+ this.listeners[event] = [];
63
+ }
64
+ this.listeners[event].push(callback);
65
+ },
66
+
67
+ emit(event, data) {
68
+ if (this.listeners[event]) {
69
+ this.listeners[event].forEach(callback => {
70
+ try {
71
+ callback(data);
72
+ } catch (e) {
73
+ console.error('Listener error:', e);
74
+ }
75
+ });
76
+ }
77
+ },
78
+
79
+ renderBlockHeader(block, context = {}) {
80
+ if (!block || !block.type) return null;
81
+
82
+ const typeLabel = block.type.charAt(0).toUpperCase() + block.type.slice(1).replace(/_/g, ' ');
83
+ const summary = document.createElement('summary');
84
+ summary.style.cursor = 'pointer';
85
+ summary.style.userSelect = 'none';
86
+ summary.className = 'block-header-summary';
87
+
88
+ let summaryText = typeLabel;
89
+ if (block.type === 'code' && block.language) {
90
+ summaryText += ` (${block.language})`;
91
+ } else if (block.type === 'bash' && block.source) {
92
+ summaryText += ` - ${block.source}`;
93
+ } else if (block.type === 'tool_use' && block.name) {
94
+ summaryText += ` - ${block.name}`;
95
+ } else if (block.type === 'text' && block.text) {
96
+ const preview = block.text.substring(0, 60).replace(/\n/g, ' ');
97
+ summaryText = preview + (block.text.length > 60 ? '...' : '');
98
+ }
99
+
100
+ summary.textContent = summaryText;
101
+
102
+ const details = document.createElement('details');
103
+ details.className = `block-${block.type}`;
104
+ details.classList.add(this._getBlockTypeClass(block.type));
105
+ details.setAttribute('data-block-type', block.type);
106
+ details.setAttribute('data-lazy-load', 'pending');
107
+ details.open = block.type === 'success' || (block.type === 'tool_result' && !block.is_error);
108
+ details.appendChild(summary);
109
+
110
+ details.addEventListener('toggle', async (e) => {
111
+ if (details.open && details.getAttribute('data-lazy-load') === 'pending') {
112
+ details.setAttribute('data-lazy-load', 'loading');
113
+ try {
114
+ const body = this.renderBlock(block, context);
115
+ if (body && body !== summary) {
116
+ details.appendChild(body);
117
+ }
118
+ details.setAttribute('data-lazy-load', 'loaded');
119
+ } catch (err) {
120
+ console.error('Failed to lazy-load block:', err);
121
+ details.setAttribute('data-lazy-load', 'failed');
122
+ }
123
+ }
124
+ }, { once: false });
125
+
126
+ return details;
127
+ },
128
+
129
+ destroy() {
130
+ if (this.observer) {
131
+ this.observer.disconnect();
132
+ }
133
+ if (this.resizeObserver) {
134
+ this.resizeObserver.disconnect();
135
+ }
136
+ if (this.batchTimer) {
137
+ clearTimeout(this.batchTimer);
138
+ }
139
+ this.listeners = {};
140
+ this.clear();
141
+ }
142
+ });
@@ -0,0 +1,181 @@
1
+ Object.assign(StreamingRenderer, {
2
+ renderSmartContentHTML(contentStr, escapeHtml, flat = false) {
3
+ const trimmed = contentStr.trim();
4
+ const esc = escapeHtml || window._escHtml;
5
+
6
+ if (trimmed.startsWith('data:image/')) {
7
+ return `<div style="padding:0.5rem"><img src="${esc(trimmed)}" style="max-width:100%;max-height:24rem;border-radius:0.375rem" loading="lazy"></div>`;
8
+ }
9
+
10
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
11
+ try {
12
+ const parsed = JSON.parse(trimmed);
13
+
14
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed[0] && parsed[0].type === 'text') {
15
+ const textParts = parsed.filter(b => b.type === 'text' && b.text);
16
+ if (textParts.length > 0) {
17
+ const combined = textParts.map(b => b.text).join('\n');
18
+ return StreamingRenderer.renderSmartContentHTML(combined, esc, flat);
19
+ }
20
+ }
21
+
22
+ if (Array.isArray(parsed) && parsed.length > 0) {
23
+ const imgParts = parsed.filter(b => b.type === 'image' && b.source && b.source.type === 'base64' && b.source.data);
24
+ if (imgParts.length > 0) {
25
+ return imgParts.map(b => {
26
+ const mime = b.source.media_type || 'image/png';
27
+ 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>`;
28
+ }).join('');
29
+ }
30
+ }
31
+
32
+ return `<div style="padding:0.5rem 0.75rem">${StreamingRenderer.renderParamsHTML(parsed, 0, esc)}</div>`;
33
+ } catch (e) {
34
+ }
35
+ }
36
+
37
+ const lines = trimmed.split('\n');
38
+ const isCatNOutput = lines.length > 1 && lines[0].match(/^\s*\d+→/);
39
+ const isGrepOutput = lines.length > 1 && lines[0].match(/^\s*\d+-/);
40
+
41
+ if (isCatNOutput || isGrepOutput) {
42
+ const cleanedLines = lines.map(line => {
43
+ if (line === '--') return null;
44
+
45
+ const match = line.match(/^\s*\d+[→\-:](.*)/);
46
+ return match ? match[1] : line;
47
+ }).filter(line => line !== null);
48
+ const cleanedContent = cleanedLines.join('\n');
49
+
50
+ return StreamingRenderer.renderCodeWithHighlight(cleanedContent, esc, flat);
51
+ }
52
+
53
+ const systemReminderPattern = /<system-reminder>([\s\S]*?)<\/system-reminder>/g;
54
+ const systemReminders = [];
55
+ let contentWithoutReminders = trimmed;
56
+
57
+ let reminderMatch;
58
+ while ((reminderMatch = systemReminderPattern.exec(trimmed)) !== null) {
59
+ systemReminders.push(reminderMatch[1].trim());
60
+ contentWithoutReminders = contentWithoutReminders.replace(reminderMatch[0], '');
61
+ }
62
+
63
+ contentWithoutReminders = contentWithoutReminders.trim();
64
+
65
+ const successPatterns = [
66
+ /^Success\s+toolu_[\w]+$/m,
67
+ /^The file .* has been (updated|created|modified)/,
68
+ /^Here's the result of running `cat -n`/,
69
+ /^Applied \d+ edits? to/,
70
+ /^\w+ tool completed successfully/
71
+ ];
72
+
73
+ const hasSuccessPattern = successPatterns.some(pattern => pattern.test(contentWithoutReminders));
74
+
75
+ if (hasSuccessPattern) {
76
+ const contentLines = contentWithoutReminders.split('\n');
77
+ let successEndIndex = -1;
78
+ let codeStartIndex = -1;
79
+
80
+ for (let i = 0; i < contentLines.length; i++) {
81
+ const line = contentLines[i];
82
+ if (line.match(/^Success\s+toolu_/)) {
83
+ successEndIndex = i;
84
+ for (let j = i + 1; j < contentLines.length; j++) {
85
+ if (contentLines[j].trim() && !contentLines[j].match(/^The file|^Here's the result/)) {
86
+ codeStartIndex = j;
87
+ break;
88
+ }
89
+ }
90
+ break;
91
+ } else if (line.match(/^The file .* has been|^Applied \d+ edits? to|^Replaced|^Created|^Deleted/)) {
92
+ for (let j = i + 1; j < contentLines.length; j++) {
93
+ if (contentLines[j].match(/^Here's the result|^\s*\d+→/)) {
94
+ if (contentLines[j].match(/^Here's the result/)) {
95
+ codeStartIndex = j + 1;
96
+ } else {
97
+ codeStartIndex = j;
98
+ }
99
+ break;
100
+ } else if (contentLines[j].trim() && !contentLines[j].match(/^cat -n|^Running/)) {
101
+ codeStartIndex = j;
102
+ break;
103
+ }
104
+ }
105
+ if (codeStartIndex === -1) {
106
+ codeStartIndex = i + 2;
107
+ }
108
+ successEndIndex = codeStartIndex - 1;
109
+ break;
110
+ }
111
+ }
112
+
113
+ if (codeStartIndex > 0 && codeStartIndex < contentLines.length) {
114
+ const beforeCode = contentLines.slice(0, codeStartIndex).join('\n');
115
+ let codeContent = contentLines.slice(codeStartIndex).join('\n');
116
+
117
+ if (codeContent.match(/^\s*\d+→/m)) {
118
+ const codeLines = codeContent.split('\n');
119
+ codeContent = codeLines.map(line => {
120
+ const match = line.match(/^\s*\d+→(.*)/);
121
+ return match ? match[1] : line;
122
+ }).join('\n');
123
+ }
124
+
125
+ let html = '';
126
+
127
+ if (beforeCode.trim()) {
128
+ html += `<div style="color:var(--color-success);font-weight:600;margin-bottom:0.75rem;font-size:0.9rem">${esc(beforeCode.trim())}</div>`;
129
+ }
130
+
131
+ if (codeContent.trim()) {
132
+ html += StreamingRenderer.renderCodeWithHighlight(codeContent, esc, flat);
133
+ }
134
+
135
+ if (systemReminders.length > 0) {
136
+ html += StreamingRenderer.renderSystemReminders(systemReminders, esc);
137
+ }
138
+
139
+ return html;
140
+ }
141
+ }
142
+
143
+ if (systemReminders.length > 0) {
144
+ let html = '';
145
+
146
+ if (contentWithoutReminders) {
147
+ if (StreamingRenderer.detectCodeContent(contentWithoutReminders)) {
148
+ html += StreamingRenderer.renderCodeWithHighlight(contentWithoutReminders, esc, flat);
149
+ } else {
150
+ html += `<pre class="tool-result-pre">${esc(contentWithoutReminders)}</pre>`;
151
+ }
152
+ }
153
+
154
+ html += StreamingRenderer.renderSystemReminders(systemReminders, esc);
155
+ return html;
156
+ }
157
+
158
+ const allFilePaths = lines.length > 1 && lines.every(l => {
159
+ const t = l.trim();
160
+ return t === '' || t.startsWith('/') || /^[A-Za-z]:[\\\/]/.test(t);
161
+ });
162
+ if (allFilePaths && lines.filter(l => l.trim()).length > 0) {
163
+ const fileHtml = lines.filter(l => l.trim()).map(l => {
164
+ const p = l.trim();
165
+ const parts = pathSplit(p);
166
+ const name = parts.pop();
167
+ const dir = parts.join('/');
168
+ 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">&#128196;</span><span style="color:var(--color-text-secondary)">${esc(dir)}/</span><span style="font-weight:600">${esc(name)}</span></div>`;
169
+ }).join('');
170
+ return `<div style="padding:0.625rem 1rem">${fileHtml}</div>`;
171
+ }
172
+
173
+ const looksLikeCode = StreamingRenderer.detectCodeContent(trimmed);
174
+ if (looksLikeCode) {
175
+ return StreamingRenderer.renderCodeWithHighlight(trimmed, esc, flat);
176
+ }
177
+
178
+ const displayContent = trimmed.length > 2000 ? trimmed.substring(0, 2000) + '\n... (truncated)' : trimmed;
179
+ return `<pre class="tool-result-pre">${esc(displayContent)}</pre>`;
180
+ }
181
+ });