agentgui 1.0.14 → 1.0.16
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/acp-launcher.js +93 -19
- package/hot-reload-manager.js +186 -0
- package/package.json +1 -1
- package/response-formatter.js +285 -0
- package/server.js +25 -2
- package/static/app.js +143 -3
- package/static/styles.css +160 -0
package/acp-launcher.js
CHANGED
|
@@ -33,19 +33,62 @@ function findBinary(paths) {
|
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const RIPPLEUI_SYSTEM_PROMPT = `ALWAYS respond with HTML using RippleUI components.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
const RIPPLEUI_SYSTEM_PROMPT = `CRITICAL: ALWAYS respond with styled HTML using RippleUI components. This is a web-based chat interface that ONLY renders HTML beautifully. NEVER respond with plain text.
|
|
37
|
+
|
|
38
|
+
FOR EVERY RESPONSE:
|
|
39
|
+
1. Wrap your ENTIRE response in a container div with proper styling
|
|
40
|
+
2. Use semantic HTML (h1-h6 for headings, p for paragraphs, ul/ol for lists)
|
|
41
|
+
3. Format code in <pre><code> blocks with language class
|
|
42
|
+
4. Use cards for organized information blocks
|
|
43
|
+
5. Use alerts for important information
|
|
44
|
+
6. Use tables for tabular data
|
|
45
|
+
7. Style everything with Tailwind CSS classes
|
|
46
|
+
|
|
47
|
+
RESPONSE STRUCTURE TEMPLATE:
|
|
48
|
+
\`\`\`html
|
|
49
|
+
<div class="space-y-4 p-4">
|
|
50
|
+
<h2 class="text-2xl font-bold">Main Heading</h2>
|
|
51
|
+
<p class="text-gray-700">Introductory text here.</p>
|
|
52
|
+
|
|
53
|
+
<div class="card bg-base-100 shadow-lg p-4">
|
|
54
|
+
<h3 class="text-lg font-semibold mb-2">Section Title</h3>
|
|
55
|
+
<p>Content goes here.</p>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<pre class="bg-base-200 p-4 rounded-lg overflow-x-auto"><code class="language-javascript">// Code example
|
|
59
|
+
const example = () => { };</code></pre>
|
|
60
|
+
|
|
61
|
+
<ul class="list-none space-y-2 ml-4">
|
|
62
|
+
<li class="p-2 bg-base-200 rounded">• Item one</li>
|
|
63
|
+
<li class="p-2 bg-base-200 rounded">• Item two</li>
|
|
64
|
+
</ul>
|
|
65
|
+
</div>
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
COMPONENT REFERENCE:
|
|
69
|
+
- Cards: <div class="card bg-base-100 shadow-lg p-6"><h3 class="font-bold">Title</h3><p>Content</p></div>
|
|
70
|
+
- Alerts: <div class="alert alert-info"><span>Message</span></div>
|
|
71
|
+
- Tables: <table class="table table-zebra"><thead><tr><th>Header</th></tr></thead><tbody><tr><td>Data</td></tr></tbody></table>
|
|
72
|
+
- Badges: <span class="badge badge-primary">Label</span>
|
|
73
|
+
- Code: <pre class="bg-base-200 p-4 rounded-lg overflow-x-auto"><code class="language-{lang}">{code}</code></pre>
|
|
74
|
+
- Lists: <ul class="list-none space-y-2"><li class="p-2 bg-base-200 rounded">Item</li></ul>
|
|
75
|
+
|
|
76
|
+
REQUIRED FOR ALL RESPONSES:
|
|
77
|
+
✓ Wrap response in HTML (usually a div with space-y-4 for vertical spacing)
|
|
78
|
+
✓ Use Tailwind classes: text-xl, font-bold, p-4, mb-3, rounded-lg, shadow, etc.
|
|
79
|
+
✓ Always use proper semantic HTML tags
|
|
80
|
+
✓ Format code blocks with language class for syntax highlighting
|
|
81
|
+
✓ Use colors: bg-blue-50, text-gray-700, border-blue-200 for visual hierarchy
|
|
82
|
+
✓ NEVER respond with raw text - ALWAYS wrap in HTML container
|
|
83
|
+
|
|
84
|
+
EXAMPLES OF GOOD RESPONSES:
|
|
85
|
+
- Questions: Wrap answer in a card with bold question and styled answer
|
|
86
|
+
- Code: Use <pre><code> with language class
|
|
87
|
+
- Lists: Use <ul> or <ol> with proper styling
|
|
88
|
+
- Instructions: Use numbered or bulleted lists in cards
|
|
89
|
+
- Explanations: Use cards for each concept, bold headings, clear spacing
|
|
90
|
+
|
|
91
|
+
Remember: The user sees raw HTML output. Make it beautiful and readable.`;
|
|
49
92
|
|
|
50
93
|
export default class ACPConnection {
|
|
51
94
|
constructor() {
|
|
@@ -255,13 +298,44 @@ export default class ACPConnection {
|
|
|
255
298
|
return this.sendRequest('session/set_mode', { sessionId: this.sessionId, modeId });
|
|
256
299
|
}
|
|
257
300
|
|
|
258
|
-
async injectSkills() {
|
|
301
|
+
async injectSkills(additionalContext = '') {
|
|
259
302
|
if (this.printMode) return {};
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
303
|
+
|
|
304
|
+
// Combine the system prompt with any additional context
|
|
305
|
+
const systemPrompt = additionalContext ? `${RIPPLEUI_SYSTEM_PROMPT}\n\n---\n\n${additionalContext}` : RIPPLEUI_SYSTEM_PROMPT;
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
const result = await this.sendRequest('session/skill_inject', {
|
|
309
|
+
sessionId: this.sessionId,
|
|
310
|
+
skills: [],
|
|
311
|
+
notification: [{ type: 'text', text: systemPrompt }]
|
|
312
|
+
});
|
|
313
|
+
return result;
|
|
314
|
+
} catch (e) {
|
|
315
|
+
// skill_inject may not be supported, try alternative methods
|
|
316
|
+
console.log(`[ACP] skill_inject failed (may not be supported): ${e.message}`);
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Inject system prompt as initial context
|
|
323
|
+
*/
|
|
324
|
+
async injectSystemContext() {
|
|
325
|
+
if (this.printMode) return {};
|
|
326
|
+
|
|
327
|
+
// Some versions may need the system prompt sent as the first message context
|
|
328
|
+
try {
|
|
329
|
+
await this.sendRequest('session/context', {
|
|
330
|
+
sessionId: this.sessionId,
|
|
331
|
+
context: RIPPLEUI_SYSTEM_PROMPT,
|
|
332
|
+
role: 'system'
|
|
333
|
+
});
|
|
334
|
+
return { success: true };
|
|
335
|
+
} catch (e) {
|
|
336
|
+
// This method may not be supported, silently fail
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
265
339
|
}
|
|
266
340
|
|
|
267
341
|
async sendPrompt(prompt) {
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hot Reload Manager
|
|
3
|
+
* Enables live reloading of client code and graceful server restarts without losing connections
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
export class HotReloadManager {
|
|
13
|
+
constructor(staticDir, options = {}) {
|
|
14
|
+
this.staticDir = staticDir;
|
|
15
|
+
this.watchedFiles = new Map();
|
|
16
|
+
this.hotReloadClients = [];
|
|
17
|
+
this.debounceTimers = new Map();
|
|
18
|
+
this.debounceDelay = options.debounceDelay || 300;
|
|
19
|
+
this.enabled = options.enabled !== false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Start watching files for changes
|
|
24
|
+
*/
|
|
25
|
+
start() {
|
|
26
|
+
if (!this.enabled) return;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
this.watchDirectory(this.staticDir);
|
|
30
|
+
console.log('[HotReload] Watching for changes:', this.staticDir);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error('[HotReload] Failed to start:', e.message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Watch directory recursively
|
|
38
|
+
*/
|
|
39
|
+
watchDirectory(dir) {
|
|
40
|
+
try {
|
|
41
|
+
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
42
|
+
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
const fullPath = path.join(dir, file.name);
|
|
45
|
+
|
|
46
|
+
if (file.isDirectory()) {
|
|
47
|
+
this.watchDirectory(fullPath);
|
|
48
|
+
} else {
|
|
49
|
+
this.watchFile(fullPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error('[HotReload] Error watching directory:', e.message);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Watch individual file for changes
|
|
59
|
+
*/
|
|
60
|
+
watchFile(filePath) {
|
|
61
|
+
if (this.watchedFiles.has(filePath)) return;
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
fs.watchFile(filePath, { interval: 100 }, (curr, prev) => {
|
|
65
|
+
if (curr.mtime > prev.mtime) {
|
|
66
|
+
this.onFileChanged(filePath);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.watchedFiles.set(filePath, true);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.error('[HotReload] Error watching file:', e.message);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Handle file change with debouncing
|
|
78
|
+
*/
|
|
79
|
+
onFileChanged(filePath) {
|
|
80
|
+
// Clear existing timer for this file
|
|
81
|
+
if (this.debounceTimers.has(filePath)) {
|
|
82
|
+
clearTimeout(this.debounceTimers.get(filePath));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Set new debounced timer
|
|
86
|
+
const timer = setTimeout(() => {
|
|
87
|
+
this.debounceTimers.delete(filePath);
|
|
88
|
+
const relPath = path.relative(this.staticDir, filePath);
|
|
89
|
+
|
|
90
|
+
console.log(`[HotReload] File changed: ${relPath}`);
|
|
91
|
+
this.broadcastReload();
|
|
92
|
+
}, this.debounceDelay);
|
|
93
|
+
|
|
94
|
+
this.debounceTimers.set(filePath, timer);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Register a WebSocket client for hot reload
|
|
99
|
+
*/
|
|
100
|
+
registerClient(ws) {
|
|
101
|
+
if (!this.enabled) return;
|
|
102
|
+
this.hotReloadClients.push(ws);
|
|
103
|
+
|
|
104
|
+
ws.on('close', () => {
|
|
105
|
+
const idx = this.hotReloadClients.indexOf(ws);
|
|
106
|
+
if (idx > -1) this.hotReloadClients.splice(idx, 1);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Broadcast reload signal to all connected clients
|
|
112
|
+
*/
|
|
113
|
+
broadcastReload() {
|
|
114
|
+
const message = JSON.stringify({ type: 'reload', timestamp: Date.now() });
|
|
115
|
+
|
|
116
|
+
for (const ws of this.hotReloadClients) {
|
|
117
|
+
if (ws.readyState === 1) { // WebSocket.OPEN
|
|
118
|
+
try {
|
|
119
|
+
ws.send(message);
|
|
120
|
+
} catch (e) {
|
|
121
|
+
// Client may have disconnected
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Cleanup watchers
|
|
129
|
+
*/
|
|
130
|
+
stop() {
|
|
131
|
+
for (const filePath of this.watchedFiles.keys()) {
|
|
132
|
+
try {
|
|
133
|
+
fs.unwatchFile(filePath);
|
|
134
|
+
} catch (e) {}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const timer of this.debounceTimers.values()) {
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.watchedFiles.clear();
|
|
142
|
+
this.debounceTimers.clear();
|
|
143
|
+
this.hotReloadClients = [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get HTML snippet to inject for hot reload
|
|
148
|
+
*/
|
|
149
|
+
getClientScript(baseUrl = '') {
|
|
150
|
+
if (!this.enabled) return '';
|
|
151
|
+
|
|
152
|
+
return `
|
|
153
|
+
<script>
|
|
154
|
+
(function() {
|
|
155
|
+
const baseUrl = '${baseUrl}';
|
|
156
|
+
const ws = new WebSocket((location.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + location.host + baseUrl + '/hot-reload');
|
|
157
|
+
|
|
158
|
+
ws.onmessage = function(event) {
|
|
159
|
+
try {
|
|
160
|
+
const data = JSON.parse(event.data);
|
|
161
|
+
if (data.type === 'reload') {
|
|
162
|
+
console.log('[HotReload] Reloading page...');
|
|
163
|
+
location.reload();
|
|
164
|
+
}
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.error('[HotReload] Error parsing message:', e);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
ws.onerror = function(e) {
|
|
171
|
+
console.log('[HotReload] WebSocket error:', e);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
ws.onclose = function() {
|
|
175
|
+
console.log('[HotReload] Connection closed, will attempt to reconnect...');
|
|
176
|
+
setTimeout(function() {
|
|
177
|
+
location.reload();
|
|
178
|
+
}, 2000);
|
|
179
|
+
};
|
|
180
|
+
})();
|
|
181
|
+
</script>
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default HotReloadManager;
|
package/package.json
CHANGED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response formatter for Claude Code outputs
|
|
3
|
+
* Handles segmentation of text, code blocks, tool calls, thinking blocks, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class ResponseFormatter {
|
|
7
|
+
/**
|
|
8
|
+
* Parse Claude Code response into structured segments
|
|
9
|
+
*/
|
|
10
|
+
static parseResponse(text) {
|
|
11
|
+
if (!text || typeof text !== 'string') return [];
|
|
12
|
+
|
|
13
|
+
const segments = [];
|
|
14
|
+
const lines = text.split('\n');
|
|
15
|
+
let current = null;
|
|
16
|
+
let codeBlockLang = null;
|
|
17
|
+
let inCodeBlock = false;
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < lines.length; i++) {
|
|
20
|
+
const line = lines[i];
|
|
21
|
+
|
|
22
|
+
// Check for code block markers
|
|
23
|
+
const codeBlockMatch = line.match(/^```(\w+)?$/);
|
|
24
|
+
if (codeBlockMatch) {
|
|
25
|
+
if (!inCodeBlock) {
|
|
26
|
+
// Starting a code block
|
|
27
|
+
if (current && current.type === 'text' && current.content.trim()) {
|
|
28
|
+
segments.push(current);
|
|
29
|
+
current = null;
|
|
30
|
+
}
|
|
31
|
+
inCodeBlock = true;
|
|
32
|
+
codeBlockLang = codeBlockMatch[1] || 'text';
|
|
33
|
+
current = { type: 'code', language: codeBlockLang, content: '' };
|
|
34
|
+
} else {
|
|
35
|
+
// Ending a code block
|
|
36
|
+
if (current) {
|
|
37
|
+
segments.push(current);
|
|
38
|
+
current = null;
|
|
39
|
+
}
|
|
40
|
+
inCodeBlock = false;
|
|
41
|
+
codeBlockLang = null;
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (inCodeBlock) {
|
|
47
|
+
current.content += (current.content ? '\n' : '') + line;
|
|
48
|
+
} else {
|
|
49
|
+
// Check for markdown formatting
|
|
50
|
+
if (line.match(/^#+\s/)) {
|
|
51
|
+
// Heading
|
|
52
|
+
if (current && current.type === 'text' && current.content.trim()) {
|
|
53
|
+
segments.push(current);
|
|
54
|
+
}
|
|
55
|
+
segments.push({ type: 'heading', level: line.match(/^#+/)[0].length, content: line.replace(/^#+\s/, '') });
|
|
56
|
+
current = null;
|
|
57
|
+
} else if (line.match(/^>\s/) || line.match(/^-\s/) || line.match(/^\d+\.\s/)) {
|
|
58
|
+
// Quote, bullet, or numbered list
|
|
59
|
+
if (current && current.type === 'text') {
|
|
60
|
+
segments.push(current);
|
|
61
|
+
}
|
|
62
|
+
if (line.match(/^>\s/)) {
|
|
63
|
+
segments.push({ type: 'blockquote', content: line.replace(/^>\s/, '') });
|
|
64
|
+
} else {
|
|
65
|
+
segments.push({ type: 'list_item', content: line.replace(/^[-\d+.]\s+/, '') });
|
|
66
|
+
}
|
|
67
|
+
current = null;
|
|
68
|
+
} else if (line.trim()) {
|
|
69
|
+
// Regular text
|
|
70
|
+
if (!current || current.type !== 'text') {
|
|
71
|
+
if (current) segments.push(current);
|
|
72
|
+
current = { type: 'text', content: line };
|
|
73
|
+
} else {
|
|
74
|
+
current.content += '\n' + line;
|
|
75
|
+
}
|
|
76
|
+
} else if (current && current.type === 'text' && current.content.trim()) {
|
|
77
|
+
// Empty line - could indicate paragraph break
|
|
78
|
+
current.content += '\n\n';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (current) {
|
|
84
|
+
segments.push(current);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return segments;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Extract tool calls, thinking blocks, and task information
|
|
92
|
+
*/
|
|
93
|
+
static extractMetadata(text) {
|
|
94
|
+
if (!text || typeof text !== 'string') return { tools: [], thinking: [], tasks: [] };
|
|
95
|
+
|
|
96
|
+
const metadata = {
|
|
97
|
+
tools: [],
|
|
98
|
+
thinking: [],
|
|
99
|
+
tasks: [],
|
|
100
|
+
subagents: []
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Find tool call patterns
|
|
104
|
+
const toolPattern = /(?:^|\n)\s*(?:Using|Calling|Invoking|Running)\s+(?:the\s+)?(\w+(?:\s+\w+)*?)(?:\s+(?:tool|command|function))?\s*(?:with|to)?\s*(.+?)(?:\n|$)/gi;
|
|
105
|
+
let match;
|
|
106
|
+
while ((match = toolPattern.exec(text)) !== null) {
|
|
107
|
+
metadata.tools.push({
|
|
108
|
+
name: match[1].trim(),
|
|
109
|
+
description: match[2]?.trim() || ''
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Find thinking/reasoning blocks
|
|
114
|
+
const thinkingPattern = /(?:thinking|reasoning|analyzing|considering)[\s:]+(.+?)(?:\n\n|$)/gi;
|
|
115
|
+
while ((match = thinkingPattern.exec(text)) !== null) {
|
|
116
|
+
metadata.thinking.push(match[1].trim());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Find task references
|
|
120
|
+
const taskPattern = /(?:task|step|doing)[\s:]+(.+?)(?:\n|$)/gi;
|
|
121
|
+
while ((match = taskPattern.exec(text)) !== null) {
|
|
122
|
+
metadata.tasks.push(match[1].trim());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Find subagent references
|
|
126
|
+
const subagentPattern = /(?:using|with|via)\s+(?:the\s+)?(\w+)\s+(?:subagent|agent)/gi;
|
|
127
|
+
while ((match = subagentPattern.exec(text)) !== null) {
|
|
128
|
+
metadata.subagents.push(match[1].trim());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return metadata;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Segment a response into logical parts
|
|
136
|
+
* Splits on natural boundaries like tool calls, thinking, etc.
|
|
137
|
+
*/
|
|
138
|
+
static segmentResponse(text) {
|
|
139
|
+
if (!text || typeof text !== 'string') return [];
|
|
140
|
+
|
|
141
|
+
const segments = [];
|
|
142
|
+
const parts = [];
|
|
143
|
+
|
|
144
|
+
// Split by major transitions
|
|
145
|
+
const transitions = [
|
|
146
|
+
{ pattern: /I'll\s+use\s+the\s+\w+\s+(?:subagent|tool|command)/i, type: 'tool_transition' },
|
|
147
|
+
{ pattern: /Let\s+me\s+(?:use|run|execute|call)\s+/i, type: 'action_start' },
|
|
148
|
+
{ pattern: /Here['s]*\s+(?:what|the|a)\s+(?:happened|result|output)/i, type: 'result' },
|
|
149
|
+
{ pattern: /^(✓|✅|✗|❌|-|•)\s+/m, type: 'bullet' }
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
let currentPart = '';
|
|
153
|
+
const lines = text.split('\n');
|
|
154
|
+
|
|
155
|
+
for (const line of lines) {
|
|
156
|
+
let isTransition = false;
|
|
157
|
+
|
|
158
|
+
for (const { pattern, type } of transitions) {
|
|
159
|
+
if (pattern.test(line)) {
|
|
160
|
+
if (currentPart.trim()) {
|
|
161
|
+
parts.push({ text: currentPart.trim(), type: 'text' });
|
|
162
|
+
currentPart = '';
|
|
163
|
+
}
|
|
164
|
+
isTransition = true;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (isTransition || line.match(/^#+\s/) || line.match(/^```/)) {
|
|
170
|
+
if (currentPart.trim()) {
|
|
171
|
+
parts.push({ text: currentPart.trim(), type: 'text' });
|
|
172
|
+
currentPart = '';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
currentPart += (currentPart ? '\n' : '') + line;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (currentPart.trim()) {
|
|
180
|
+
parts.push({ text: currentPart.trim(), type: 'text' });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Further parse each part
|
|
184
|
+
for (const part of parts) {
|
|
185
|
+
segments.push({
|
|
186
|
+
...part,
|
|
187
|
+
parsed: this.parseResponse(part.text),
|
|
188
|
+
metadata: this.extractMetadata(part.text)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return segments;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Format segments for display with proper HTML
|
|
197
|
+
*/
|
|
198
|
+
static formatForDisplay(segments) {
|
|
199
|
+
if (!Array.isArray(segments)) return '';
|
|
200
|
+
|
|
201
|
+
const html = [];
|
|
202
|
+
|
|
203
|
+
for (const segment of segments) {
|
|
204
|
+
if (segment.type === 'code') {
|
|
205
|
+
html.push(`<pre class="code-block language-${segment.language}"><code>${this.escapeHtml(segment.content)}</code></pre>`);
|
|
206
|
+
} else if (segment.type === 'heading') {
|
|
207
|
+
const tag = `h${Math.min(segment.level, 6)}`;
|
|
208
|
+
html.push(`<${tag} class="response-heading">${this.escapeHtml(segment.content)}</${tag}>`);
|
|
209
|
+
} else if (segment.type === 'blockquote') {
|
|
210
|
+
html.push(`<blockquote class="response-quote">${this.escapeHtml(segment.content)}</blockquote>`);
|
|
211
|
+
} else if (segment.type === 'list_item') {
|
|
212
|
+
html.push(`<li class="response-list-item">${this.escapeHtml(segment.content)}</li>`);
|
|
213
|
+
} else if (segment.type === 'text') {
|
|
214
|
+
html.push(`<p class="response-text">${this.escapeHtml(segment.content).replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\*(.*?)\*/g, '<em>$1</em>')}</p>`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return html.join('\n');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static escapeHtml(text) {
|
|
222
|
+
if (typeof text !== 'string') return '';
|
|
223
|
+
return text
|
|
224
|
+
.replace(/&/g, '&')
|
|
225
|
+
.replace(/</g, '<')
|
|
226
|
+
.replace(/>/g, '>')
|
|
227
|
+
.replace(/"/g, '"')
|
|
228
|
+
.replace(/'/g, ''');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Create rich metadata display
|
|
233
|
+
*/
|
|
234
|
+
static createMetadataDisplay(metadata) {
|
|
235
|
+
if (!metadata || Object.keys(metadata).every(k => !metadata[k] || metadata[k].length === 0)) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const html = ['<div class="response-metadata">'];
|
|
240
|
+
|
|
241
|
+
if (metadata.tools?.length) {
|
|
242
|
+
html.push('<div class="metadata-section tools">');
|
|
243
|
+
html.push('<strong>Tools Used:</strong>');
|
|
244
|
+
html.push('<ul>');
|
|
245
|
+
for (const tool of metadata.tools) {
|
|
246
|
+
html.push(`<li><code>${this.escapeHtml(tool.name)}</code>${tool.description ? ': ' + this.escapeHtml(tool.description) : ''}</li>`);
|
|
247
|
+
}
|
|
248
|
+
html.push('</ul></div>');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (metadata.thinking?.length) {
|
|
252
|
+
html.push('<details class="metadata-section thinking">');
|
|
253
|
+
html.push('<summary>Reasoning</summary>');
|
|
254
|
+
for (const thought of metadata.thinking) {
|
|
255
|
+
html.push(`<p>${this.escapeHtml(thought)}</p>`);
|
|
256
|
+
}
|
|
257
|
+
html.push('</details>');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (metadata.subagents?.length) {
|
|
261
|
+
html.push('<div class="metadata-section subagents">');
|
|
262
|
+
html.push('<strong>Subagents:</strong>');
|
|
263
|
+
html.push('<ul>');
|
|
264
|
+
for (const agent of metadata.subagents) {
|
|
265
|
+
html.push(`<li>${this.escapeHtml(agent)}</li>`);
|
|
266
|
+
}
|
|
267
|
+
html.push('</ul></div>');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (metadata.tasks?.length) {
|
|
271
|
+
html.push('<div class="metadata-section tasks">');
|
|
272
|
+
html.push('<strong>Tasks:</strong>');
|
|
273
|
+
html.push('<ul>');
|
|
274
|
+
for (const task of metadata.tasks) {
|
|
275
|
+
html.push(`<li>${this.escapeHtml(task)}</li>`);
|
|
276
|
+
}
|
|
277
|
+
html.push('</ul></div>');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
html.push('</div>');
|
|
281
|
+
return html.join('\n');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export default ResponseFormatter;
|
package/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import os from 'os';
|
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
import { queries } from './database.js';
|
|
9
9
|
import ACPConnection from './acp-launcher.js';
|
|
10
|
+
import { ResponseFormatter } from './response-formatter.js';
|
|
10
11
|
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
const PORT = process.env.PORT || 3000;
|
|
@@ -31,7 +32,9 @@ async function getACP(agentId, cwd) {
|
|
|
31
32
|
await conn.initialize();
|
|
32
33
|
await conn.newSession(cwd);
|
|
33
34
|
await conn.setSessionMode('bypassPermissions');
|
|
34
|
-
|
|
35
|
+
// Inject system prompt to ensure HTML/RippleUI formatting
|
|
36
|
+
await conn.injectSkills();
|
|
37
|
+
await conn.injectSystemContext();
|
|
35
38
|
acpPool.set(agentId, conn);
|
|
36
39
|
console.log(`ACP connection ready for ${agentId} in ${cwd}`);
|
|
37
40
|
return conn;
|
|
@@ -313,17 +316,21 @@ async function processMessage(conversationId, messageId, sessionId, content, age
|
|
|
313
316
|
|
|
314
317
|
let fullText = '';
|
|
315
318
|
const blocks = [];
|
|
319
|
+
const updateChunks = []; // Track all message chunks in order
|
|
316
320
|
conn.onUpdate = (params) => {
|
|
317
321
|
const u = params.update;
|
|
318
322
|
if (!u) return;
|
|
319
323
|
const kind = u.sessionUpdate;
|
|
320
324
|
if (kind === 'agent_message_chunk' && u.content?.text) {
|
|
321
325
|
fullText += u.content.text;
|
|
326
|
+
updateChunks.push({ type: 'text', content: u.content.text, timestamp: Date.now() });
|
|
322
327
|
} else if (kind === 'html_content' && u.content?.html) {
|
|
323
328
|
blocks.push({ type: 'html', html: u.content.html, title: u.content.title, id: u.content.id });
|
|
329
|
+
updateChunks.push({ type: 'html', content: u.content.html, title: u.content.title, timestamp: Date.now() });
|
|
324
330
|
} else if (kind === 'image_content' && u.content?.path) {
|
|
325
331
|
const imageUrl = BASE_URL + '/api/image/' + encodeURIComponent(u.content.path);
|
|
326
332
|
blocks.push({ type: 'image', path: u.content.path, url: imageUrl, title: u.content.title, alt: u.content.alt });
|
|
333
|
+
updateChunks.push({ type: 'image', path: u.content.path, url: imageUrl, title: u.content.title, timestamp: Date.now() });
|
|
327
334
|
}
|
|
328
335
|
};
|
|
329
336
|
|
|
@@ -331,7 +338,23 @@ async function processMessage(conversationId, messageId, sessionId, content, age
|
|
|
331
338
|
conn.onUpdate = null;
|
|
332
339
|
|
|
333
340
|
const responseText = fullText || result?.result || (result?.stopReason ? `Completed: ${result.stopReason}` : 'No response.');
|
|
334
|
-
|
|
341
|
+
|
|
342
|
+
// Segment and format the response for better display
|
|
343
|
+
const segments = ResponseFormatter.segmentResponse(responseText);
|
|
344
|
+
const metadata = ResponseFormatter.extractMetadata(responseText);
|
|
345
|
+
|
|
346
|
+
const messageContent = blocks.length > 0 ? {
|
|
347
|
+
text: responseText,
|
|
348
|
+
blocks,
|
|
349
|
+
segments,
|
|
350
|
+
metadata,
|
|
351
|
+
updateChunks
|
|
352
|
+
} : {
|
|
353
|
+
text: responseText,
|
|
354
|
+
segments,
|
|
355
|
+
metadata,
|
|
356
|
+
updateChunks
|
|
357
|
+
};
|
|
335
358
|
|
|
336
359
|
const assistantMessage = queries.createMessage(conversationId, 'assistant', messageContent);
|
|
337
360
|
queries.updateSession(sessionId, { status: 'completed', response: { text: responseText, messageId: assistantMessage.id }, completed_at: Date.now() });
|
package/static/app.js
CHANGED
|
@@ -525,10 +525,15 @@ class GMGUIApp {
|
|
|
525
525
|
|
|
526
526
|
looksLikeHtml(text) {
|
|
527
527
|
const trimmed = text.trim();
|
|
528
|
+
// Check for HTML tags at the start
|
|
528
529
|
if (/^<[a-z][\s\S]*>/i.test(trimmed)) return true;
|
|
529
|
-
|
|
530
|
+
// Check for closing tags of common HTML elements
|
|
531
|
+
if (/<\/(div|span|p|table|ul|ol|h[1-6]|section|article|header|footer|nav|main|aside|details|summary|figure|figcaption|blockquote|pre|code|a|strong|em|img|br|hr|button|input|form|label)>/i.test(trimmed)) return true;
|
|
532
|
+
// Check for Tailwind/RippleUI classes (strong indicator of HTML)
|
|
533
|
+
if (/class\s*=\s*["'][^"']*(?:card|alert|badge|btn|table|space-y|p-\d+|text-|bg-|rounded|shadow)/.test(trimmed)) return true;
|
|
534
|
+
// Count HTML tags
|
|
530
535
|
const tagCount = (trimmed.match(/<[a-z][^>]*>/gi) || []).length;
|
|
531
|
-
if (tagCount >=
|
|
536
|
+
if (tagCount >= 2) return true; // Lower threshold for HTML detection
|
|
532
537
|
return false;
|
|
533
538
|
}
|
|
534
539
|
|
|
@@ -599,7 +604,13 @@ class GMGUIApp {
|
|
|
599
604
|
el.appendChild(bubble);
|
|
600
605
|
}
|
|
601
606
|
} else if (typeof msg.content === 'object' && msg.content !== null) {
|
|
602
|
-
|
|
607
|
+
// Display segmented content if available
|
|
608
|
+
if (msg.content.segments && Array.isArray(msg.content.segments)) {
|
|
609
|
+
msg.content.segments.forEach(segment => {
|
|
610
|
+
el.appendChild(this.renderSegment(segment));
|
|
611
|
+
});
|
|
612
|
+
} else if (msg.content.text) {
|
|
613
|
+
// Fallback to regular text rendering
|
|
603
614
|
const parsed = this.parseAndRenderContent(msg.content.text);
|
|
604
615
|
if (parsed) {
|
|
605
616
|
parsed.forEach(elem => el.appendChild(elem));
|
|
@@ -610,6 +621,8 @@ class GMGUIApp {
|
|
|
610
621
|
el.appendChild(bubble);
|
|
611
622
|
}
|
|
612
623
|
}
|
|
624
|
+
|
|
625
|
+
// Display blocks if available
|
|
613
626
|
if (msg.content.blocks && Array.isArray(msg.content.blocks)) {
|
|
614
627
|
msg.content.blocks.forEach(block => {
|
|
615
628
|
if (block.type === 'html') {
|
|
@@ -621,6 +634,12 @@ class GMGUIApp {
|
|
|
621
634
|
}
|
|
622
635
|
});
|
|
623
636
|
}
|
|
637
|
+
|
|
638
|
+
// Display metadata if available
|
|
639
|
+
if (msg.content.metadata) {
|
|
640
|
+
const metadataEl = this.renderMetadata(msg.content.metadata);
|
|
641
|
+
if (metadataEl) el.appendChild(metadataEl);
|
|
642
|
+
}
|
|
624
643
|
} else {
|
|
625
644
|
const bubble = document.createElement('div');
|
|
626
645
|
bubble.className = 'message-bubble';
|
|
@@ -631,6 +650,127 @@ class GMGUIApp {
|
|
|
631
650
|
div.appendChild(el);
|
|
632
651
|
}
|
|
633
652
|
|
|
653
|
+
renderSegment(segment) {
|
|
654
|
+
const el = document.createElement('div');
|
|
655
|
+
el.className = `segment segment-${segment.type}`;
|
|
656
|
+
|
|
657
|
+
if (segment.type === 'code') {
|
|
658
|
+
const pre = document.createElement('pre');
|
|
659
|
+
pre.className = `code-block language-${segment.language || 'text'}`;
|
|
660
|
+
const code = document.createElement('code');
|
|
661
|
+
code.textContent = segment.content;
|
|
662
|
+
pre.appendChild(code);
|
|
663
|
+
el.appendChild(pre);
|
|
664
|
+
} else if (segment.type === 'heading') {
|
|
665
|
+
const tag = `h${Math.min(segment.level, 6)}`;
|
|
666
|
+
const heading = document.createElement(tag);
|
|
667
|
+
heading.className = 'response-heading';
|
|
668
|
+
heading.textContent = segment.content;
|
|
669
|
+
el.appendChild(heading);
|
|
670
|
+
} else if (segment.type === 'blockquote') {
|
|
671
|
+
const quote = document.createElement('blockquote');
|
|
672
|
+
quote.className = 'response-quote';
|
|
673
|
+
quote.textContent = segment.content;
|
|
674
|
+
el.appendChild(quote);
|
|
675
|
+
} else if (segment.type === 'list_item') {
|
|
676
|
+
const li = document.createElement('li');
|
|
677
|
+
li.className = 'response-list-item';
|
|
678
|
+
li.textContent = segment.content;
|
|
679
|
+
el.appendChild(li);
|
|
680
|
+
} else if (segment.type === 'text') {
|
|
681
|
+
const p = document.createElement('p');
|
|
682
|
+
p.className = 'response-text';
|
|
683
|
+
p.innerHTML = segment.content
|
|
684
|
+
.replace(/&/g, '&')
|
|
685
|
+
.replace(/</g, '<')
|
|
686
|
+
.replace(/>/g, '>')
|
|
687
|
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
688
|
+
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
689
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
690
|
+
el.appendChild(p);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return el;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
renderMetadata(metadata) {
|
|
697
|
+
if (!metadata || Object.keys(metadata).every(k => !metadata[k] || metadata[k].length === 0)) {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const container = document.createElement('div');
|
|
702
|
+
container.className = 'response-metadata';
|
|
703
|
+
|
|
704
|
+
if (metadata.tools && metadata.tools.length > 0) {
|
|
705
|
+
const section = document.createElement('div');
|
|
706
|
+
section.className = 'metadata-section tools';
|
|
707
|
+
const title = document.createElement('strong');
|
|
708
|
+
title.textContent = 'Tools Used:';
|
|
709
|
+
section.appendChild(title);
|
|
710
|
+
const ul = document.createElement('ul');
|
|
711
|
+
metadata.tools.forEach(tool => {
|
|
712
|
+
const li = document.createElement('li');
|
|
713
|
+
const code = document.createElement('code');
|
|
714
|
+
code.textContent = tool.name;
|
|
715
|
+
li.appendChild(code);
|
|
716
|
+
if (tool.description) {
|
|
717
|
+
li.appendChild(document.createTextNode(`: ${tool.description}`));
|
|
718
|
+
}
|
|
719
|
+
ul.appendChild(li);
|
|
720
|
+
});
|
|
721
|
+
section.appendChild(ul);
|
|
722
|
+
container.appendChild(section);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (metadata.thinking && metadata.thinking.length > 0) {
|
|
726
|
+
const section = document.createElement('details');
|
|
727
|
+
section.className = 'metadata-section thinking';
|
|
728
|
+
const summary = document.createElement('summary');
|
|
729
|
+
summary.textContent = 'Reasoning';
|
|
730
|
+
section.appendChild(summary);
|
|
731
|
+
metadata.thinking.forEach(thought => {
|
|
732
|
+
const p = document.createElement('p');
|
|
733
|
+
p.textContent = thought;
|
|
734
|
+
section.appendChild(p);
|
|
735
|
+
});
|
|
736
|
+
container.appendChild(section);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (metadata.subagents && metadata.subagents.length > 0) {
|
|
740
|
+
const section = document.createElement('div');
|
|
741
|
+
section.className = 'metadata-section subagents';
|
|
742
|
+
const title = document.createElement('strong');
|
|
743
|
+
title.textContent = 'Subagents:';
|
|
744
|
+
section.appendChild(title);
|
|
745
|
+
const ul = document.createElement('ul');
|
|
746
|
+
metadata.subagents.forEach(agent => {
|
|
747
|
+
const li = document.createElement('li');
|
|
748
|
+
li.textContent = agent;
|
|
749
|
+
ul.appendChild(li);
|
|
750
|
+
});
|
|
751
|
+
section.appendChild(ul);
|
|
752
|
+
container.appendChild(section);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (metadata.tasks && metadata.tasks.length > 0) {
|
|
756
|
+
const section = document.createElement('div');
|
|
757
|
+
section.className = 'metadata-section tasks';
|
|
758
|
+
const title = document.createElement('strong');
|
|
759
|
+
title.textContent = 'Tasks:';
|
|
760
|
+
section.appendChild(title);
|
|
761
|
+
const ul = document.createElement('ul');
|
|
762
|
+
metadata.tasks.forEach(task => {
|
|
763
|
+
const li = document.createElement('li');
|
|
764
|
+
li.textContent = task;
|
|
765
|
+
ul.appendChild(li);
|
|
766
|
+
});
|
|
767
|
+
section.appendChild(ul);
|
|
768
|
+
container.appendChild(section);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return container;
|
|
772
|
+
}
|
|
773
|
+
|
|
634
774
|
async startNewChat(folderPath) {
|
|
635
775
|
if (!this.selectedAgent) {
|
|
636
776
|
const firstAgent = Array.from(this.agents.keys())[0];
|
package/static/styles.css
CHANGED
|
@@ -1418,3 +1418,163 @@ html, body {
|
|
|
1418
1418
|
overflow: visible;
|
|
1419
1419
|
}
|
|
1420
1420
|
}
|
|
1421
|
+
|
|
1422
|
+
/* Rich Response Rendering */
|
|
1423
|
+
|
|
1424
|
+
/* Code blocks */
|
|
1425
|
+
.segment-code {
|
|
1426
|
+
margin: 1rem 0;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
.code-block {
|
|
1430
|
+
background: #f5f5f5;
|
|
1431
|
+
border-left: 4px solid #007acc;
|
|
1432
|
+
padding: 1rem;
|
|
1433
|
+
border-radius: 4px;
|
|
1434
|
+
overflow-x: auto;
|
|
1435
|
+
font-family: 'Courier New', monospace;
|
|
1436
|
+
font-size: 0.9rem;
|
|
1437
|
+
line-height: 1.5;
|
|
1438
|
+
color: #333;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
.code-block code {
|
|
1442
|
+
color: inherit;
|
|
1443
|
+
background: none;
|
|
1444
|
+
padding: 0;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/* Inline code */
|
|
1448
|
+
p code {
|
|
1449
|
+
background: #f0f0f0;
|
|
1450
|
+
padding: 0.2em 0.4em;
|
|
1451
|
+
border-radius: 3px;
|
|
1452
|
+
font-family: 'Courier New', monospace;
|
|
1453
|
+
color: #d73a49;
|
|
1454
|
+
font-size: 0.9em;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/* Headings */
|
|
1458
|
+
.response-heading {
|
|
1459
|
+
font-weight: 600;
|
|
1460
|
+
margin: 1rem 0 0.5rem 0;
|
|
1461
|
+
line-height: 1.3;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
.segment-heading {
|
|
1465
|
+
margin-top: 1.5rem;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
.segment-heading:first-child {
|
|
1469
|
+
margin-top: 0;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
/* Blockquotes */
|
|
1473
|
+
.response-quote {
|
|
1474
|
+
border-left: 4px solid #ccc;
|
|
1475
|
+
padding-left: 1rem;
|
|
1476
|
+
color: #666;
|
|
1477
|
+
font-style: italic;
|
|
1478
|
+
margin: 0.5rem 0;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
/* Lists */
|
|
1482
|
+
.response-list-item {
|
|
1483
|
+
margin-left: 2rem;
|
|
1484
|
+
margin-bottom: 0.25rem;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
/* Text segments */
|
|
1488
|
+
.segment-text {
|
|
1489
|
+
margin: 0.5rem 0;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
.response-text {
|
|
1493
|
+
margin: 0.5rem 0;
|
|
1494
|
+
line-height: 1.6;
|
|
1495
|
+
color: #333;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
/* Metadata display */
|
|
1499
|
+
.response-metadata {
|
|
1500
|
+
background: #f9f9f9;
|
|
1501
|
+
border: 1px solid #e0e0e0;
|
|
1502
|
+
border-radius: 8px;
|
|
1503
|
+
padding: 1rem;
|
|
1504
|
+
margin: 1rem 0 0 0;
|
|
1505
|
+
font-size: 0.9rem;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
.metadata-section {
|
|
1509
|
+
margin-bottom: 1rem;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
.metadata-section:last-child {
|
|
1513
|
+
margin-bottom: 0;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
.metadata-section strong {
|
|
1517
|
+
display: block;
|
|
1518
|
+
margin-bottom: 0.5rem;
|
|
1519
|
+
color: #555;
|
|
1520
|
+
font-weight: 600;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
.metadata-section ul {
|
|
1524
|
+
list-style: none;
|
|
1525
|
+
padding-left: 1rem;
|
|
1526
|
+
margin: 0;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
.metadata-section li {
|
|
1530
|
+
margin-bottom: 0.25rem;
|
|
1531
|
+
padding-left: 0.5rem;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
.metadata-section li::before {
|
|
1535
|
+
content: '▸ ';
|
|
1536
|
+
color: #007acc;
|
|
1537
|
+
font-weight: bold;
|
|
1538
|
+
margin-right: 0.5rem;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
.metadata-section.tools li code {
|
|
1542
|
+
background: #e8f4f8;
|
|
1543
|
+
color: #0071bc;
|
|
1544
|
+
padding: 0.2em 0.4em;
|
|
1545
|
+
border-radius: 3px;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
.metadata-section.thinking {
|
|
1549
|
+
background: #fff9e6;
|
|
1550
|
+
border: 1px solid #f5e6cc;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
.metadata-section.thinking summary {
|
|
1554
|
+
cursor: pointer;
|
|
1555
|
+
font-weight: 600;
|
|
1556
|
+
color: #997700;
|
|
1557
|
+
user-select: none;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
.metadata-section.thinking summary:hover {
|
|
1561
|
+
color: #bb9900;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
.metadata-section.thinking p {
|
|
1565
|
+
margin: 0.5rem 0;
|
|
1566
|
+
padding-left: 1rem;
|
|
1567
|
+
border-left: 3px solid #f0c674;
|
|
1568
|
+
padding-left: 1rem;
|
|
1569
|
+
color: #666;
|
|
1570
|
+
font-size: 0.9rem;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
.metadata-section.subagents li::before {
|
|
1574
|
+
content: '🤖 ';
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
.metadata-section.tasks li::before {
|
|
1578
|
+
content: '✓ ';
|
|
1579
|
+
color: #28a745;
|
|
1580
|
+
}
|