@@ -39,6 +39,44 @@ export const ConversationManager: React.FC = () => {
3939 }
4040 } , [ settings . agentServerUrl , settings . agentServerApiKey ] ) ;
4141
42+ // Cleanup WebSocket connections on unmount
43+ useEffect ( ( ) => {
44+ return ( ) => {
45+ if ( selectedConversation ?. remoteConversation ) {
46+ selectedConversation . remoteConversation . stopWebSocketClient ( ) . catch ( err => {
47+ console . warn ( 'Failed to stop WebSocket client on unmount:' , err ) ;
48+ } ) ;
49+ }
50+ } ;
51+ } , [ selectedConversation ?. remoteConversation ] ) ;
52+
53+ // Periodic status refresh for running conversations
54+ useEffect ( ( ) => {
55+ if ( ! selectedConversation ?. remoteConversation ) return ;
56+
57+ const refreshStatus = async ( ) => {
58+ try {
59+ const status = await selectedConversation . remoteConversation ! . state . getAgentStatus ( ) ;
60+ setConversations ( prev => prev . map ( conv =>
61+ conv . id === selectedConversation . id
62+ ? { ...conv , agentStatus : status }
63+ : conv
64+ ) ) ;
65+ } catch ( err ) {
66+ console . warn ( 'Failed to refresh agent status:' , err ) ;
67+ }
68+ } ;
69+
70+ // Refresh every 2 seconds if the agent is running
71+ const interval = setInterval ( ( ) => {
72+ if ( selectedConversation . agentStatus === 'running' ) {
73+ refreshStatus ( ) ;
74+ }
75+ } , 2000 ) ;
76+
77+ return ( ) => clearInterval ( interval ) ;
78+ } , [ selectedConversation ?. id , selectedConversation ?. agentStatus ] ) ;
79+
4280 const loadConversations = async ( conversationManager ?: SDKConversationManager ) => {
4381 const mgr = conversationManager || manager ;
4482 if ( ! mgr ) return ;
@@ -84,13 +122,46 @@ export const ConversationManager: React.FC = () => {
84122 }
85123 } ;
86124
87- const conversation = await manager . createConversation ( agent , {
88- initialMessage : 'Hello! I\'m ready to help you with your tasks.' ,
89- maxIterations : 50 ,
90- } ) ;
125+ const conversation = await RemoteConversation . create (
126+ manager . host ,
127+ agent ,
128+ {
129+ apiKey : manager . apiKey ,
130+ initialMessage : 'Hello! I\'m ready to help you with your tasks.' ,
131+ maxIterations : 50 ,
132+ callback : ( event : Event ) => {
133+ console . log ( 'Received WebSocket event for new conversation:' , event ) ;
134+
135+ // Update the conversation's events in real-time
136+ setConversations ( prev => prev . map ( conv => {
137+ if ( conv . id === conversation . id && conv . events ) {
138+ const updatedEvents = [ ...conv . events , event ] ;
139+ return { ...conv , events : updatedEvents } ;
140+ }
141+ return conv ;
142+ } ) ) ;
143+ } ,
144+ }
145+ ) ;
91146
92147 console . log ( 'Created conversation:' , conversation ) ;
93148
149+ // Start WebSocket client for real-time updates
150+ try {
151+ await conversation . startWebSocketClient ( ) ;
152+ console . log ( 'WebSocket client started for new conversation' ) ;
153+ } catch ( wsErr ) {
154+ console . warn ( 'Failed to start WebSocket client for new conversation:' , wsErr ) ;
155+ }
156+
157+ // Run the initial message to start the conversation
158+ try {
159+ await conversation . run ( ) ;
160+ console . log ( 'Started conversation with initial message' ) ;
161+ } catch ( runErr ) {
162+ console . warn ( 'Failed to run initial message:' , runErr ) ;
163+ }
164+
94165 // Reload conversations to show the new one
95166 await loadConversations ( ) ;
96167 } catch ( err ) {
@@ -127,14 +198,72 @@ export const ConversationManager: React.FC = () => {
127198 const selectConversation = async ( conversationId : string ) => {
128199 if ( ! manager ) return ;
129200
201+ // Clean up previous conversation's WebSocket if any
202+ if ( selectedConversation ?. remoteConversation ) {
203+ try {
204+ await selectedConversation . remoteConversation . stopWebSocketClient ( ) ;
205+ console . log ( 'Stopped WebSocket client for previous conversation' ) ;
206+ } catch ( err ) {
207+ console . warn ( 'Failed to stop previous WebSocket client:' , err ) ;
208+ }
209+ }
210+
130211 console . log ( 'Selecting conversation:' , conversationId ) ;
131212 setSelectedConversationId ( conversationId ) ;
132213
133214 // Load conversation details
134215 try {
135- const remoteConversation = await manager . loadConversation ( conversationId ) ;
216+ // Create a callback to handle real-time events
217+ const eventCallback = ( event : Event ) => {
218+ console . log ( 'Received WebSocket event:' , event ) ;
219+
220+ // Update the conversation's events in real-time
221+ setConversations ( prev => prev . map ( conv => {
222+ if ( conv . id === conversationId && conv . events ) {
223+ // Add the new event to the existing events
224+ const updatedEvents = [ ...conv . events , event ] ;
225+ return { ...conv , events : updatedEvents } ;
226+ }
227+ return conv ;
228+ } ) ) ;
229+
230+ // If it's a status change event, update the agent status
231+ if ( event . type === 'agent_status_change' || event . type === 'agent_state_changed' ) {
232+ // Refresh agent status after a short delay to ensure the server has updated
233+ setTimeout ( ( ) => {
234+ if ( remoteConversation ) {
235+ remoteConversation . state . getAgentStatus ( ) . then ( status => {
236+ setConversations ( prev => prev . map ( conv =>
237+ conv . id === conversationId
238+ ? { ...conv , agentStatus : status }
239+ : conv
240+ ) ) ;
241+ } ) . catch ( err => console . warn ( 'Failed to update agent status:' , err ) ) ;
242+ }
243+ } , 100 ) ;
244+ }
245+ } ;
246+
247+ // Load conversation with callback
248+ const remoteConversation = await RemoteConversation . load (
249+ manager . host ,
250+ conversationId ,
251+ {
252+ apiKey : manager . apiKey ,
253+ callback : eventCallback ,
254+ }
255+ ) ;
136256 console . log ( 'Loaded remote conversation:' , remoteConversation ) ;
137257
258+ // Start WebSocket client for real-time updates
259+ try {
260+ await remoteConversation . startWebSocketClient ( ) ;
261+ console . log ( 'WebSocket client started for conversation:' , conversationId ) ;
262+ } catch ( wsErr ) {
263+ console . warn ( 'Failed to start WebSocket client:' , wsErr ) ;
264+ // Don't fail the whole operation if WebSocket fails
265+ }
266+
138267 // Get events
139268 const events = await remoteConversation . state . events . getEvents ( ) ;
140269 console . log ( 'Loaded events:' , events ) ;
@@ -160,14 +289,19 @@ export const ConversationManager: React.FC = () => {
160289 if ( ! selectedConversation ?. remoteConversation || ! messageInput . trim ( ) ) return ;
161290
162291 try {
292+ // Send the message
163293 await selectedConversation . remoteConversation . sendMessage ( messageInput ) ;
164294 setMessageInput ( '' ) ;
165295
166- // Reload conversation details to show new events
167- await selectConversation ( selectedConversation . id ) ;
296+ // Start the agent to process the message (non-blocking)
297+ await selectedConversation . remoteConversation . run ( ) ;
298+ console . log ( 'Agent started to process the message' ) ;
299+
300+ // The WebSocket will receive events as the agent works
301+ // No need to reload immediately - events will come via WebSocket
168302 } catch ( err ) {
169- console . error ( 'Failed to send message:' , err ) ;
170- setError ( err instanceof Error ? err . message : 'Failed to send message' ) ;
303+ console . error ( 'Failed to send message or start agent :' , err ) ;
304+ setError ( err instanceof Error ? err . message : 'Failed to send message or start agent ' ) ;
171305 }
172306 } ;
173307
@@ -185,10 +319,23 @@ export const ConversationManager: React.FC = () => {
185319 case 'running' : return 'text-green-500' ;
186320 case 'stopped' : return 'text-red-500' ;
187321 case 'paused' : return 'text-orange-500' ;
322+ case 'finished' : return 'text-blue-500' ;
323+ case 'error' : return 'text-red-600' ;
188324 default : return 'text-gray-500' ;
189325 }
190326 } ;
191327
328+ const getStatusIcon = ( status ?: string ) => {
329+ switch ( status ) {
330+ case 'running' : return '🔄' ;
331+ case 'stopped' : return '⏹️' ;
332+ case 'paused' : return '⏸️' ;
333+ case 'finished' : return '✅' ;
334+ case 'error' : return '❌' ;
335+ default : return '❓' ;
336+ }
337+ } ;
338+
192339 return (
193340 < div className = "bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 m-0" >
194341 < div className = "flex justify-between items-center mb-6 pb-4 border-b border-gray-200 dark:border-gray-700" >
@@ -252,7 +399,7 @@ export const ConversationManager: React.FC = () => {
252399 < div className = "flex items-center gap-2" >
253400 < span className = "text-gray-600 dark:text-gray-400" > Status:</ span >
254401 < span className = { `font-medium ${ getStatusColorClass ( conversation . status ) } ` } >
255- ● { conversation . status || 'unknown' }
402+ { getStatusIcon ( conversation . status ) } { conversation . status || 'unknown' }
256403 </ span >
257404 </ div >
258405 </ div >
@@ -292,7 +439,7 @@ export const ConversationManager: React.FC = () => {
292439 < div className = "flex justify-between" >
293440 < span className = "font-medium text-gray-900 dark:text-white" > Status:</ span >
294441 < span className = { `font-medium ${ getStatusColorClass ( selectedConversation . status ) } ` } >
295- ● { selectedConversation . status || 'unknown' }
442+ { getStatusIcon ( selectedConversation . status ) } { selectedConversation . status || 'unknown' }
296443 </ span >
297444 </ div >
298445 < div className = "flex justify-between" >
@@ -366,13 +513,55 @@ export const ConversationManager: React.FC = () => {
366513 className = "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:border-indigo-600 focus:shadow-md transition-all duration-200 resize-vertical"
367514 rows = { 3 }
368515 />
369- < button
370- onClick = { sendMessage }
371- disabled = { ! messageInput . trim ( ) || loading }
372- className = "w-full bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
373- >
374- Send Message
375- </ button >
516+ < div className = "flex gap-2" >
517+ < button
518+ onClick = { sendMessage }
519+ disabled = { ! messageInput . trim ( ) || loading }
520+ className = "flex-1 bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
521+ >
522+ Send & Run
523+ </ button >
524+ < button
525+ onClick = { async ( ) => {
526+ if ( selectedConversation ?. remoteConversation ) {
527+ try {
528+ await selectedConversation . remoteConversation . run ( ) ;
529+ console . log ( 'Agent started manually' ) ;
530+ // Reload conversation details
531+ await selectConversation ( selectedConversation . id ) ;
532+ } catch ( err ) {
533+ console . error ( 'Failed to start agent:' , err ) ;
534+ setError ( err instanceof Error ? err . message : 'Failed to start agent' ) ;
535+ }
536+ }
537+ } }
538+ disabled = { loading }
539+ className = "bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
540+ title = "Start/Resume the agent"
541+ >
542+ ▶️ Run
543+ </ button >
544+ < button
545+ onClick = { async ( ) => {
546+ if ( selectedConversation ?. remoteConversation ) {
547+ try {
548+ await selectedConversation . remoteConversation . pause ( ) ;
549+ console . log ( 'Agent paused manually' ) ;
550+ // Reload conversation details
551+ await selectConversation ( selectedConversation . id ) ;
552+ } catch ( err ) {
553+ console . error ( 'Failed to pause agent:' , err ) ;
554+ setError ( err instanceof Error ? err . message : 'Failed to pause agent' ) ;
555+ }
556+ }
557+ } }
558+ disabled = { loading }
559+ className = "bg-orange-600 hover:bg-orange-700 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
560+ title = "Pause the agent"
561+ >
562+ ⏸️ Pause
563+ </ button >
564+ </ div >
376565 </ div >
377566 </ div >
378567 </ div >
0 commit comments