agentgui 1.0.17 → 1.0.18
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/html-wrapper.js +117 -0
- package/package.json +1 -1
- package/server.js +12 -3
package/html-wrapper.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Wrapper for Claude Responses
|
|
3
|
+
* Converts plain text/markdown responses into beautiful HTML
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class HTMLWrapper {
|
|
7
|
+
/**
|
|
8
|
+
* Wrap plain text response in HTML with RippleUI styling
|
|
9
|
+
*/
|
|
10
|
+
static wrapResponse(text) {
|
|
11
|
+
if (!text || typeof text !== 'string') return text;
|
|
12
|
+
|
|
13
|
+
// If already HTML, return as-is
|
|
14
|
+
if (text.trim().startsWith('<')) return text;
|
|
15
|
+
|
|
16
|
+
// Parse markdown-style text and convert to HTML
|
|
17
|
+
const lines = text.split('\n');
|
|
18
|
+
const html = this.parseMarkdownToHTML(lines);
|
|
19
|
+
|
|
20
|
+
// Wrap in container
|
|
21
|
+
return `<div class="space-y-4 p-6 max-w-4xl">${html}</div>`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static parseMarkdownToHTML(lines) {
|
|
25
|
+
let html = '';
|
|
26
|
+
let inCodeBlock = false;
|
|
27
|
+
let codeLanguage = 'text';
|
|
28
|
+
let codeContent = '';
|
|
29
|
+
let listItems = [];
|
|
30
|
+
let inList = false;
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
const line = lines[i];
|
|
34
|
+
|
|
35
|
+
// Code blocks
|
|
36
|
+
if (line.match(/^```(\w+)?$/)) {
|
|
37
|
+
if (!inCodeBlock) {
|
|
38
|
+
// Start code block
|
|
39
|
+
inCodeBlock = true;
|
|
40
|
+
codeLanguage = line.match(/```(\w+)?/)?.[1] || 'text';
|
|
41
|
+
codeContent = '';
|
|
42
|
+
} else {
|
|
43
|
+
// End code block
|
|
44
|
+
inCodeBlock = false;
|
|
45
|
+
html += `<pre class="bg-gray-900 text-white p-4 rounded-lg overflow-x-auto"><code class="language-${codeLanguage}">${this.escapeHtml(codeContent)}</code></pre>`;
|
|
46
|
+
codeContent = '';
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (inCodeBlock) {
|
|
52
|
+
codeContent += (codeContent ? '\n' : '') + line;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Headings
|
|
57
|
+
if (line.match(/^#+\s/)) {
|
|
58
|
+
const level = line.match(/^#+/)[0].length;
|
|
59
|
+
const heading = line.replace(/^#+\s/, '');
|
|
60
|
+
html += `<h${level} class="text-${level === 1 ? '3xl' : level === 2 ? '2xl' : 'xl'} font-bold text-gray-900 mt-4">${this.escapeHtml(heading)}</h${level}>`;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Lists
|
|
65
|
+
if (line.match(/^[-*•]\s/) || line.match(/^\d+\.\s/)) {
|
|
66
|
+
const itemText = line.replace(/^[-*•\d+.]\s+/, '');
|
|
67
|
+
if (!inList) {
|
|
68
|
+
inList = true;
|
|
69
|
+
listItems = [];
|
|
70
|
+
}
|
|
71
|
+
listItems.push(itemText);
|
|
72
|
+
continue;
|
|
73
|
+
} else if (inList && line.trim()) {
|
|
74
|
+
// End list
|
|
75
|
+
html += `<ul class="list-none space-y-2 ml-0"><li class="p-3 bg-gray-100 rounded border-l-4 border-blue-500">${listItems.map(item => `• ${this.escapeHtml(item)}`).join('</li><li class="p-3 bg-gray-100 rounded border-l-4 border-blue-500">')}</li></ul>`;
|
|
76
|
+
inList = false;
|
|
77
|
+
listItems = [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Empty lines
|
|
81
|
+
if (!line.trim()) {
|
|
82
|
+
if (!html.endsWith('</p>')) html += '<br>';
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Regular paragraphs
|
|
87
|
+
if (line.trim()) {
|
|
88
|
+
// Format inline markdown
|
|
89
|
+
let formatted = this.escapeHtml(line);
|
|
90
|
+
formatted = formatted.replace(/\*\*(.*?)\*\*/g, '<strong class="font-bold">$1</strong>');
|
|
91
|
+
formatted = formatted.replace(/\*(.*?)\*/g, '<em class="italic">$1</em>');
|
|
92
|
+
formatted = formatted.replace(/`([^`]+)`/g, '<code class="bg-gray-200 px-2 py-1 rounded font-mono text-sm">$1</code>');
|
|
93
|
+
|
|
94
|
+
html += `<p class="text-gray-700 leading-relaxed">${formatted}</p>`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Close any remaining list
|
|
99
|
+
if (inList) {
|
|
100
|
+
html += `<ul class="list-none space-y-2 ml-0"><li class="p-3 bg-gray-100 rounded border-l-4 border-blue-500">${listItems.map(item => `• ${this.escapeHtml(item)}`).join('</li><li class="p-3 bg-gray-100 rounded border-l-4 border-blue-500">')}</li></ul>`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return html;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static escapeHtml(text) {
|
|
107
|
+
if (typeof text !== 'string') return '';
|
|
108
|
+
return text
|
|
109
|
+
.replace(/&/g, '&')
|
|
110
|
+
.replace(/</g, '<')
|
|
111
|
+
.replace(/>/g, '>')
|
|
112
|
+
.replace(/"/g, '"')
|
|
113
|
+
.replace(/'/g, ''');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default HTMLWrapper;
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -8,6 +8,7 @@ import { execSync } from 'child_process';
|
|
|
8
8
|
import { queries } from './database.js';
|
|
9
9
|
import ACPConnection from './acp-launcher.js';
|
|
10
10
|
import { ResponseFormatter } from './response-formatter.js';
|
|
11
|
+
import { HTMLWrapper } from './html-wrapper.js';
|
|
11
12
|
|
|
12
13
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
const PORT = process.env.PORT || 3000;
|
|
@@ -337,7 +338,13 @@ async function processMessage(conversationId, messageId, sessionId, content, age
|
|
|
337
338
|
const result = await conn.sendPrompt(content);
|
|
338
339
|
conn.onUpdate = null;
|
|
339
340
|
|
|
340
|
-
|
|
341
|
+
let responseText = fullText || result?.result || (result?.stopReason ? `Completed: ${result.stopReason}` : 'No response.');
|
|
342
|
+
|
|
343
|
+
// Wrap response in HTML if it's not already
|
|
344
|
+
const isHTML = responseText.trim().startsWith('<');
|
|
345
|
+
if (!isHTML) {
|
|
346
|
+
responseText = HTMLWrapper.wrapResponse(responseText);
|
|
347
|
+
}
|
|
341
348
|
|
|
342
349
|
// Segment and format the response for better display
|
|
343
350
|
const segments = ResponseFormatter.segmentResponse(responseText);
|
|
@@ -348,12 +355,14 @@ async function processMessage(conversationId, messageId, sessionId, content, age
|
|
|
348
355
|
blocks,
|
|
349
356
|
segments,
|
|
350
357
|
metadata,
|
|
351
|
-
updateChunks
|
|
358
|
+
updateChunks,
|
|
359
|
+
isHTML: true
|
|
352
360
|
} : {
|
|
353
361
|
text: responseText,
|
|
354
362
|
segments,
|
|
355
363
|
metadata,
|
|
356
|
-
updateChunks
|
|
364
|
+
updateChunks,
|
|
365
|
+
isHTML: true
|
|
357
366
|
};
|
|
358
367
|
|
|
359
368
|
const assistantMessage = queries.createMessage(conversationId, 'assistant', messageContent);
|