1
- import { v4 } from 'uuid' ;
2
- import { useSetRecoilState } from 'recoil' ;
3
- import { useEffect , useState } from 'react' ;
1
+ import type { EventSubmission , TMessage , TPayload , TSubmission } from 'librechat-data-provider' ;
4
2
import {
5
3
/* @ts -ignore */
6
- SSE ,
7
4
createPayload ,
8
5
isAgentsEndpoint ,
9
- removeNullishValues ,
10
6
isAssistantsEndpoint ,
7
+ removeNullishValues ,
8
+ request ,
11
9
} from 'librechat-data-provider' ;
12
- import { useGetUserBalance , useGetStartupConfig } from 'librechat-data-provider/react-query' ;
13
- import type { TMessage , TSubmission , TPayload , EventSubmission } from 'librechat-data-provider' ;
14
- import type { EventHandlerParams } from './useEventHandlers' ;
10
+ import { useGetStartupConfig , useGetUserBalance } from 'librechat-data-provider/react-query' ;
11
+ import { useEffect , useState } from 'react' ;
12
+ import { useSetRecoilState } from 'recoil' ;
13
+ import { SSE } from 'sse.js' ;
14
+ import { v4 } from 'uuid' ;
15
15
import type { TResData } from '~/common' ;
16
16
import { useGenTitleMutation } from '~/data-provider' ;
17
17
import { useAuthContext } from '~/hooks/AuthContext' ;
18
- import useEventHandlers from './useEventHandlers' ;
19
18
import store from '~/store' ;
19
+ import type { EventHandlerParams } from './useEventHandlers' ;
20
+ import useEventHandlers from './useEventHandlers' ;
20
21
21
22
type ChatHelpers = Pick <
22
23
EventHandlerParams ,
@@ -94,21 +95,21 @@ export default function useSSE(
94
95
95
96
let textIndex = null ;
96
97
97
- const events = new SSE ( payloadData . server , {
98
+ const sse = new SSE ( payloadData . server , {
98
99
payload : JSON . stringify ( payload ) ,
99
100
headers : { 'Content-Type' : 'application/json' , Authorization : `Bearer ${ token } ` } ,
100
101
} ) ;
101
102
102
- events . onattachment = ( e : MessageEvent ) => {
103
+ sse . addEventListener ( 'attachment' , ( e : MessageEvent ) => {
103
104
try {
104
105
const data = JSON . parse ( e . data ) ;
105
106
attachmentHandler ( { data, submission : submission as EventSubmission } ) ;
106
107
} catch ( error ) {
107
108
console . error ( error ) ;
108
109
}
109
- } ;
110
+ } ) ;
110
111
111
- events . onmessage = ( e : MessageEvent ) => {
112
+ sse . addEventListener ( 'message' , ( e : MessageEvent ) => {
112
113
const data = JSON . parse ( e . data ) ;
113
114
114
115
if ( data . final != null ) {
@@ -155,14 +156,14 @@ export default function useSSE(
155
156
messageHandler ( text , { ...submission , plugin, plugins, userMessage, initialResponse } ) ;
156
157
}
157
158
}
158
- } ;
159
+ } ) ;
159
160
160
- events . onopen = ( ) => {
161
+ sse . addEventListener ( 'open' , ( ) => {
161
162
setAbortScroll ( false ) ;
162
163
console . log ( 'connection is opened' ) ;
163
- } ;
164
+ } ) ;
164
165
165
- events . oncancel = async ( ) => {
166
+ sse . addEventListener ( 'cancel' , async ( ) => {
166
167
const streamKey = ( submission as TSubmission | null ) ?. [ 'initialResponse' ] ?. messageId ;
167
168
if ( completed . has ( streamKey ) ) {
168
169
setIsSubmitting ( false ) ;
@@ -181,9 +182,27 @@ export default function useSSE(
181
182
submission as EventSubmission ,
182
183
latestMessages ,
183
184
) ;
184
- } ;
185
+ } ) ;
186
+
187
+ sse . addEventListener ( 'error' , async ( e : MessageEvent ) => {
188
+ /* @ts -ignore */
189
+ if ( e . responseCode === 401 ) {
190
+ /* token expired, refresh and retry */
191
+ try {
192
+ const refreshResponse = await request . refreshToken ( ) ;
193
+ sse . headers = {
194
+ 'Content-Type' : 'application/json' ,
195
+ Authorization : `Bearer ${ refreshResponse . token } ` ,
196
+ } ;
197
+ request . dispatchTokenUpdatedEvent ( refreshResponse . token ) ;
198
+ sse . stream ( ) ;
199
+ return ;
200
+ } catch ( error ) {
201
+ /* token refresh failed, continue handling the original 401 */
202
+ console . log ( error ) ;
203
+ }
204
+ }
185
205
186
- events . onerror = function ( e : MessageEvent ) {
187
206
console . log ( 'error in server stream.' ) ;
188
207
( startupConfig ?. checkBalance ?? false ) && balanceQuery . refetch ( ) ;
189
208
@@ -197,18 +216,18 @@ export default function useSSE(
197
216
}
198
217
199
218
errorHandler ( { data, submission : { ...submission , userMessage } as EventSubmission } ) ;
200
- } ;
219
+ } ) ;
201
220
202
221
setIsSubmitting ( true ) ;
203
- events . stream ( ) ;
222
+ sse . stream ( ) ;
204
223
205
224
return ( ) => {
206
- const isCancelled = events . readyState <= 1 ;
207
- events . close ( ) ;
208
- // setSource(null);
225
+ const isCancelled = sse . readyState <= 1 ;
226
+ sse . close ( ) ;
209
227
if ( isCancelled ) {
210
228
const e = new Event ( 'cancel' ) ;
211
- events . dispatchEvent ( e ) ;
229
+ /* @ts -ignore */
230
+ sse . dispatchEvent ( e ) ;
212
231
}
213
232
} ;
214
233
// eslint-disable-next-line react-hooks/exhaustive-deps
0 commit comments