@@ -141,112 +141,80 @@ export function ChatProvider({ children }: PropsWithChildren) {
141141 const eventSourceRef = useRef < EventSource | null > ( null ) ;
142142 const agentAPIUrl = useAgentAPIUrl ( ) ;
143143
144- // Set up SSE connection to the events endpoint
144+ // Set up SSE connection to the events endpoint. EventSource handles
145+ // reconnection automatically, so we only create it once per URL and
146+ // let the browser manage transient failures. Messages are NOT cleared
147+ // on reconnect to avoid blanking the conversation on network blips.
145148 useEffect ( ( ) => {
146- // Function to create and set up EventSource
147- const setupEventSource = ( ) => {
148- if ( eventSourceRef . current ) {
149- eventSourceRef . current . close ( ) ;
150- }
149+ if ( ! agentAPIUrl ) {
150+ console . warn (
151+ "agentAPIUrl is not set, SSE connection cannot be established."
152+ ) ;
153+ setServerStatus ( "offline" ) ;
154+ return ;
155+ }
151156
152- // Reset messages when establishing a new connection
153- setMessages ( [ ] ) ;
157+ const eventSource = new EventSource ( ` ${ agentAPIUrl } /events` ) ;
158+ eventSourceRef . current = eventSource ;
154159
155- if ( ! agentAPIUrl ) {
156- console . warn (
157- "agentAPIUrl is not set, SSE connection cannot be established."
160+ // Handle message updates
161+ eventSource . addEventListener ( "message_update" , ( event ) => {
162+ const data : MessageUpdateEvent = JSON . parse ( event . data ) ;
163+ const confirmed : Message = {
164+ role : data . role ,
165+ content : data . message ,
166+ id : data . id ,
167+ } ;
168+
169+ setMessages ( ( prevMessages ) => {
170+ // Check if message with this ID already exists
171+ const existingIndex = prevMessages . findIndex (
172+ ( m ) => m . id === data . id
158173 ) ;
159- setServerStatus ( "offline" ) ; // Or some other appropriate status
160- return null ; // Don't try to connect if URL is empty
161- }
162174
163- const eventSource = new EventSource ( `${ agentAPIUrl } /events` ) ;
164- eventSourceRef . current = eventSource ;
165-
166- // Handle message updates
167- eventSource . addEventListener ( "message_update" , ( event ) => {
168- const data : MessageUpdateEvent = JSON . parse ( event . data ) ;
169-
170- setMessages ( ( prevMessages ) => {
171- // Clean up draft messages
172- const updatedMessages = [ ...prevMessages ] . filter (
173- ( m ) => ! isDraftMessage ( m )
174- ) ;
175-
176- // Check if message with this ID already exists
177- const existingIndex = updatedMessages . findIndex (
178- ( m ) => m . id === data . id
179- ) ;
180-
181- if ( existingIndex !== - 1 ) {
182- // Update existing message
183- updatedMessages [ existingIndex ] = {
184- role : data . role ,
185- content : data . message ,
186- id : data . id ,
187- } ;
188- return updatedMessages ;
189- } else {
190- // Add new message
191- return [
192- ...updatedMessages ,
193- {
194- role : data . role ,
195- content : data . message ,
196- id : data . id ,
197- } ,
198- ] ;
199- }
200- } ) ;
201- } ) ;
175+ if ( existingIndex !== - 1 ) {
176+ // Update in place without copying the whole array prefix/suffix.
177+ const updated = [ ...prevMessages ] ;
178+ updated [ existingIndex ] = confirmed ;
179+ return updated ;
180+ }
202181
203- // Handle status changes
204- eventSource . addEventListener ( "status_change" , ( event ) => {
205- const data : StatusChangeEvent = JSON . parse ( event . data ) ;
206- if ( data . status === "stable" ) {
207- setServerStatus ( "stable" ) ;
208- } else if ( data . status === "running" ) {
209- setServerStatus ( "running" ) ;
210- } else {
211- setServerStatus ( "unknown" ) ;
182+ // New confirmed message: replace any trailing draft that matches
183+ // the same role (the optimistic message we inserted on send).
184+ const last = prevMessages [ prevMessages . length - 1 ] ;
185+ if ( last && isDraftMessage ( last ) && last . role === confirmed . role ) {
186+ return [ ...prevMessages . slice ( 0 , - 1 ) , confirmed ] ;
212187 }
213188
214- // Set agent type
215- setAgentType ( data . agent_type === "" ? "unknown" : data . agent_type as AgentType ) ;
189+ return [ ...prevMessages , confirmed ] ;
216190 } ) ;
191+ } ) ;
192+
193+ // Handle status changes
194+ eventSource . addEventListener ( "status_change" , ( event ) => {
195+ const data : StatusChangeEvent = JSON . parse ( event . data ) ;
196+ if ( data . status === "stable" ) {
197+ setServerStatus ( "stable" ) ;
198+ } else if ( data . status === "running" ) {
199+ setServerStatus ( "running" ) ;
200+ } else {
201+ setServerStatus ( "unknown" ) ;
202+ }
203+ setAgentType ( data . agent_type === "" ? "unknown" : data . agent_type as AgentType ) ;
204+ } ) ;
217205
218- // Handle connection open (server is online)
219- eventSource . onopen = ( ) => {
220- // Connection is established, but we'll wait for status_change event
221- // for the actual server status
222- console . log ( "EventSource connection established - messages reset" ) ;
223- } ;
224-
225- // Handle connection errors
226- eventSource . onerror = ( error ) => {
227- console . error ( "EventSource error:" , error ) ;
228- setServerStatus ( "offline" ) ;
229-
230- // Try to reconnect after delay
231- setTimeout ( ( ) => {
232- if ( eventSourceRef . current ) {
233- setupEventSource ( ) ;
234- }
235- } , 3000 ) ;
236- } ;
237-
238- return eventSource ;
206+ eventSource . onopen = ( ) => {
207+ console . log ( "EventSource connection established" ) ;
239208 } ;
240209
241- // Initial setup
242- const eventSource = setupEventSource ( ) ;
210+ // Mark offline on error. The browser will retry automatically.
211+ eventSource . onerror = ( ) => {
212+ setServerStatus ( "offline" ) ;
213+ } ;
243214
244- // Clean up on component unmount
245215 return ( ) => {
246- if ( eventSource ) {
247- // Check if eventSource was successfully created
248- eventSource . close ( ) ;
249- }
216+ eventSource . close ( ) ;
217+ eventSourceRef . current = null ;
250218 } ;
251219 } , [ agentAPIUrl ] ) ;
252220
@@ -304,9 +272,6 @@ export function ChatProvider({ children }: PropsWithChildren) {
304272 } ) ;
305273 } finally {
306274 if ( type === "user" ) {
307- setMessages ( ( prevMessages ) =>
308- prevMessages . filter ( ( m ) => ! isDraftMessage ( m ) )
309- ) ;
310275 setLoading ( false ) ;
311276 }
312277 }
0 commit comments