agentgui 1.0.107 → 1.0.108
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/.prd +66 -0
- package/package.json +1 -1
- package/static/app.js +118 -10
- package/static/styles.css +59 -20
package/.prd
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Response Block Rendering Analysis & Fix
|
|
2
|
+
|
|
3
|
+
## Problem Statement
|
|
4
|
+
Response blocks in GUI are not being beautified/rendered correctly. Issues:
|
|
5
|
+
- Blocks may be separated incorrectly
|
|
6
|
+
- Detection logic may be failing
|
|
7
|
+
- Rendering may not match intended HTML structure
|
|
8
|
+
- Need perfect rendering with no edge cases or surprises
|
|
9
|
+
|
|
10
|
+
## Investigation Phase (Completed)
|
|
11
|
+
- [x] Search for response block rendering code in app.js
|
|
12
|
+
- [x] Search for block type definitions and separators
|
|
13
|
+
- [x] Review message bubble structure and flex/layout CSS
|
|
14
|
+
- [x] Examine response block detection logic in app.js
|
|
15
|
+
- [x] Check HTML/CSS rendering for response blocks
|
|
16
|
+
- [x] Review message rendering pipeline
|
|
17
|
+
|
|
18
|
+
## Root Cause Analysis (Findings)
|
|
19
|
+
- [x] Traced message flow: Claude outputs (stream-json) → claude-runner.js parses JSON → processMessage collects blocks
|
|
20
|
+
- [x] server.js line 738: extracts output.message.content blocks from assistant responses
|
|
21
|
+
- [x] app.js lines 194-209: renderMessageBlock handles each block based on type field
|
|
22
|
+
- [x] Block types: 'text', 'tool_use', 'tool_result', 'file_operation'
|
|
23
|
+
- [x] Text blocks parsed for markdown code blocks with regex
|
|
24
|
+
- [x] Found potential issue: blocks may not be properly formatted/beautified
|
|
25
|
+
- [x] HTML structure looks correct but need to verify actual output
|
|
26
|
+
|
|
27
|
+
## Edge Cases & Issues
|
|
28
|
+
- [ ] Multiple consecutive blocks of same type
|
|
29
|
+
- [ ] Empty blocks or whitespace handling
|
|
30
|
+
- [ ] Mixed block types in same message
|
|
31
|
+
- [ ] Long content overflow in blocks
|
|
32
|
+
- [ ] Special characters in block content
|
|
33
|
+
- [ ] Code/pre tag interaction with beautification
|
|
34
|
+
|
|
35
|
+
## Root Cause Identified
|
|
36
|
+
ROOT CAUSE: Text blocks from Claude contain MARKDOWN but are being rendered as plain escaped text
|
|
37
|
+
- Claude outputs blocks with type:'text' containing markdown (headings, lists, bold, code, links)
|
|
38
|
+
- Current code only extracts code blocks with regex, escapes everything else
|
|
39
|
+
- Markdown structure (headings, lists, etc.) is lost
|
|
40
|
+
|
|
41
|
+
## Solution: Implement Markdown Parser (Completed)
|
|
42
|
+
- [x] Create markdown to HTML converter (markdownToHtml method)
|
|
43
|
+
- [x] Handle markdown syntax: # headings, - lists, **bold**, `code`, ```code blocks```, links
|
|
44
|
+
- [x] Integrate parser into renderMessageBlock for text type
|
|
45
|
+
- [x] Ensure HTML is properly escaped to prevent XSS
|
|
46
|
+
- [x] Tested all markdown features: h1-h6, ul/ol lists, bold/italic, inline code, code blocks, links
|
|
47
|
+
- [x] Verify blocks render with proper semantic HTML
|
|
48
|
+
|
|
49
|
+
## CSS Updates (Completed)
|
|
50
|
+
- [x] Added styling for markdown elements (h1-h3, ul/ol, lists, inline code, links)
|
|
51
|
+
- [x] Remove duplicate .code-block definition (removed old version at lines 1454-1470)
|
|
52
|
+
- [x] Keep modern version at line 842 with CSS variables
|
|
53
|
+
|
|
54
|
+
## Execution & Verification (Ready)
|
|
55
|
+
- [ ] Commit changes to git
|
|
56
|
+
- [ ] Test with real Claude Code execution output via browser
|
|
57
|
+
- [ ] Verify blocks render with perfect formatting
|
|
58
|
+
- [ ] Verify no edge cases or surprises
|
|
59
|
+
- [ ] Verify layout doesn't break on wide screens
|
|
60
|
+
|
|
61
|
+
## Completion Criteria
|
|
62
|
+
- Response blocks detect correctly
|
|
63
|
+
- Blocks render with proper styling
|
|
64
|
+
- All block types display correctly
|
|
65
|
+
- No edge cases or surprises
|
|
66
|
+
- Verified through real execution
|
package/package.json
CHANGED
package/static/app.js
CHANGED
|
@@ -222,16 +222,8 @@ class GMGUIApp {
|
|
|
222
222
|
switch (block.type) {
|
|
223
223
|
case 'text': {
|
|
224
224
|
const text = block.text || '';
|
|
225
|
-
const
|
|
226
|
-
html +=
|
|
227
|
-
for (const part of parts) {
|
|
228
|
-
if (part.type === 'text') {
|
|
229
|
-
html += `<div>${this.escapeHtml(part.content)}</div>`;
|
|
230
|
-
} else if (part.type === 'code') {
|
|
231
|
-
html += this.renderCodeBlock(part.language, part.content);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
html += '</div>';
|
|
225
|
+
const beautified = this.markdownToHtml(text);
|
|
226
|
+
html += `<div class="block-text">${beautified}</div>`;
|
|
235
227
|
break;
|
|
236
228
|
}
|
|
237
229
|
|
|
@@ -454,6 +446,122 @@ class GMGUIApp {
|
|
|
454
446
|
return parts.length > 0 ? parts : [{ type: 'text', content: text }];
|
|
455
447
|
}
|
|
456
448
|
|
|
449
|
+
markdownToHtml(markdown) {
|
|
450
|
+
const lines = markdown.split('\n');
|
|
451
|
+
let html = '';
|
|
452
|
+
let inList = false;
|
|
453
|
+
let listType = null;
|
|
454
|
+
let i = 0;
|
|
455
|
+
|
|
456
|
+
while (i < lines.length) {
|
|
457
|
+
const line = lines[i];
|
|
458
|
+
const trimmed = line.trim();
|
|
459
|
+
|
|
460
|
+
// Code blocks
|
|
461
|
+
if (trimmed.startsWith('```')) {
|
|
462
|
+
if (inList) {
|
|
463
|
+
html += listType === 'ul' ? '</ul>' : '</ol>';
|
|
464
|
+
inList = false;
|
|
465
|
+
}
|
|
466
|
+
const match = trimmed.match(/^```(\w*)/);
|
|
467
|
+
const lang = (match && match[1]) || 'text';
|
|
468
|
+
i++;
|
|
469
|
+
const codeLines = [];
|
|
470
|
+
while (i < lines.length && !lines[i].trim().startsWith('```')) {
|
|
471
|
+
codeLines.push(lines[i]);
|
|
472
|
+
i++;
|
|
473
|
+
}
|
|
474
|
+
html += `<div class="code-block" data-language="${this.escapeHtml(lang)}"><pre><code>${this.escapeHtml(codeLines.join('\n'))}</code></pre></div>`;
|
|
475
|
+
i++;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Headings
|
|
480
|
+
if (trimmed.startsWith('#')) {
|
|
481
|
+
if (inList) {
|
|
482
|
+
html += listType === 'ul' ? '</ul>' : '</ol>';
|
|
483
|
+
inList = false;
|
|
484
|
+
}
|
|
485
|
+
const match = trimmed.match(/^(#+)\s+(.*)/);
|
|
486
|
+
if (match) {
|
|
487
|
+
const level = match[1].length;
|
|
488
|
+
const text = this.escapeAndFormatInline(match[2]);
|
|
489
|
+
html += `<h${level}>${text}</h${level}>`;
|
|
490
|
+
i++;
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Lists
|
|
496
|
+
if (trimmed.match(/^[-*+]\s/)) {
|
|
497
|
+
if (!inList) {
|
|
498
|
+
html += '<ul>';
|
|
499
|
+
inList = true;
|
|
500
|
+
listType = 'ul';
|
|
501
|
+
}
|
|
502
|
+
const match = trimmed.match(/^[-*+]\s+(.*)/);
|
|
503
|
+
if (match) {
|
|
504
|
+
const text = this.escapeAndFormatInline(match[1]);
|
|
505
|
+
html += `<li>${text}</li>`;
|
|
506
|
+
}
|
|
507
|
+
i++;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (trimmed.match(/^\d+\.\s/)) {
|
|
512
|
+
if (!inList || listType !== 'ol') {
|
|
513
|
+
if (inList) html += '</ul>';
|
|
514
|
+
html += '<ol>';
|
|
515
|
+
inList = true;
|
|
516
|
+
listType = 'ol';
|
|
517
|
+
}
|
|
518
|
+
const match = trimmed.match(/^\d+\.\s+(.*)/);
|
|
519
|
+
if (match) {
|
|
520
|
+
const text = this.escapeAndFormatInline(match[1]);
|
|
521
|
+
html += `<li>${text}</li>`;
|
|
522
|
+
}
|
|
523
|
+
i++;
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// End list if not a list item
|
|
528
|
+
if (inList && trimmed && !trimmed.match(/^[-*+]\s/) && !trimmed.match(/^\d+\.\s/)) {
|
|
529
|
+
html += listType === 'ul' ? '</ul>' : '</ol>';
|
|
530
|
+
inList = false;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Paragraphs
|
|
534
|
+
if (trimmed) {
|
|
535
|
+
const text = this.escapeAndFormatInline(trimmed);
|
|
536
|
+
html += `<p>${text}</p>`;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
i++;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Close any open list
|
|
543
|
+
if (inList) {
|
|
544
|
+
html += listType === 'ul' ? '</ul>' : '</ol>';
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return html;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
escapeAndFormatInline(text) {
|
|
551
|
+
text = this.escapeHtml(text);
|
|
552
|
+
// Bold and italic (must be before single asterisk)
|
|
553
|
+
text = text.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
554
|
+
text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
|
555
|
+
text = text.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
|
556
|
+
text = text.replace(/__(.*?)__/g, '<strong>$1</strong>');
|
|
557
|
+
text = text.replace(/_(.*?)_/g, '<em>$1</em>');
|
|
558
|
+
// Inline code
|
|
559
|
+
text = text.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
|
|
560
|
+
// Links
|
|
561
|
+
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
562
|
+
return text;
|
|
563
|
+
}
|
|
564
|
+
|
|
457
565
|
renderCodeBlock(language, code) {
|
|
458
566
|
if (language === 'html') {
|
|
459
567
|
return `<div class="html-block">
|
package/static/styles.css
CHANGED
|
@@ -1451,24 +1451,6 @@ html, body {
|
|
|
1451
1451
|
margin: 1rem 0;
|
|
1452
1452
|
}
|
|
1453
1453
|
|
|
1454
|
-
.code-block {
|
|
1455
|
-
background: #f5f5f5;
|
|
1456
|
-
border-left: 4px solid #007acc;
|
|
1457
|
-
padding: 1rem;
|
|
1458
|
-
border-radius: 4px;
|
|
1459
|
-
overflow-x: auto;
|
|
1460
|
-
font-family: 'Courier New', monospace;
|
|
1461
|
-
font-size: 0.9rem;
|
|
1462
|
-
line-height: 1.5;
|
|
1463
|
-
color: #333;
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
.code-block code {
|
|
1467
|
-
color: inherit;
|
|
1468
|
-
background: none;
|
|
1469
|
-
padding: 0;
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
1454
|
/* Inline code */
|
|
1473
1455
|
p code {
|
|
1474
1456
|
background: #f0f0f0;
|
|
@@ -1734,8 +1716,65 @@ p code {
|
|
|
1734
1716
|
|
|
1735
1717
|
.block-text {
|
|
1736
1718
|
color: var(--text-primary);
|
|
1737
|
-
|
|
1738
|
-
|
|
1719
|
+
line-height: 1.6;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
.block-text h1 {
|
|
1723
|
+
font-size: 1.75rem;
|
|
1724
|
+
font-weight: 700;
|
|
1725
|
+
margin: 1rem 0 0.5rem 0;
|
|
1726
|
+
color: var(--text-primary);
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
.block-text h2 {
|
|
1730
|
+
font-size: 1.5rem;
|
|
1731
|
+
font-weight: 700;
|
|
1732
|
+
margin: 0.875rem 0 0.5rem 0;
|
|
1733
|
+
color: var(--text-primary);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
.block-text h3 {
|
|
1737
|
+
font-size: 1.25rem;
|
|
1738
|
+
font-weight: 600;
|
|
1739
|
+
margin: 0.75rem 0 0.375rem 0;
|
|
1740
|
+
color: var(--text-primary);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
.block-text p {
|
|
1744
|
+
margin: 0.5rem 0;
|
|
1745
|
+
white-space: normal;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
.block-text ul,
|
|
1749
|
+
.block-text ol {
|
|
1750
|
+
margin: 0.5rem 0 0.5rem 1.5rem;
|
|
1751
|
+
padding-left: 1rem;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
.block-text li {
|
|
1755
|
+
margin: 0.25rem 0;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
.block-text code.inline-code {
|
|
1759
|
+
background: var(--bg-tertiary);
|
|
1760
|
+
color: var(--color-info);
|
|
1761
|
+
padding: 0.15rem 0.4rem;
|
|
1762
|
+
border-radius: 0.25rem;
|
|
1763
|
+
font-family: 'Courier New', monospace;
|
|
1764
|
+
font-size: 0.9em;
|
|
1765
|
+
white-space: nowrap;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
.block-text a {
|
|
1769
|
+
color: var(--color-primary);
|
|
1770
|
+
text-decoration: none;
|
|
1771
|
+
border-bottom: 1px solid transparent;
|
|
1772
|
+
transition: var(--transition-fast);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
.block-text a:hover {
|
|
1776
|
+
border-bottom-color: var(--color-primary);
|
|
1777
|
+
text-decoration: underline;
|
|
1739
1778
|
}
|
|
1740
1779
|
|
|
1741
1780
|
.block-tool-use {
|