agentgui 1.0.18 → 1.0.19

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.
@@ -0,0 +1,189 @@
1
+ # AgentGUI Implementation Status
2
+
3
+ ## ✅ Completed Features
4
+
5
+ ### 1. OAuth Authentication
6
+ - ✅ Binary discovery for `claude-code-acp`
7
+ - ✅ Automatic PATH management
8
+ - ✅ Timeout optimization for ACP bridge
9
+ - ✅ Uses local Claude Code credentials (no API key needed)
10
+
11
+ ### 2. Response Formatting Infrastructure
12
+ - ✅ ResponseFormatter module for parsing responses
13
+ - ✅ Segment detection (code, headings, text, lists)
14
+ - ✅ Metadata extraction (tools, thinking, tasks, subagents)
15
+ - ✅ Frontend rendering for segments and metadata
16
+
17
+ ### 3. HTML/RippleUI System
18
+ - ✅ Enhanced system prompt with detailed HTML instructions
19
+ - ✅ HTMLWrapper module for automatic HTML wrapping
20
+ - ✅ Markdown parsing to HTML conversion
21
+ - ✅ Tailwind CSS styling integration
22
+
23
+ ### 4. Frontend Improvements
24
+ - ✅ Enhanced HTML detection (tags + Tailwind classes)
25
+ - ✅ Rich CSS styling for code blocks, metadata, segments
26
+ - ✅ Responsive design for all components
27
+ - ✅ Print-friendly styles
28
+
29
+ ### 5. Infrastructure
30
+ - ✅ Hot reload preparation (HotReloadManager module)
31
+ - ✅ Git version control with comprehensive commit history
32
+ - ✅ Port configuration (3000 dev, 9897 production)
33
+ - ✅ Database persistence
34
+
35
+ ## 🔄 Partially Implemented
36
+
37
+ ### Hot Reload for Node Modules
38
+ - ⚠️ Static files auto-reload: YES (CSS, HTML, JS in browser)
39
+ - ⚠️ Node.js module changes: NO (requires server restart)
40
+ - **Workaround**: Changes to `.js` files in `/config/workspace/agentgui/` require manual server restart
41
+ - **Future**: Implement full ES module reloading
42
+
43
+ ## 📊 Current Architecture
44
+
45
+ ```
46
+ User → Browser (9897)
47
+
48
+ Server.js (Node.js)
49
+ ├→ ACP Pool (connects to claude-code-acp)
50
+ │ └→ OAuth via local credentials
51
+ ├→ HTMLWrapper (wraps responses in HTML)
52
+ ├→ ResponseFormatter (segments & metadata)
53
+ └→ Database (SQLite)
54
+ ```
55
+
56
+ ## 🎯 Current Limitations
57
+
58
+ 1. **System Prompt Not Fully Enforced**
59
+ - Claude Code's system prompt about HTML responses works partially
60
+ - Plain text responses are now auto-wrapped by HTMLWrapper
61
+ - Result: All responses display as HTML regardless of original format
62
+
63
+ 2. **Hot Module Reloading**
64
+ - Static files (CSS, HTML) reload automatically
65
+ - JavaScript/Node modules need manual restart
66
+ - Recommendation: Changes to server logic need restart
67
+
68
+ 3. **ACP Skill Injection**
69
+ - `session/skill_inject` not supported by current ACP version
70
+ - Falls back gracefully without error
71
+ - System prompt still injected via context
72
+
73
+ ## 📋 Next Steps
74
+
75
+ ### For Full HTML Response Enforcement
76
+ 1. ✅ Already Done: HTMLWrapper auto-converts plain text to HTML
77
+ 2. No further action needed - all responses now display as beautifully formatted HTML
78
+
79
+ ### For True Hot Module Reloading
80
+ 1. Implement dynamic `import()` for module reloading
81
+ 2. Add module-level cache busting
82
+ 3. Handle state preservation during reload
83
+
84
+ ### For Enhanced Display
85
+ 1. Add streaming responses (real-time message display)
86
+ 2. Add more sophisticated metadata visualization
87
+ 3. Add export/sharing functionality
88
+
89
+ ## 🧪 Testing
90
+
91
+ ### Test a Message
92
+ ```bash
93
+ CONV=$(curl -s -X POST http://localhost:9897/gm/api/conversations \
94
+ -H "Content-Type: application/json" \
95
+ -d '{"agentId": "claude-code", "title": "Test"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['conversation']['id'])")
96
+
97
+ curl -s -X POST "http://localhost:9897/gm/api/conversations/$CONV/messages" \
98
+ -H "Content-Type: application/json" \
99
+ -d '{"agentId": "claude-code", "content": "Your question here", "idempotencyKey": "test-1"}'
100
+
101
+ # Check response after ~30-50 seconds
102
+ curl -s "http://localhost:9897/gm/api/conversations/$CONV/messages" | python3 -m json.tool
103
+ ```
104
+
105
+ ## 📝 Files Structure
106
+
107
+ ```
108
+ agentgui/
109
+ ├── server.js # Main HTTP server + WebSocket
110
+ ├── acp-launcher.js # ACP connection management + system prompt
111
+ ├── database.js # SQLite persistence
112
+ ├── response-formatter.js # Response parsing & segmentation
113
+ ├── html-wrapper.js # Markdown to HTML conversion
114
+ ├── hot-reload-manager.js # Hot reload infrastructure (prepared)
115
+ ├── static/
116
+ │ ├── app.js # Frontend logic
117
+ │ ├── index.html # UI template
118
+ │ ├── styles.css # Comprehensive styling
119
+ │ └── theme.js # Theme management
120
+ └── package.json # Dependencies
121
+ ```
122
+
123
+ ## 🚀 Running the Server
124
+
125
+ ```bash
126
+ # Development (port 3000)
127
+ npm start
128
+
129
+ # Production (port 9897)
130
+ PORT=9897 npm start
131
+
132
+ # With hot reload enabled (default)
133
+ PORT=9897 HOT_RELOAD=true npm start
134
+
135
+ # To disable hot reload
136
+ PORT=9897 HOT_RELOAD=false npm start
137
+ ```
138
+
139
+ ## 💡 Key Implementation Details
140
+
141
+ ### HTML Wrapping Flow
142
+ ```
143
+ Claude's plain text response
144
+
145
+ HTMLWrapper.wrapResponse()
146
+
147
+ Parse markdown syntax
148
+
149
+ Convert to HTML with Tailwind classes
150
+
151
+ Wrap in container div
152
+
153
+ Store as messageContent.text
154
+
155
+ Frontend detects HTML (starts with <div)
156
+
157
+ Renders with sanitization
158
+ ```
159
+
160
+ ### Response Structure
161
+ ```json
162
+ {
163
+ "id": "msg-xxx",
164
+ "role": "assistant",
165
+ "content": {
166
+ "text": "<div class=\"space-y-4 p-6\">...HTML...</div>",
167
+ "segments": [...],
168
+ "metadata": {...},
169
+ "updateChunks": [...],
170
+ "blocks": [],
171
+ "isHTML": true
172
+ }
173
+ }
174
+ ```
175
+
176
+ ## ✨ Results
177
+
178
+ - All responses now display as beautiful, styled HTML
179
+ - Code blocks are properly syntax-highlighted
180
+ - Metadata (tools, thinking, tasks) are rich and interactive
181
+ - System runs on port 9897 for production
182
+ - OAuth authentication works seamlessly
183
+ - Database persists conversations and history
184
+
185
+ ---
186
+
187
+ **Last Updated**: February 3, 2026
188
+ **Version**: 1.0.16+
189
+ **Status**: Production Ready (with auto-HTML wrapping)
@@ -0,0 +1,157 @@
1
+ # Response Display Issues & Analysis
2
+
3
+ ## Issue 1: Combined Responses Without Separation
4
+
5
+ Example from user feedback:
6
+ ```
7
+ "Let me start by reading the PRD file to understand what tasks need to be completed.This is a large PRD with many unchecked items across 6 phases. Let me explore the codebase to understand the current state before planning implementation."
8
+ ```
9
+
10
+ **Problem**: Two separate thoughts/steps are combined into one paragraph without proper separation:
11
+ - "Let me start by reading..." (statement of intent)
12
+ - "This is a large PRD..." (observation/analysis)
13
+
14
+ ### Root Cause Analysis
15
+
16
+ The ResponseFormatter is parsing continuous text as a single segment if it doesn't have explicit markdown formatting. When Claude sends thinking or analysis steps, they may be:
17
+ 1. Separated by newlines in the actual response
18
+ 2. Separated by periods/punctuation but no blank lines
19
+ 3. Represented as separate agent messages but concatenated
20
+
21
+ ### Current Handling
22
+
23
+ In `response-formatter.js`, the `parseResponse()` function treats consecutive text lines as one segment unless they have markdown markers (# ## etc).
24
+
25
+ ### Fix Needed
26
+
27
+ 1. **Detect step boundaries**: Recognize patterns like:
28
+ - "Let me..." → New action/step
29
+ - "I'll..." → New intent
30
+ - "Now..." → Transition
31
+ - "Here's..." → Result presentation
32
+ - "First..." / "Next..." → Sequential steps
33
+
34
+ 2. **Segment by semantic meaning**: Break text into logical paragraphs that represent:
35
+ - Planning/Analysis
36
+ - Investigation
37
+ - Results
38
+ - Explanations
39
+
40
+ 3. **Add visual separators**: Use cards or dividers between segments
41
+
42
+ ## Issue 2: Tags/JSON Not Rendering
43
+
44
+ Types of content that should render specially:
45
+ - `<thinking>` tags (Claude's reasoning)
46
+ - `<tool_use>` tags (Tool call indicators)
47
+ - `<result>` tags (Tool results)
48
+ - Metadata blocks
49
+ - Tool output
50
+
51
+ Example that should render:
52
+ ```
53
+ <thinking>
54
+ This problem requires analysis
55
+ </thinking>
56
+
57
+ <tool_use>
58
+ name: fs_access
59
+ </tool_use>
60
+ ```
61
+
62
+ ## Issue 3: Metadata-Rich Content
63
+
64
+ Elements that need special rendering:
65
+ - Tool names (should be in code styling)
66
+ - Function signatures (should be formatted as code)
67
+ - API responses (should be formatted as JSON blocks)
68
+ - Task lists (should be checkboxes or special formatting)
69
+ - Subagent notifications (should have special styling)
70
+
71
+ ## Solution Architecture
72
+
73
+ ### Enhanced ResponseFormatter
74
+
75
+ 1. **XML Tag Detection**
76
+ ```javascript
77
+ detectXMLTags(text) // Find <thinking>, <tool_use>, <result>, etc.
78
+ ```
79
+
80
+ 2. **Smart Segmentation**
81
+ ```javascript
82
+ segmentByIntent(text) // Break on "Let me", "I'll", "Now", etc.
83
+ ```
84
+
85
+ 3. **Special Element Handling**
86
+ ```javascript
87
+ renderToolCall(toolData)
88
+ renderThinking(thoughtText)
89
+ renderResult(resultData)
90
+ ```
91
+
92
+ ### Frontend Enhancement
93
+
94
+ 1. **New Segment Types**
95
+ - `thinking` → Collapsible gray box
96
+ - `tool_call` → Highlighted with tool name
97
+ - `tool_result` → Code/result styling
98
+ - `analysis` → Regular text with better spacing
99
+ - `action` → Action statement styling
100
+
101
+ 2. **CSS Classes for Each**
102
+ ```css
103
+ .segment-thinking { background: #f9f9f9; border-left: 4px solid #999; }
104
+ .segment-tool_call { background: #f0f8ff; border-left: 4px solid #007acc; }
105
+ .segment-tool_result { background: #fff9e6; border-left: 4px solid #ffb300; }
106
+ .segment-action { font-weight: 500; color: #333; margin-top: 1.5rem; }
107
+ ```
108
+
109
+ ## Implementation Priority
110
+
111
+ 1. **High Priority** (Breaking issues)
112
+ - Fix response combining (split on semantic boundaries)
113
+ - Render `<thinking>` blocks separately
114
+ - Proper code block formatting
115
+
116
+ 2. **Medium Priority** (Display quality)
117
+ - Tool call highlighting
118
+ - Tool result formatting
119
+ - Better metadata display
120
+
121
+ 3. **Low Priority** (Enhancement)
122
+ - Animated reveals for collapsible sections
123
+ - Copy-to-clipboard for code blocks
124
+ - Export formatting
125
+
126
+ ## Files to Modify
127
+
128
+ 1. `response-formatter.js`
129
+ - Add XML tag detection
130
+ - Add intent-based segmentation
131
+ - Add special element parsing
132
+
133
+ 2. `static/app.js`
134
+ - Add `renderThinkingSegment()`
135
+ - Add `renderToolCallSegment()`
136
+ - Add `renderActionSegment()`
137
+
138
+ 3. `static/styles.css`
139
+ - Add styling for new segment types
140
+ - Add visual hierarchy
141
+
142
+ ## Testing Strategy
143
+
144
+ Create test cases with responses like:
145
+ ```
146
+ Let me analyze this requirement.
147
+
148
+ Looking at the code structure, I see...
149
+
150
+ Now I'll implement the solution.
151
+ ```
152
+
153
+ Should produce:
154
+ - Segment 1: "Let me analyze..." (action/planning)
155
+ - Segment 2: "Looking at..." (analysis)
156
+ - Segment 3: "Now I'll..." (implementation step)
157
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -138,49 +138,17 @@ export class ResponseFormatter {
138
138
  static segmentResponse(text) {
139
139
  if (!text || typeof text !== 'string') return [];
140
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;
141
+ // First, extract XML tags
142
+ const xmlSegments = this.extractXMLTags(text);
143
+ if (xmlSegments.length > 0) {
144
+ return xmlSegments;
177
145
  }
178
146
 
179
- if (currentPart.trim()) {
180
- parts.push({ text: currentPart.trim(), type: 'text' });
181
- }
147
+ // Otherwise, segment by intent and transitions
148
+ const segments = [];
149
+ const parts = this.segmentByIntent(text);
182
150
 
183
- // Further parse each part
151
+ // Parse each part
184
152
  for (const part of parts) {
185
153
  segments.push({
186
154
  ...part,
@@ -192,6 +160,82 @@ export class ResponseFormatter {
192
160
  return segments;
193
161
  }
194
162
 
163
+ /**
164
+ * Extract XML-tagged content as separate segments
165
+ */
166
+ static extractXMLTags(text) {
167
+ const xmlPattern = /<(thinking|tool_use|tool_result|result|action)[\s>]([\s\S]*?)<\/\1>/gi;
168
+ const segments = [];
169
+ let lastIndex = 0;
170
+ let match;
171
+
172
+ while ((match = xmlPattern.exec(text)) !== null) {
173
+ const before = text.substring(lastIndex, match.index);
174
+ if (before.trim()) {
175
+ segments.push({ type: 'text', text: before.trim() });
176
+ }
177
+
178
+ const tagType = match[1].toLowerCase();
179
+ const tagContent = match[2].trim();
180
+ segments.push({ type: tagType, text: tagContent });
181
+ lastIndex = xmlPattern.lastIndex;
182
+ }
183
+
184
+ if (lastIndex < text.length) {
185
+ const remaining = text.substring(lastIndex).trim();
186
+ if (remaining) {
187
+ segments.push({ type: 'text', text: remaining });
188
+ }
189
+ }
190
+
191
+ return segments.length > 0 ? segments : [];
192
+ }
193
+
194
+ /**
195
+ * Segment by semantic intent/actions
196
+ */
197
+ static segmentByIntent(text) {
198
+ const segments = [];
199
+ const intentPatterns = [
200
+ { pattern: /^(Let me|I'll|I'm going to|First,?|Next,?|Now,?|Here's)/im, type: 'action' },
201
+ { pattern: /^(Looking|Examining|Analyzing|Reviewing|Checking|Reading)/im, type: 'analysis' },
202
+ { pattern: /^(Here'?s|Result|Output|Found|Got|Completed)/im, type: 'result' },
203
+ { pattern: /^(The|This|That|These|Those)/im, type: 'explanation' }
204
+ ];
205
+
206
+ let currentSegment = '';
207
+ let currentType = 'text';
208
+ const lines = text.split('\n');
209
+
210
+ for (const line of lines) {
211
+ let newType = currentType;
212
+
213
+ // Check for intent pattern match
214
+ for (const { pattern, type } of intentPatterns) {
215
+ if (pattern.test(line)) {
216
+ newType = type;
217
+ break;
218
+ }
219
+ }
220
+
221
+ // If type changed and we have content, save segment
222
+ if (newType !== currentType && currentSegment.trim()) {
223
+ segments.push({ type: currentType, text: currentSegment.trim() });
224
+ currentSegment = '';
225
+ currentType = newType;
226
+ }
227
+
228
+ currentSegment += (currentSegment ? '\n' : '') + line;
229
+ }
230
+
231
+ // Add remaining segment
232
+ if (currentSegment.trim()) {
233
+ segments.push({ type: currentType, text: currentSegment.trim() });
234
+ }
235
+
236
+ return segments.length > 0 ? segments : [{ type: 'text', text }];
237
+ }
238
+
195
239
  /**
196
240
  * Format segments for display with proper HTML
197
241
  */
package/static/app.js CHANGED
@@ -677,6 +677,54 @@ class GMGUIApp {
677
677
  li.className = 'response-list-item';
678
678
  li.textContent = segment.content;
679
679
  el.appendChild(li);
680
+ } else if (segment.type === 'thinking') {
681
+ // Collapsible thinking block
682
+ const details = document.createElement('details');
683
+ details.className = 'segment-thinking';
684
+ const summary = document.createElement('summary');
685
+ summary.textContent = '💭 Thinking';
686
+ details.appendChild(summary);
687
+ const content = document.createElement('div');
688
+ content.className = 'thinking-content';
689
+ content.textContent = segment.text;
690
+ details.appendChild(content);
691
+ el.appendChild(details);
692
+ } else if (segment.type === 'tool_use') {
693
+ // Tool call highlight
694
+ const div = document.createElement('div');
695
+ div.className = 'segment-tool-use';
696
+ div.innerHTML = `<div class="tool-icon">⚙️ Tool Call</div><pre class="tool-content"><code>${this.escapeHtml(segment.text)}</code></pre>`;
697
+ el.appendChild(div);
698
+ } else if (segment.type === 'tool_result') {
699
+ // Tool result
700
+ const div = document.createElement('div');
701
+ div.className = 'segment-tool-result';
702
+ div.innerHTML = `<div class="result-icon">📦 Result</div><pre class="result-content"><code>${this.escapeHtml(segment.text)}</code></pre>`;
703
+ el.appendChild(div);
704
+ } else if (segment.type === 'action') {
705
+ // Action statement - bold and prominent
706
+ const p = document.createElement('p');
707
+ p.className = 'response-action';
708
+ p.innerHTML = `<strong>→ ${this.escapeHtml(segment.text)}</strong>`;
709
+ el.appendChild(p);
710
+ } else if (segment.type === 'analysis') {
711
+ // Analysis/investigation
712
+ const p = document.createElement('p');
713
+ p.className = 'response-analysis';
714
+ p.innerHTML = `<em>🔍 ${this.escapeHtml(segment.text)}</em>`;
715
+ el.appendChild(p);
716
+ } else if (segment.type === 'result') {
717
+ // Result presentation
718
+ const div = document.createElement('div');
719
+ div.className = 'response-result';
720
+ div.innerHTML = segment.text
721
+ .replace(/&/g, '&amp;')
722
+ .replace(/</g, '&lt;')
723
+ .replace(/>/g, '&gt;')
724
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
725
+ .replace(/\*(.*?)\*/g, '<em>$1</em>')
726
+ .replace(/`([^`]+)`/g, '<code>$1</code>');
727
+ el.appendChild(div);
680
728
  } else if (segment.type === 'text') {
681
729
  const p = document.createElement('p');
682
730
  p.className = 'response-text';
@@ -693,6 +741,17 @@ class GMGUIApp {
693
741
  return el;
694
742
  }
695
743
 
744
+ escapeHtml(text) {
745
+ if (typeof text !== 'string') return '';
746
+ return text
747
+ .replace(/&/g, '&amp;')
748
+ .replace(/</g, '&lt;')
749
+ .replace(/>/g, '&gt;')
750
+ .replace(/"/g, '&quot;')
751
+ .replace(/'/g, '&#039;');
752
+ }
753
+ }
754
+
696
755
  renderMetadata(metadata) {
697
756
  if (!metadata || Object.keys(metadata).every(k => !metadata[k] || metadata[k].length === 0)) {
698
757
  return null;
package/static/styles.css CHANGED
@@ -1578,3 +1578,115 @@ p code {
1578
1578
  content: '✓ ';
1579
1579
  color: #28a745;
1580
1580
  }
1581
+
1582
+ /* New Segment Types */
1583
+
1584
+ /* Thinking blocks */
1585
+ .segment-thinking {
1586
+ background: #f9f9f9;
1587
+ border-left: 4px solid #999;
1588
+ padding: 1rem;
1589
+ margin: 1rem 0;
1590
+ border-radius: 4px;
1591
+ }
1592
+
1593
+ .segment-thinking summary {
1594
+ cursor: pointer;
1595
+ font-weight: 600;
1596
+ color: #666;
1597
+ user-select: none;
1598
+ padding-bottom: 0.5rem;
1599
+ }
1600
+
1601
+ .segment-thinking summary:hover {
1602
+ color: #333;
1603
+ }
1604
+
1605
+ .thinking-content {
1606
+ color: #555;
1607
+ font-size: 0.95em;
1608
+ line-height: 1.6;
1609
+ margin-top: 0.5rem;
1610
+ padding: 0.5rem 0;
1611
+ }
1612
+
1613
+ /* Tool use blocks */
1614
+ .segment-tool-use {
1615
+ background: #f0f8ff;
1616
+ border-left: 4px solid #007acc;
1617
+ padding: 1rem;
1618
+ margin: 1rem 0;
1619
+ border-radius: 4px;
1620
+ }
1621
+
1622
+ .tool-icon {
1623
+ font-weight: 600;
1624
+ color: #007acc;
1625
+ margin-bottom: 0.5rem;
1626
+ }
1627
+
1628
+ .tool-content {
1629
+ background: #f5f5f5;
1630
+ padding: 0.75rem;
1631
+ border-radius: 3px;
1632
+ overflow-x: auto;
1633
+ }
1634
+
1635
+ .tool-content code {
1636
+ color: #d73a49;
1637
+ font-family: 'Courier New', monospace;
1638
+ }
1639
+
1640
+ /* Tool result blocks */
1641
+ .segment-tool-result {
1642
+ background: #fff9e6;
1643
+ border-left: 4px solid #ffb300;
1644
+ padding: 1rem;
1645
+ margin: 1rem 0;
1646
+ border-radius: 4px;
1647
+ }
1648
+
1649
+ .result-icon {
1650
+ font-weight: 600;
1651
+ color: #997700;
1652
+ margin-bottom: 0.5rem;
1653
+ }
1654
+
1655
+ .result-content {
1656
+ background: #f5f5f5;
1657
+ padding: 0.75rem;
1658
+ border-radius: 3px;
1659
+ overflow-x: auto;
1660
+ }
1661
+
1662
+ /* Action statements */
1663
+ .response-action {
1664
+ background: #e8f5e9;
1665
+ border-left: 4px solid #28a745;
1666
+ padding: 0.75rem 1rem;
1667
+ margin: 1.5rem 0 0.5rem 0;
1668
+ border-radius: 4px;
1669
+ font-weight: 500;
1670
+ color: #1b5e20;
1671
+ }
1672
+
1673
+ /* Analysis blocks */
1674
+ .response-analysis {
1675
+ background: #e3f2fd;
1676
+ border-left: 4px solid #1976d2;
1677
+ padding: 0.75rem 1rem;
1678
+ margin: 1rem 0;
1679
+ border-radius: 4px;
1680
+ color: #0d47a1;
1681
+ font-size: 0.95em;
1682
+ }
1683
+
1684
+ /* Result presentation */
1685
+ .response-result {
1686
+ background: #f3e5f5;
1687
+ border-left: 4px solid #7b1fa2;
1688
+ padding: 1rem;
1689
+ margin: 1rem 0;
1690
+ border-radius: 4px;
1691
+ line-height: 1.6;
1692
+ }