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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.89",
3
+ "version": "1.0.90",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
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
- debugLog(`[processMessage] Calling runClaudeWithStreaming with prompt: "${content.substring(0, 50)}..."`);
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${content}`;
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
- html += `<div class="block-text">${this.escapeHtml(block.text || '')}</div>`;
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;
@@ -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
- html += `<div class="message-text">${this.escapeHtml(block.text)}</div>`;
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
- max-width: 100%;
823
+ margin: 0.75rem 0;
825
824
  }
826
825
 
827
826
  .html-header {
828
827
  padding: 0.5rem 0.75rem;
829
- background: var(--bg-tertiary);
828
+ background: #3b82f6;
830
829
  font-weight: 500;
831
- font-size: 0.875rem;
832
- color: var(--text-primary);
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 {