Skip to content

Commit 68f1c57

Browse files
Fix events display issue and implement expando UI
- Fix property mismatch: UI was looking for event.type but API returns event.kind - Add proper event content extraction for different event types (MessageEvent, ActionEvent, ObservationEvent, etc.) - Implement expando UI showing most recent event by default with option to show all events - Add enhanced type definitions for specific event types with proper discriminated unions - Improve event display with better visual hierarchy, content truncation, and raw data details - Add proper dark mode support and responsive design - Sort events by timestamp with most recent first and highlight latest event Co-authored-by: openhands <openhands@all-hands.dev>
1 parent e76ac48 commit 68f1c57

File tree

3 files changed

+206
-22
lines changed

3 files changed

+206
-22
lines changed

example/src/components/ConversationManager.tsx

Lines changed: 161 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,82 @@ interface ConversationData extends ConversationInfo {
1515
agentStatus?: AgentExecutionStatus;
1616
}
1717

18+
// Utility function to extract displayable content from events
19+
const getEventDisplayContent = (event: Event): { title: string; content: string; details?: any } => {
20+
switch (event.kind) {
21+
case 'MessageEvent':
22+
const messageEvent = event as any;
23+
const message = messageEvent.llm_message;
24+
if (message && message.content && Array.isArray(message.content)) {
25+
const textContent = message.content
26+
.filter((c: any) => c.type === 'text')
27+
.map((c: any) => c.text)
28+
.join(' ');
29+
return {
30+
title: `Message from ${messageEvent.source || 'unknown'}`,
31+
content: textContent || 'No text content',
32+
details: message
33+
};
34+
}
35+
return {
36+
title: `Message from ${messageEvent.source || 'unknown'}`,
37+
content: 'No message content available',
38+
details: messageEvent
39+
};
40+
41+
case 'ActionEvent':
42+
const actionEvent = event as any;
43+
const action = actionEvent.action;
44+
return {
45+
title: `Action: ${action?.kind || 'Unknown Action'}`,
46+
content: action?.command || action?.content || JSON.stringify(action, null, 2),
47+
details: action
48+
};
49+
50+
case 'ObservationEvent':
51+
const obsEvent = event as any;
52+
return {
53+
title: `Observation: ${obsEvent.tool_name || 'Unknown Tool'}`,
54+
content: typeof obsEvent.observation === 'string'
55+
? obsEvent.observation
56+
: JSON.stringify(obsEvent.observation, null, 2),
57+
details: obsEvent.observation
58+
};
59+
60+
case 'AgentErrorEvent':
61+
const errorEvent = event as any;
62+
return {
63+
title: `Error: ${errorEvent.tool_name || 'Unknown Tool'}`,
64+
content: typeof errorEvent.observation === 'string'
65+
? errorEvent.observation
66+
: JSON.stringify(errorEvent.observation, null, 2),
67+
details: errorEvent.observation
68+
};
69+
70+
case 'SystemPromptEvent':
71+
const sysEvent = event as any;
72+
return {
73+
title: 'System Prompt',
74+
content: sysEvent.system_prompt?.text || 'System prompt updated',
75+
details: sysEvent.system_prompt
76+
};
77+
78+
case 'PauseEvent':
79+
return {
80+
title: 'Agent Paused',
81+
content: 'Agent execution was paused',
82+
details: null
83+
};
84+
85+
default:
86+
return {
87+
title: event.kind || 'Unknown Event',
88+
content: JSON.stringify(event, null, 2),
89+
details: event
90+
};
91+
}
92+
};
93+
1894
export const ConversationManager: React.FC = () => {
1995
const { settings } = useSettings();
2096
const [conversations, setConversations] = useState<ConversationData[]>([]);
@@ -23,6 +99,7 @@ export const ConversationManager: React.FC = () => {
2399
const [manager, setManager] = useState<SDKConversationManager | null>(null);
24100
const [selectedConversationId, setSelectedConversationId] = useState<string | null>(null);
25101
const [messageInput, setMessageInput] = useState('');
102+
const [showAllEvents, setShowAllEvents] = useState(false);
26103

27104
// Get selected conversation data
28105
const selectedConversation = conversations.find(c => c.id === selectedConversationId);
@@ -472,31 +549,93 @@ export const ConversationManager: React.FC = () => {
472549
</div>
473550

474551
<div className="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700">
475-
<h4 className="text-base font-semibold text-gray-900 dark:text-white mb-3">Events & Messages</h4>
552+
<div className="flex justify-between items-center mb-3">
553+
<h4 className="text-base font-semibold text-gray-900 dark:text-white">Events & Messages</h4>
554+
{selectedConversation.events && selectedConversation.events.length > 1 && (
555+
<button
556+
onClick={() => setShowAllEvents(!showAllEvents)}
557+
className="text-sm text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300 font-medium transition-colors duration-200"
558+
>
559+
{showAllEvents ? '▼ Show Recent Only' : `▶ Show All (${selectedConversation.events.length})`}
560+
</button>
561+
)}
562+
</div>
476563
<div className="max-h-64 overflow-y-auto space-y-3">
477564
{selectedConversation.events && selectedConversation.events.length > 0 ? (
478-
selectedConversation.events.map((event, index) => (
479-
<div key={index} className="border border-gray-200 dark:border-gray-700 rounded-lg p-3 bg-gray-50 dark:bg-gray-900">
480-
<div className="flex justify-between items-center mb-2">
481-
<span className="text-sm font-medium text-indigo-600 dark:text-indigo-400 bg-indigo-50 dark:bg-indigo-900/30 px-2 py-1 rounded">
482-
{event.type}
483-
</span>
484-
<span className="text-xs text-gray-500 dark:text-gray-400">
485-
{event.timestamp ? new Date(event.timestamp).toLocaleTimeString() : ''}
486-
</span>
487-
</div>
488-
{event.message && (
489-
<div className="text-sm text-gray-900 dark:text-white mb-2 p-2 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700">
490-
{event.message}
565+
(() => {
566+
// Sort events by timestamp (most recent first)
567+
const sortedEvents = [...selectedConversation.events].sort((a, b) =>
568+
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
569+
);
570+
571+
// Show only the most recent event unless expanded
572+
const eventsToShow = showAllEvents ? sortedEvents : sortedEvents.slice(0, 1);
573+
574+
return eventsToShow.map((event, index) => {
575+
const displayContent = getEventDisplayContent(event);
576+
const isRecent = index === 0 && !showAllEvents;
577+
578+
return (
579+
<div
580+
key={event.id || index}
581+
className={`border border-gray-200 dark:border-gray-700 rounded-lg p-3 bg-gray-50 dark:bg-gray-900 ${
582+
isRecent ? 'ring-2 ring-indigo-200 dark:ring-indigo-800' : ''
583+
}`}
584+
>
585+
<div className="flex justify-between items-center mb-2">
586+
<div className="flex items-center gap-2">
587+
<span className="text-sm font-medium text-indigo-600 dark:text-indigo-400 bg-indigo-50 dark:bg-indigo-900/30 px-2 py-1 rounded">
588+
{event.kind}
589+
</span>
590+
{event.source && (
591+
<span className="text-xs text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">
592+
{event.source}
593+
</span>
594+
)}
595+
{isRecent && (
596+
<span className="text-xs text-green-600 dark:text-green-400 bg-green-50 dark:bg-green-900/30 px-2 py-1 rounded font-medium">
597+
Latest
598+
</span>
599+
)}
600+
</div>
601+
<span className="text-xs text-gray-500 dark:text-gray-400">
602+
{event.timestamp ? new Date(event.timestamp).toLocaleTimeString() : ''}
603+
</span>
604+
</div>
605+
606+
<div className="text-sm font-medium text-gray-900 dark:text-white mb-2">
607+
{displayContent.title}
608+
</div>
609+
610+
{displayContent.content && (
611+
<div className="text-sm text-gray-700 dark:text-gray-300 mb-2 p-2 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700">
612+
<div className="max-h-32 overflow-y-auto">
613+
{displayContent.content.length > 200 ? (
614+
<>
615+
{displayContent.content.substring(0, 200)}
616+
<span className="text-gray-500 dark:text-gray-400">... (truncated)</span>
617+
</>
618+
) : (
619+
displayContent.content
620+
)}
621+
</div>
622+
</div>
623+
)}
624+
625+
{displayContent.details && (
626+
<details className="mt-2">
627+
<summary className="text-xs text-gray-500 dark:text-gray-400 cursor-pointer hover:text-gray-700 dark:hover:text-gray-300">
628+
Show raw data
629+
</summary>
630+
<div className="text-xs text-gray-600 dark:text-gray-400 font-mono bg-gray-100 dark:bg-gray-800 p-2 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto mt-1">
631+
<pre>{JSON.stringify(displayContent.details, null, 2)}</pre>
632+
</div>
633+
</details>
634+
)}
491635
</div>
492-
)}
493-
{event.content && (
494-
<div className="text-xs text-gray-600 dark:text-gray-400 font-mono bg-gray-100 dark:bg-gray-800 p-2 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">
495-
<pre>{JSON.stringify(event.content, null, 2)}</pre>
496-
</div>
497-
)}
498-
</div>
499-
))
636+
);
637+
});
638+
})()
500639
) : (
501640
<div className="text-center py-4 text-gray-500 dark:text-gray-400">No events yet</div>
502641
)}

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ export { HttpClient, HttpError } from './client/http-client';
2222
export type {
2323
ConversationID,
2424
Event,
25+
MessageEvent,
26+
ActionEvent,
27+
ObservationEvent,
28+
AgentErrorEvent,
29+
SystemPromptEvent,
30+
PauseEvent,
2531
Message,
2632
MessageContent,
2733
TextContent,

src/types/base.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,48 @@ export interface Event {
88
id: string;
99
kind: string;
1010
timestamp: string;
11+
source?: 'agent' | 'user' | 'environment';
1112
[key: string]: any;
1213
}
1314

15+
// Specific event types for better type safety
16+
export interface MessageEvent extends Event {
17+
kind: 'MessageEvent';
18+
llm_message: Message;
19+
activated_skills?: string[];
20+
}
21+
22+
export interface ActionEvent extends Event {
23+
kind: 'ActionEvent';
24+
action: any; // The action object varies by action type
25+
}
26+
27+
export interface ObservationEvent extends Event {
28+
kind: 'ObservationEvent';
29+
tool_name: string;
30+
tool_call_id: string;
31+
observation: any;
32+
action_id: string;
33+
}
34+
35+
export interface AgentErrorEvent extends Event {
36+
kind: 'AgentErrorEvent';
37+
tool_name: string;
38+
tool_call_id: string;
39+
observation: any;
40+
action_id: string;
41+
}
42+
43+
export interface SystemPromptEvent extends Event {
44+
kind: 'SystemPromptEvent';
45+
system_prompt: TextContent;
46+
tools: any[];
47+
}
48+
49+
export interface PauseEvent extends Event {
50+
kind: 'PauseEvent';
51+
}
52+
1453
export interface Message {
1554
role: 'user' | 'assistant' | 'system';
1655
content: MessageContent[];

0 commit comments

Comments
 (0)