@@ -97,6 +97,13 @@ export async function isNeedWebsocket(accessToken) {
97
97
)
98
98
}
99
99
100
+ export async function sendWebsocketConversation ( accessToken , options ) {
101
+ const apiUrl = ( await getUserConfig ( ) ) . customChatGptWebApiUrl
102
+ const response = await fetch ( `${ apiUrl } /backend-api/conversation` , options ) . then ( ( r ) => r . json ( ) )
103
+ console . debug ( `request: ws /conversation` , response )
104
+ return { conversationId : response . conversation_id , wsRequestId : response . websocket_request_id }
105
+ }
106
+
100
107
export async function stopWebsocketConversation ( accessToken , conversationId , wsRequestId ) {
101
108
await request ( accessToken , 'POST' , '/stop_conversation' , {
102
109
conversation_id : conversationId ,
@@ -111,11 +118,11 @@ let websocket
111
118
/**
112
119
* @type {Date }
113
120
*/
114
- let expired_at
121
+ let expires_at
115
122
let wsCallbacks = [ ]
116
123
117
124
export async function registerWebsocket ( accessToken ) {
118
- if ( websocket && new Date ( ) < expired_at - 300000 ) return
125
+ if ( websocket && new Date ( ) < expires_at - 300000 ) return
119
126
120
127
const response = JSON . parse (
121
128
( await request ( accessToken , 'POST' , '/register-websocket' ) ) . responseText ,
@@ -124,11 +131,13 @@ export async function registerWebsocket(accessToken) {
124
131
websocket = new WebSocket ( response . wss_url )
125
132
websocket . onclose = ( ) => {
126
133
websocket = null
134
+ expires_at = null
135
+ console . debug ( 'global websocket closed' )
127
136
}
128
137
websocket . onmessage = ( event ) => {
129
138
wsCallbacks . forEach ( ( cb ) => cb ( event ) )
130
139
}
131
- expired_at = new Date ( response . expired_at )
140
+ expires_at = new Date ( response . expires_at )
132
141
}
133
142
}
134
143
@@ -139,15 +148,14 @@ export async function registerWebsocket(accessToken) {
139
148
* @param {string } accessToken
140
149
*/
141
150
export async function generateAnswersWithChatgptWebApi ( port , question , session , accessToken ) {
142
- let ws
143
151
const { controller, cleanController } = setAbortController (
144
152
port ,
145
153
( ) => {
146
- if ( ws ) ws . close ( )
154
+ if ( session . wsRequestId )
155
+ stopWebsocketConversation ( accessToken , session . conversationId , session . wsRequestId )
147
156
} ,
148
157
( ) => {
149
158
if ( session . autoClean ) deleteConversation ( accessToken , session . conversationId )
150
- if ( ws ) ws . close ( )
151
159
} ,
152
160
)
153
161
@@ -184,13 +192,9 @@ export async function generateAnswersWithChatgptWebApi(port, question, session,
184
192
) . value
185
193
}
186
194
187
- let answer = ''
188
- let generationPrefixAnswer = ''
189
- let generatedImageUrl = ''
190
- let wss_url = ''
191
-
192
195
const url = `${ config . customChatGptWebApiUrl } ${ config . customChatGptWebApiPath } `
193
196
session . messageId = uuidv4 ( )
197
+ session . wsRequestId = uuidv4 ( )
194
198
if ( session . parentMessageId == null ) {
195
199
session . parentMessageId = uuidv4 ( )
196
200
}
@@ -232,141 +236,139 @@ export async function generateAnswersWithChatgptWebApi(port, question, session,
232
236
parent_message_id : session . parentMessageId ,
233
237
timezone_offset_min : new Date ( ) . getTimezoneOffset ( ) ,
234
238
history_and_training_disabled : config . disableWebModeHistory ,
239
+ websocket_request_id : session . wsRequestId ,
235
240
} ) ,
236
241
}
237
- await fetchSSE ( url , {
238
- ...options ,
239
- onMessage ( message ) {
240
- function handleMessage ( data ) {
241
- if ( data . error ) {
242
- throw new Error ( data . error )
243
- }
244
242
245
- if ( data . conversation_id ) session . conversationId = data . conversation_id
246
- if ( data . message ?. id ) session . parentMessageId = data . message . id
247
-
248
- const respAns = data . message ?. content ?. parts ?. [ 0 ]
249
- const contentType = data . message ?. content ?. content_type
250
- if ( contentType === 'text' && respAns ) {
251
- answer =
252
- generationPrefixAnswer +
253
- ( generatedImageUrl && `\n\n\n\n` ) +
254
- respAns
255
- } else if ( contentType === 'code' && data . message ?. status === 'in_progress' ) {
256
- const generationText = '\n\n' + t ( 'Generating...' )
257
- if ( answer && ! answer . endsWith ( generationText ) ) generationPrefixAnswer = answer
258
- answer = generationPrefixAnswer + generationText
259
- } else if (
260
- contentType === 'multimodal_text' &&
261
- respAns ?. content_type === 'image_asset_pointer'
262
- ) {
263
- const imageAsset = respAns ?. asset_pointer || ''
264
- if ( imageAsset ) {
265
- fetch (
266
- `${ config . customChatGptWebApiUrl } /backend-api/files/${ imageAsset . replace (
267
- 'file-service://' ,
268
- '' ,
269
- ) } /download`,
270
- {
271
- credentials : 'include' ,
272
- headers : {
273
- Authorization : `Bearer ${ accessToken } ` ,
274
- ...( cookie && { Cookie : cookie } ) ,
275
- } ,
276
- } ,
277
- ) . then ( ( r ) => r . json ( ) . then ( ( json ) => ( generatedImageUrl = json ?. download_url ) ) )
278
- }
279
- }
280
-
281
- if ( answer ) {
282
- port . postMessage ( { answer : answer , done : false , session : null } )
283
- }
284
- }
285
-
286
- function finishMessage ( ) {
287
- pushRecord ( session , question , answer )
288
- console . debug ( 'conversation history' , { content : session . conversationRecords } )
289
- port . postMessage ( { answer : answer , done : true , session : session } )
290
- }
243
+ let answer = ''
244
+ let generationPrefixAnswer = ''
245
+ let generatedImageUrl = ''
291
246
292
- console . debug ( 'sse message' , message )
293
- if ( message . trim ( ) === '[DONE]' ) {
294
- if ( ! wss_url ) {
295
- finishMessage ( )
296
- } else {
297
- ws = new WebSocket ( wss_url )
298
- ws . onmessage = ( event ) => {
299
- let wsData
300
- try {
301
- wsData = JSON . parse ( event . data )
302
- } catch ( error ) {
303
- console . debug ( 'json error' , error )
304
- return
305
- }
306
- if ( wsData . type === 'http.response.body' ) {
307
- let body
308
- try {
309
- body = atob ( wsData . body ) . replace ( / ^ d a t a : / , '' )
310
- const data = JSON . parse ( body )
311
- console . debug ( 'ws message' , data )
312
- if ( wsData . conversation_id === session . conversationId ) {
313
- handleMessage ( data )
314
- }
315
- } catch ( error ) {
316
- if ( body && body . trim ( ) === '[DONE]' ) {
317
- console . debug ( 'ws message' , '[DONE]' )
318
- if ( wsData . conversation_id === session . conversationId ) {
319
- finishMessage ( )
320
- ws . close ( )
321
- }
322
- } else {
323
- console . debug ( 'json error' , error )
324
- }
325
- }
326
- }
327
- }
328
- ws . onopen = ( ) => {
329
- // fetch(url, options)
330
- }
331
- ws . onclose = ( ) => {
332
- port . postMessage ( { done : true } )
333
- cleanController ( )
334
- }
335
- ws . onerror = ( event ) => {
336
- console . debug ( 'ws error' , event )
337
- port . postMessage ( { error : event } )
338
- cleanController ( )
339
- }
340
- }
341
- return
342
- }
343
- let data
247
+ if ( useWebsocket ) {
248
+ await registerWebsocket ( accessToken )
249
+ const wsCallback = async ( event ) => {
250
+ let wsData
344
251
try {
345
- data = JSON . parse ( message )
252
+ wsData = JSON . parse ( event . data )
346
253
} catch ( error ) {
347
254
console . debug ( 'json error' , error )
348
255
return
349
256
}
350
- if ( data . wss_url ) wss_url = data . wss_url
351
- handleMessage ( data )
352
- } ,
353
- async onStart ( ) {
354
- // sendModerations(accessToken, question, session.conversationId, session.messageId)
355
- } ,
356
- async onEnd ( ) {
357
- if ( ! wss_url ) {
257
+ if ( wsData . type === 'http.response.body' ) {
258
+ let body
259
+ try {
260
+ body = atob ( wsData . body ) . replace ( / ^ d a t a : / , '' )
261
+ const data = JSON . parse ( body )
262
+ console . debug ( 'ws message' , data )
263
+ if ( wsData . conversation_id === session . conversationId ) {
264
+ handleMessage ( data )
265
+ }
266
+ } catch ( error ) {
267
+ if ( body && body . trim ( ) === '[DONE]' ) {
268
+ console . debug ( 'ws message' , '[DONE]' )
269
+ if ( wsData . conversation_id === session . conversationId ) {
270
+ finishMessage ( )
271
+ wsCallbacks = wsCallbacks . filter ( ( cb ) => cb !== wsCallback )
272
+ }
273
+ } else {
274
+ console . debug ( 'json error' , error )
275
+ }
276
+ }
277
+ }
278
+ }
279
+ wsCallbacks . push ( wsCallback )
280
+ const { conversationId, wsRequestId } = await sendWebsocketConversation ( accessToken , options )
281
+ session . conversationId = conversationId
282
+ session . wsRequestId = wsRequestId
283
+ port . postMessage ( { session : session } )
284
+ } else {
285
+ await fetchSSE ( url , {
286
+ ...options ,
287
+ onMessage ( message ) {
288
+ console . debug ( 'sse message' , message )
289
+ if ( message . trim ( ) === '[DONE]' ) {
290
+ finishMessage ( )
291
+ return
292
+ }
293
+ let data
294
+ try {
295
+ data = JSON . parse ( message )
296
+ } catch ( error ) {
297
+ console . debug ( 'json error' , error )
298
+ return
299
+ }
300
+ handleMessage ( data )
301
+ } ,
302
+ async onStart ( ) {
303
+ // sendModerations(accessToken, question, session.conversationId, session.messageId)
304
+ } ,
305
+ async onEnd ( ) {
358
306
port . postMessage ( { done : true } )
359
307
cleanController ( )
308
+ } ,
309
+ async onError ( resp ) {
310
+ cleanController ( )
311
+ if ( resp instanceof Error ) throw resp
312
+ if ( resp . status === 403 ) {
313
+ throw new Error ( 'CLOUDFLARE' )
314
+ }
315
+ const error = await resp . json ( ) . catch ( ( ) => ( { } ) )
316
+ throw new Error (
317
+ ! isEmpty ( error ) ? JSON . stringify ( error ) : `${ resp . status } ${ resp . statusText } ` ,
318
+ )
319
+ } ,
320
+ } )
321
+ }
322
+
323
+ function handleMessage ( data ) {
324
+ if ( data . error ) {
325
+ throw new Error ( data . error )
326
+ }
327
+
328
+ if ( data . conversation_id ) session . conversationId = data . conversation_id
329
+ if ( data . message ?. id ) session . parentMessageId = data . message . id
330
+
331
+ const respAns = data . message ?. content ?. parts ?. [ 0 ]
332
+ const contentType = data . message ?. content ?. content_type
333
+ if ( contentType === 'text' && respAns ) {
334
+ answer =
335
+ generationPrefixAnswer +
336
+ ( generatedImageUrl && `\n\n\n\n` ) +
337
+ respAns
338
+ } else if ( contentType === 'code' && data . message ?. status === 'in_progress' ) {
339
+ const generationText = '\n\n' + t ( 'Generating...' )
340
+ if ( answer && ! answer . endsWith ( generationText ) ) generationPrefixAnswer = answer
341
+ answer = generationPrefixAnswer + generationText
342
+ } else if (
343
+ contentType === 'multimodal_text' &&
344
+ respAns ?. content_type === 'image_asset_pointer'
345
+ ) {
346
+ const imageAsset = respAns ?. asset_pointer || ''
347
+ if ( imageAsset ) {
348
+ fetch (
349
+ `${ config . customChatGptWebApiUrl } /backend-api/files/${ imageAsset . replace (
350
+ 'file-service://' ,
351
+ '' ,
352
+ ) } /download`,
353
+ {
354
+ credentials : 'include' ,
355
+ headers : {
356
+ Authorization : `Bearer ${ accessToken } ` ,
357
+ ...( cookie && { Cookie : cookie } ) ,
358
+ } ,
359
+ } ,
360
+ ) . then ( ( r ) => r . json ( ) . then ( ( json ) => ( generatedImageUrl = json ?. download_url ) ) )
360
361
}
361
- } ,
362
- async onError ( resp ) {
363
- cleanController ( )
364
- if ( resp instanceof Error ) throw resp
365
- if ( resp . status === 403 ) {
366
- throw new Error ( 'CLOUDFLARE' )
367
- }
368
- const error = await resp . json ( ) . catch ( ( ) => ( { } ) )
369
- throw new Error ( ! isEmpty ( error ) ? JSON . stringify ( error ) : `${ resp . status } ${ resp . statusText } ` )
370
- } ,
371
- } )
362
+ }
363
+
364
+ if ( answer ) {
365
+ port . postMessage ( { answer : answer , done : false , session : null } )
366
+ }
367
+ }
368
+
369
+ function finishMessage ( ) {
370
+ pushRecord ( session , question , answer )
371
+ console . debug ( 'conversation history' , { content : session . conversationRecords } )
372
+ port . postMessage ( { answer : answer , done : true , session : session } )
373
+ }
372
374
}
0 commit comments