Skip to content

Commit 87c22d3

Browse files
Fix conversation selection to load full conversation details
- Add loadConversationDetails function to fetch RemoteConversation and events - Update conversation click handler to load details when selected - Add conversation events display with messages, actions, and observations - Add proper styling for events list with color-coded event types - Clear conversation details when conversation is deleted or deselected - Show loading state while fetching conversation details Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 02cd869 commit 87c22d3

File tree

2 files changed

+199
-5
lines changed

2 files changed

+199
-5
lines changed

example/src/components/ConversationManager.css

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,104 @@
289289
margin-bottom: 10px;
290290
}
291291

292+
/* Events and Messages Styles */
293+
.conversation-events {
294+
background-color: white;
295+
border: 1px solid #d1d5db;
296+
border-radius: 6px;
297+
padding: 15px;
298+
margin-bottom: 20px;
299+
max-height: 400px;
300+
overflow-y: auto;
301+
}
302+
303+
.conversation-events h3 {
304+
margin: 0 0 15px 0;
305+
color: #1f2937;
306+
font-size: 1rem;
307+
}
308+
309+
.events-list {
310+
display: flex;
311+
flex-direction: column;
312+
gap: 10px;
313+
}
314+
315+
.event-item {
316+
border: 1px solid #e5e7eb;
317+
border-radius: 4px;
318+
padding: 10px;
319+
background-color: #f9fafb;
320+
}
321+
322+
.event-item.event-message {
323+
border-left: 4px solid #3b82f6;
324+
}
325+
326+
.event-item.event-action {
327+
border-left: 4px solid #f59e0b;
328+
}
329+
330+
.event-item.event-observation {
331+
border-left: 4px solid #10b981;
332+
}
333+
334+
.event-header {
335+
display: flex;
336+
justify-content: space-between;
337+
align-items: center;
338+
margin-bottom: 8px;
339+
font-size: 12px;
340+
}
341+
342+
.event-type {
343+
background-color: #e5e7eb;
344+
padding: 2px 6px;
345+
border-radius: 3px;
346+
font-weight: 500;
347+
text-transform: uppercase;
348+
}
349+
350+
.event-timestamp {
351+
color: #6b7280;
352+
font-size: 11px;
353+
}
354+
355+
.event-content {
356+
font-size: 13px;
357+
line-height: 1.4;
358+
}
359+
360+
.message-content {
361+
margin-top: 5px;
362+
padding: 8px;
363+
background-color: white;
364+
border-radius: 3px;
365+
border: 1px solid #e5e7eb;
366+
}
367+
368+
.action-args {
369+
margin-top: 8px;
370+
}
371+
372+
.action-args pre {
373+
background-color: #f3f4f6;
374+
padding: 8px;
375+
border-radius: 3px;
376+
font-size: 11px;
377+
overflow-x: auto;
378+
margin: 4px 0 0 0;
379+
}
380+
381+
.observation-content {
382+
margin-top: 5px;
383+
padding: 8px;
384+
background-color: white;
385+
border-radius: 3px;
386+
border: 1px solid #e5e7eb;
387+
white-space: pre-wrap;
388+
}
389+
292390
/* Responsive design */
293391
@media (max-width: 768px) {
294392
.conversation-layout {

example/src/components/ConversationManager.tsx

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export const ConversationManager: React.FC = () => {
2222
const [error, setError] = useState<string | null>(null);
2323
const [manager, setManager] = useState<SDKConversationManager | null>(null);
2424
const [selectedConversation, setSelectedConversation] = useState<string | null>(null);
25+
const [selectedConversationDetails, setSelectedConversationDetails] = useState<RemoteConversation | null>(null);
26+
const [conversationEvents, setConversationEvents] = useState<any[]>([]);
2527
const [messageInput, setMessageInput] = useState('');
2628
const [activeConversations, setActiveConversations] = useState<Map<string, RemoteConversation>>(new Map());
2729
const [currentAgent, setCurrentAgent] = useState<AgentBase | null>(null);
@@ -152,6 +154,8 @@ export const ConversationManager: React.FC = () => {
152154
// Clear selection if this conversation was selected
153155
if (selectedConversation === conversationId) {
154156
setSelectedConversation(null);
157+
setSelectedConversationDetails(null);
158+
setConversationEvents([]);
155159
}
156160

157161
// Reload conversations list
@@ -163,6 +167,36 @@ export const ConversationManager: React.FC = () => {
163167
}
164168
};
165169

170+
// Load conversation details
171+
const loadConversationDetails = async (conversationId: string) => {
172+
if (!manager) return;
173+
174+
setLoading(true);
175+
setError(null);
176+
try {
177+
// Check if we already have this conversation loaded
178+
let conversation = activeConversations.get(conversationId);
179+
180+
if (!conversation) {
181+
// Load the conversation if not already active
182+
conversation = await manager.loadConversation(conversationId);
183+
setActiveConversations(prev => new Map(prev.set(conversationId, conversation!)));
184+
}
185+
186+
setSelectedConversationDetails(conversation);
187+
188+
// Load events
189+
const events = await conversation.state.events.getEvents();
190+
setConversationEvents(events);
191+
} catch (err) {
192+
setError(err instanceof Error ? err.message : 'Failed to load conversation details');
193+
setSelectedConversationDetails(null);
194+
setConversationEvents([]);
195+
} finally {
196+
setLoading(false);
197+
}
198+
};
199+
166200
// Send message to conversation
167201
const sendMessage = async (conversationId: string, message: string) => {
168202
if (!manager || !message.trim()) return;
@@ -272,7 +306,10 @@ export const ConversationManager: React.FC = () => {
272306
<div
273307
key={conversation.id}
274308
className={`conversation-item ${selectedConversation === conversation.id ? 'selected' : ''}`}
275-
onClick={() => setSelectedConversation(conversation.id)}
309+
onClick={() => {
310+
setSelectedConversation(conversation.id);
311+
loadConversationDetails(conversation.id);
312+
}}
276313
>
277314
<div className="conversation-info">
278315
<div className="conversation-id">
@@ -331,15 +368,74 @@ export const ConversationManager: React.FC = () => {
331368
</p>
332369
<p><strong>Agent:</strong> {conv.agent?.name}</p>
333370
<p><strong>Model:</strong> {conv.agent?.llm?.model}</p>
334-
<p><strong>Total Events:</strong> {conv.conversation_stats?.total_events || 0}</p>
335-
<p><strong>Messages:</strong> {conv.conversation_stats?.message_events || 0}</p>
336-
<p><strong>Actions:</strong> {conv.conversation_stats?.action_events || 0}</p>
337-
<p><strong>Observations:</strong> {conv.conversation_stats?.observation_events || 0}</p>
371+
<p><strong>Total Events:</strong> {conversationEvents.length}</p>
372+
<p><strong>Messages:</strong> {conversationEvents.filter(e => e.event_type === 'message').length}</p>
373+
<p><strong>Actions:</strong> {conversationEvents.filter(e => e.event_type === 'action').length}</p>
374+
<p><strong>Observations:</strong> {conversationEvents.filter(e => e.event_type === 'observation').length}</p>
338375
</div>
339376
);
340377
})()}
341378
</div>
342379

380+
{/* Events/Messages Section */}
381+
<div className="conversation-events">
382+
<h3>Events & Messages</h3>
383+
{loading && <p>Loading conversation details...</p>}
384+
{conversationEvents.length === 0 && !loading && (
385+
<p>No events found in this conversation.</p>
386+
)}
387+
<div className="events-list">
388+
{conversationEvents.map((event, index) => (
389+
<div key={index} className={`event-item event-${event.event_type}`}>
390+
<div className="event-header">
391+
<span className="event-type">{event.event_type}</span>
392+
<span className="event-timestamp">
393+
{event.timestamp ? new Date(event.timestamp).toLocaleString() : 'No timestamp'}
394+
</span>
395+
</div>
396+
<div className="event-content">
397+
{event.event_type === 'message' && event.content && (
398+
<div>
399+
<strong>{event.content.role}:</strong>
400+
<div className="message-content">
401+
{Array.isArray(event.content.content)
402+
? event.content.content.map((c: any, i: number) => (
403+
<div key={i}>{c.text || JSON.stringify(c)}</div>
404+
))
405+
: event.content.content
406+
}
407+
</div>
408+
</div>
409+
)}
410+
{event.event_type === 'action' && (
411+
<div>
412+
<strong>Action:</strong> {event.action || 'Unknown action'}
413+
{event.args && (
414+
<div className="action-args">
415+
<strong>Args:</strong> <pre>{JSON.stringify(event.args, null, 2)}</pre>
416+
</div>
417+
)}
418+
</div>
419+
)}
420+
{event.event_type === 'observation' && (
421+
<div>
422+
<strong>Observation:</strong>
423+
<div className="observation-content">
424+
{event.content || event.observation || 'No content'}
425+
</div>
426+
</div>
427+
)}
428+
{!['message', 'action', 'observation'].includes(event.event_type) && (
429+
<div>
430+
<pre>{JSON.stringify(event, null, 2)}</pre>
431+
</div>
432+
)}
433+
</div>
434+
</div>
435+
))}
436+
</div>
437+
</div>
438+
343439
<div className="message-input-section">
344440
<h3>Send Message</h3>
345441
<div className="message-input-container">

0 commit comments

Comments
 (0)