agentgui 1.0.89 → 1.0.90
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 +40 -0
- package/package.json +1 -1
- package/server.js +8 -2
- package/static/app.js +64 -2
- package/static/js/client.js +72 -1
- package/static/styles.css +26 -5
package/.prd
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# AgentGUI HTML Rendering Implementation - IN PROGRESS
|
|
2
|
+
|
|
3
|
+
## Task
|
|
4
|
+
Implement markdown code block parsing within text blocks to enable HTML rendering in conversation messages.
|
|
5
|
+
|
|
6
|
+
## Work Items
|
|
7
|
+
|
|
8
|
+
### 1. Implement parseMarkdownCodeBlocks() in client.js
|
|
9
|
+
- Parse ```language\ncode``` patterns from text blocks
|
|
10
|
+
- Use regex: /```(\w*)\n([\s\S]*?)```/g
|
|
11
|
+
- Return array of parts with type ('text' or 'code') and content/language/code
|
|
12
|
+
- Handle text before, after, and between code blocks
|
|
13
|
+
|
|
14
|
+
### 2. Update renderMessageContent() in client.js
|
|
15
|
+
- For text blocks, check if they contain markdown code blocks
|
|
16
|
+
- If markdown blocks found, parse and render each part
|
|
17
|
+
- For code blocks with language='html', render with innerHTML
|
|
18
|
+
- For other code blocks, render as escaped text with monospace font
|
|
19
|
+
- Apply "Rendered HTML" badge styling for HTML blocks
|
|
20
|
+
|
|
21
|
+
### 3. Add CSS styling for rendered HTML blocks
|
|
22
|
+
- .html-rendered-label: blue background, white text, small font, padding
|
|
23
|
+
- .html-content: white background, border, padding, overflow handling
|
|
24
|
+
- Support both light and dark modes with CSS variables
|
|
25
|
+
|
|
26
|
+
### 4. Test HTML rendering
|
|
27
|
+
- Open "HTML Test" conversation in agent-browser
|
|
28
|
+
- Verify HTML table renders as actual table element
|
|
29
|
+
- Verify "Rendered HTML" badge appears
|
|
30
|
+
- Verify no escaped HTML entities visible
|
|
31
|
+
- Verify no markdown delimiters visible
|
|
32
|
+
- Verify CSS styling applies correctly
|
|
33
|
+
|
|
34
|
+
## Files to Modify
|
|
35
|
+
- /config/workspace/agentgui/static/js/client.js
|
|
36
|
+
- Add parseMarkdownCodeBlocks() method
|
|
37
|
+
- Update renderMessageContent() to use parser for text blocks
|
|
38
|
+
|
|
39
|
+
## Status
|
|
40
|
+
PENDING: Implementation not yet complete
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -508,9 +508,15 @@ async function processMessage(conversationId, messageId, content, agentId) {
|
|
|
508
508
|
const cwd = '/config';
|
|
509
509
|
const actualAgentId = agentId || 'claude-code';
|
|
510
510
|
|
|
511
|
-
|
|
511
|
+
// Handle both string content and object content (for structured messages)
|
|
512
|
+
let contentStr = content;
|
|
513
|
+
if (typeof content === 'object') {
|
|
514
|
+
contentStr = JSON.stringify(content);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
debugLog(`[processMessage] Calling runClaudeWithStreaming with prompt: "${contentStr.substring(0, 50)}..."`);
|
|
512
518
|
// Prepend system prompt to user content
|
|
513
|
-
const promptWithSystem = `${SYSTEM_PROMPT}\n\n${
|
|
519
|
+
const promptWithSystem = `${SYSTEM_PROMPT}\n\n${contentStr}`;
|
|
514
520
|
const outputs = await runClaudeWithStreaming(promptWithSystem, cwd, actualAgentId);
|
|
515
521
|
debugLog(`[processMessage] Claude returned ${outputs.length} outputs`);
|
|
516
522
|
|
package/static/app.js
CHANGED
|
@@ -220,9 +220,20 @@ class GMGUIApp {
|
|
|
220
220
|
let html = '<div class="message-block">';
|
|
221
221
|
|
|
222
222
|
switch (block.type) {
|
|
223
|
-
case 'text':
|
|
224
|
-
|
|
223
|
+
case 'text': {
|
|
224
|
+
const text = block.text || '';
|
|
225
|
+
const parts = this.parseMarkdownCodeBlocks(text);
|
|
226
|
+
html += '<div class="block-text">';
|
|
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
235
|
break;
|
|
236
|
+
}
|
|
226
237
|
|
|
227
238
|
case 'tool_use':
|
|
228
239
|
html += `<div class="block-tool-use">`;
|
|
@@ -405,6 +416,57 @@ class GMGUIApp {
|
|
|
405
416
|
}
|
|
406
417
|
}
|
|
407
418
|
|
|
419
|
+
parseMarkdownCodeBlocks(text) {
|
|
420
|
+
const parts = [];
|
|
421
|
+
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
422
|
+
let lastIndex = 0;
|
|
423
|
+
let match;
|
|
424
|
+
|
|
425
|
+
while ((match = codeBlockRegex.exec(text)) !== null) {
|
|
426
|
+
// Add text before code block
|
|
427
|
+
if (match.index > lastIndex) {
|
|
428
|
+
parts.push({
|
|
429
|
+
type: 'text',
|
|
430
|
+
content: text.substring(lastIndex, match.index)
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Add code block
|
|
435
|
+
const language = match[1] || 'text';
|
|
436
|
+
const code = match[2];
|
|
437
|
+
parts.push({
|
|
438
|
+
type: 'code',
|
|
439
|
+
language,
|
|
440
|
+
content: code
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
lastIndex = codeBlockRegex.lastIndex;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Add remaining text
|
|
447
|
+
if (lastIndex < text.length) {
|
|
448
|
+
parts.push({
|
|
449
|
+
type: 'text',
|
|
450
|
+
content: text.substring(lastIndex)
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return parts.length > 0 ? parts : [{ type: 'text', content: text }];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
renderCodeBlock(language, code) {
|
|
458
|
+
if (language === 'html') {
|
|
459
|
+
return `<div class="html-block">
|
|
460
|
+
<div class="html-header">Rendered HTML</div>
|
|
461
|
+
<div class="html-content">${code}</div>
|
|
462
|
+
</div>`;
|
|
463
|
+
} else {
|
|
464
|
+
return `<div class="code-block" data-language="${this.escapeHtml(language)}">
|
|
465
|
+
<pre><code>${this.escapeHtml(code)}</code></pre>
|
|
466
|
+
</div>`;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
408
470
|
escapeHtml(text) {
|
|
409
471
|
const div = document.createElement('div');
|
|
410
472
|
div.textContent = text;
|
package/static/js/client.js
CHANGED
|
@@ -363,6 +363,69 @@ class AgentGUIClient {
|
|
|
363
363
|
this.emit('message:created', data);
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Parse markdown code blocks from text
|
|
368
|
+
* Returns array of parts with type ('text' or 'code') and content/language/code
|
|
369
|
+
*/
|
|
370
|
+
parseMarkdownCodeBlocks(text) {
|
|
371
|
+
const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
|
|
372
|
+
const parts = [];
|
|
373
|
+
let lastIndex = 0;
|
|
374
|
+
let match;
|
|
375
|
+
|
|
376
|
+
while ((match = codeBlockRegex.exec(text)) !== null) {
|
|
377
|
+
// Add text before the code block
|
|
378
|
+
if (match.index > lastIndex) {
|
|
379
|
+
parts.push({
|
|
380
|
+
type: 'text',
|
|
381
|
+
content: text.substring(lastIndex, match.index)
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
// Add the code block
|
|
385
|
+
parts.push({
|
|
386
|
+
type: 'code',
|
|
387
|
+
language: match[1] || 'plain',
|
|
388
|
+
code: match[2]
|
|
389
|
+
});
|
|
390
|
+
lastIndex = codeBlockRegex.lastIndex;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Add remaining text after last code block
|
|
394
|
+
if (lastIndex < text.length) {
|
|
395
|
+
parts.push({
|
|
396
|
+
type: 'text',
|
|
397
|
+
content: text.substring(lastIndex)
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// If no code blocks found, return the text as-is
|
|
402
|
+
if (parts.length === 0) {
|
|
403
|
+
return [{ type: 'text', content: text }];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return parts;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Render a markdown code block part
|
|
411
|
+
*/
|
|
412
|
+
renderCodeBlock(language, code) {
|
|
413
|
+
if (language.toLowerCase() === 'html') {
|
|
414
|
+
return `
|
|
415
|
+
<div class="message-code">
|
|
416
|
+
<div class="html-rendered-label mb-2 p-2 bg-blue-50 dark:bg-blue-900 rounded border border-blue-200 dark:border-blue-700 text-xs text-blue-700 dark:text-blue-300">
|
|
417
|
+
Rendered HTML
|
|
418
|
+
</div>
|
|
419
|
+
<div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">
|
|
420
|
+
${code}
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
`;
|
|
424
|
+
} else {
|
|
425
|
+
return `<div class="message-code"><pre>${this.escapeHtml(code)}</pre></div>`;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
366
429
|
/**
|
|
367
430
|
* Render message content based on type
|
|
368
431
|
*/
|
|
@@ -374,7 +437,15 @@ class AgentGUIClient {
|
|
|
374
437
|
if (content.blocks && Array.isArray(content.blocks)) {
|
|
375
438
|
content.blocks.forEach(block => {
|
|
376
439
|
if (block.type === 'text') {
|
|
377
|
-
|
|
440
|
+
// Parse markdown code blocks from text
|
|
441
|
+
const parts = this.parseMarkdownCodeBlocks(block.text);
|
|
442
|
+
parts.forEach(part => {
|
|
443
|
+
if (part.type === 'text') {
|
|
444
|
+
html += `<div class="message-text">${this.escapeHtml(part.content)}</div>`;
|
|
445
|
+
} else if (part.type === 'code') {
|
|
446
|
+
html += this.renderCodeBlock(part.language, part.code);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
378
449
|
} else if (block.type === 'code_block') {
|
|
379
450
|
// Render HTML code blocks as actual HTML elements
|
|
380
451
|
if (block.language === 'html') {
|
package/static/styles.css
CHANGED
|
@@ -816,21 +816,22 @@ html, body {
|
|
|
816
816
|
}
|
|
817
817
|
|
|
818
818
|
.html-block {
|
|
819
|
-
flex: 0 1 100%;
|
|
820
819
|
border: 1px solid var(--border-color);
|
|
821
820
|
border-radius: 0.5rem;
|
|
822
821
|
overflow: visible;
|
|
823
822
|
background: var(--bg-secondary);
|
|
824
|
-
|
|
823
|
+
margin: 0.75rem 0;
|
|
825
824
|
}
|
|
826
825
|
|
|
827
826
|
.html-header {
|
|
828
827
|
padding: 0.5rem 0.75rem;
|
|
829
|
-
background:
|
|
828
|
+
background: #3b82f6;
|
|
830
829
|
font-weight: 500;
|
|
831
|
-
font-size: 0.
|
|
832
|
-
color:
|
|
830
|
+
font-size: 0.75rem;
|
|
831
|
+
color: white;
|
|
833
832
|
border-bottom: 1px solid var(--border-color);
|
|
833
|
+
text-transform: uppercase;
|
|
834
|
+
letter-spacing: 0.5px;
|
|
834
835
|
}
|
|
835
836
|
|
|
836
837
|
.html-content {
|
|
@@ -838,6 +839,26 @@ html, body {
|
|
|
838
839
|
overflow-x: auto;
|
|
839
840
|
}
|
|
840
841
|
|
|
842
|
+
.code-block {
|
|
843
|
+
border: 1px solid var(--border-color);
|
|
844
|
+
border-radius: 0.5rem;
|
|
845
|
+
overflow-x: auto;
|
|
846
|
+
background: var(--bg-tertiary);
|
|
847
|
+
margin: 0.75rem 0;
|
|
848
|
+
font-family: 'Monaco', 'Courier New', monospace;
|
|
849
|
+
font-size: 0.8rem;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.code-block pre {
|
|
853
|
+
margin: 0;
|
|
854
|
+
padding: 0.75rem;
|
|
855
|
+
color: var(--text-secondary);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.code-block code {
|
|
859
|
+
color: var(--text-secondary);
|
|
860
|
+
}
|
|
861
|
+
|
|
841
862
|
|
|
842
863
|
|
|
843
864
|
.image-block {
|