UI Layout
The final UI has three main parts:
- Chat Panel (left) - User interacts here
- Trace Viewer (right top) - See agent steps
- Memory Inspector (right bottom) - View agent memory
Backend API
First, create an API endpoint:
from flask import Flask, request, jsonify
from agent import run_agent_conversation
app = Flask(__name__)
@app.route('/api/chat', methods=['POST'])
def chat():
data = request.json
user_message = data.get('message')
user_id = data.get('user_id')
try:
result = run_agent_conversation(user_message, user_id)
return jsonify({
'success': True,
'type': result['type'],
'reply': result['reply'],
'trace': result.get('trace', []),
'memory': memory_store.get(user_id) if user_id else {}
})
except Exception as e:
return jsonify({
'success': False,
'error': str(e)
}), 500
Frontend: Chat Interface
Here’s a simple React component:
import { useState } from 'react';
function ChatInterface() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const [trace, setTrace] = useState([]);
const [memory, setMemory] = useState({});
const sendMessage = async () => {
if (!input.trim()) return;
const userMessage = {
role: 'user',
content: input
};
setMessages(prev => [...prev, userMessage]);
setInput('');
setLoading(true);
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: input,
user_id: 'user-123' // In real app, get from auth
})
});
const data = await response.json();
if (data.success) {
setMessages(prev => [...prev, {
role: 'assistant',
content: data.reply
}]);
setTrace(data.trace);
setMemory(data.memory);
}
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
return (
<div className="chat-container">
<div className="chat-messages">
{messages.map((msg, i) => (
<div key={i} className={`message message-${msg.role}`}>
{msg.content}
</div>
))}
{loading && <div className="loading">Thinking...</div>}
</div>
<div className="chat-input">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
placeholder="Type your message..."
/>
<button onClick={sendMessage}>Send</button>
</div>
</div>
);
}
Trace Viewer Component
Display the agent trace:
function TraceViewer({ trace }) {
if (!trace || trace.length === 0) {
return <div>No trace data yet</div>;
}
return (
<div className="trace-viewer">
<h3>Agent Trace</h3>
{trace.map((step, i) => (
<div key={i} className={`trace-step trace-${step.action}`}>
<div className="step-header">
<span>Step {step.step}</span>
<span className="action-badge">{step.action}</span>
</div>
{step.tool_name && (
<div className="tool-info">
<strong>Tool:</strong> {step.tool_name}
{step.tool_args && (
<pre>{JSON.stringify(step.tool_args, null, 2)}</pre>
)}
</div>
)}
{step.final_reply && (
<div className="reply">{step.final_reply}</div>
)}
</div>
))}
</div>
);
}
Memory Inspector Component
Show agent memory:
function MemoryInspector({ memory, userId }) {
if (!memory || Object.keys(memory).length === 0) {
return <div>No memory data</div>;
}
return (
<div className="memory-inspector">
<h3>Agent Memory</h3>
<div className="user-id">User: {userId}</div>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{Object.entries(memory).map(([key, value]) => (
<tr key={key}>
<td><code>{key}</code></td>
<td>{JSON.stringify(value)}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Complete Layout
Put it all together:
function AgentPlayground() {
const [messages, setMessages] = useState([]);
const [trace, setTrace] = useState([]);
const [memory, setMemory] = useState({});
const userId = 'user-123';
return (
<div className="playground">
<div className="left-panel">
<ChatInterface
onMessage={handleMessage}
messages={messages}
/>
</div>
<div className="right-panel">
<TraceViewer trace={trace} />
<MemoryInspector memory={memory} userId={userId} />
</div>
</div>
);
}
Error Handling
Handle errors gracefully:
@app.route('/api/chat', methods=['POST'])
def chat():
try:
# Validate input
if not request.json or 'message' not in request.json:
return jsonify({'error': 'Message required'}), 400
user_message = request.json['message']
if len(user_message) > 1000:
return jsonify({'error': 'Message too long'}), 400
# Rate limiting (simple version)
# ... check rate limits ...
# Run agent
result = run_agent_conversation(
user_message,
request.json.get('user_id'),
max_steps=5
)
return jsonify({
'success': True,
**result
})
except TimeoutError:
return jsonify({
'success': False,
'error': 'Request timed out'
}), 504
except Exception as e:
logger.error(f"Agent error: {e}")
return jsonify({
'success': False,
'error': 'Internal server error'
}), 500
Scenario Presets
Add preset buttons for testing:
const scenarios = [
{ label: 'FAQ', message: 'What is SimpleSaaS?' },
{ label: 'Subscription', message: 'What\'s my current plan?' },
{ label: 'Billing Issue', message: 'I want a refund for last month' }
];
function ScenarioPresets({ onSelect }) {
return (
<div className="scenario-presets">
{scenarios.map((scenario) => (
<button
key={scenario.label}
onClick={() => onSelect(scenario.message)}
>
{scenario.label}
</button>
))}
</div>
);
}
Live Demo
Here’s what the final UI looks like:
SimpleSaaS Support
● OnlineTrace Example
When the agent runs, you’ll see a trace like this:
Agent Execution Trace
Step 1 thinking 10:23:15
Reasoning: User asked about subscription. Need to check their account.
Step 2 tool call 10:23:16
Tool: get_subscription_status
Arguments
{
"user_id": "user-123"
} Result
{
"plan": "Pro",
"status": "active",
"expires": "2025-12-01"
} Step 3 final answer 10:23:17
Reply: Your current plan is Pro, and it's active until December 1, 2025.
Memory Display
Memory updates as the agent learns:
Agent Memory
User ID:
user-123 | Key | Value | Updated |
|---|---|---|
last_known_plan | Pro | 2025-11-24 10:23:16 |
last_interaction | subscription_check | 2025-11-24 10:23:17 |
Key Implementation Points
- Separate concerns - Chat, trace, and memory are separate components
- Real-time updates - Update UI as agent processes
- Error handling - Show errors clearly to users
- Loading states - Show when agent is thinking
- Responsive design - Works on mobile and desktop
What’s Next?
You’ve built a complete agent! The final page covers extensions, exercises, and reflection on what you’ve learned.
Progress 88%
Page 7 of 8
← Previous
→ Next