1
1
import { useCallback , useEffect , useId , useRef , useState } from 'react' ;
2
2
import useSWR , { KeyedMutator } from 'swr' ;
3
- import { nanoid , createChunkDecoder , COMPLEX_HEADER } from '../shared/utils' ;
3
+ import { nanoid } from '../shared/utils' ;
4
4
5
5
import type {
6
6
ChatRequest ,
7
+ ChatRequestOptions ,
7
8
CreateMessage ,
9
+ JSONValue ,
8
10
Message ,
9
11
UseChatOptions ,
10
- ChatRequestOptions ,
11
- FunctionCall ,
12
12
} from '../shared/types' ;
13
- import { parseComplexResponse } from './parse-complex-response' ;
14
13
14
+ import { callApi } from '../shared/call-api' ;
15
+ import { processChatStream } from '../shared/process-chat-stream' ;
15
16
import type {
16
17
ReactResponseRow ,
17
18
experimental_StreamingReactResponse ,
18
19
} from '../streams/streaming-react-response' ;
19
- export type { Message , CreateMessage , UseChatOptions } ;
20
+ export type { CreateMessage , Message , UseChatOptions } ;
20
21
21
22
export type UseChatHelpers = {
22
23
/** Current messages in the chat */
@@ -70,7 +71,7 @@ export type UseChatHelpers = {
70
71
/** Whether the API request is in progress */
71
72
isLoading : boolean ;
72
73
/** Additional data added on the server via StreamData */
73
- data ?: any ;
74
+ data ?: JSONValue [ ] | undefined ;
74
75
} ;
75
76
76
77
type StreamingReactResponseAction = ( payload : {
@@ -82,8 +83,8 @@ const getStreamedResponse = async (
82
83
api : string | StreamingReactResponseAction ,
83
84
chatRequest : ChatRequest ,
84
85
mutate : KeyedMutator < Message [ ] > ,
85
- mutateStreamData : KeyedMutator < any [ ] > ,
86
- existingData : any ,
86
+ mutateStreamData : KeyedMutator < JSONValue [ ] | undefined > ,
87
+ existingData : JSONValue [ ] | undefined ,
87
88
extraMetadataRef : React . MutableRefObject < any > ,
88
89
messagesRef : React . MutableRefObject < Message [ ] > ,
89
90
abortControllerRef : React . MutableRefObject < AbortController | null > ,
@@ -152,10 +153,10 @@ const getStreamedResponse = async (
152
153
return responseMessage ;
153
154
}
154
155
155
- const res = await fetch ( api , {
156
- method : 'POST' ,
157
- body : JSON . stringify ( {
158
- messages : constructedMessagesPayload ,
156
+ return await callApi ( {
157
+ api ,
158
+ messages : constructedMessagesPayload ,
159
+ body : {
159
160
data : chatRequest . data ,
160
161
...extraMetadataRef . current . body ,
161
162
...chatRequest . options ?. body ,
@@ -165,111 +166,26 @@ const getStreamedResponse = async (
165
166
...( chatRequest . function_call !== undefined && {
166
167
function_call : chatRequest . function_call ,
167
168
} ) ,
168
- } ) ,
169
+ } ,
169
170
credentials : extraMetadataRef . current . credentials ,
170
171
headers : {
171
172
...extraMetadataRef . current . headers ,
172
173
...chatRequest . options ?. headers ,
173
174
} ,
174
- ...( abortControllerRef . current !== null && {
175
- signal : abortControllerRef . current . signal ,
176
- } ) ,
177
- } ) . catch ( err => {
178
- // Restore the previous messages if the request fails.
179
- mutate ( previousMessages , false ) ;
180
- throw err ;
175
+ abortController : ( ) => abortControllerRef . current ,
176
+ appendMessage ( message ) {
177
+ mutate ( [ ...chatRequest . messages , message ] , false ) ;
178
+ } ,
179
+ restoreMessagesOnFailure ( ) {
180
+ mutate ( previousMessages , false ) ;
181
+ } ,
182
+ onResponse,
183
+ onUpdate ( merged , data ) {
184
+ mutate ( [ ...chatRequest . messages , ...merged ] , false ) ;
185
+ mutateStreamData ( [ ...( existingData || [ ] ) , ...( data || [ ] ) ] , false ) ;
186
+ } ,
187
+ onFinish,
181
188
} ) ;
182
-
183
- if ( onResponse ) {
184
- try {
185
- await onResponse ( res ) ;
186
- } catch ( err ) {
187
- throw err ;
188
- }
189
- }
190
-
191
- if ( ! res . ok ) {
192
- // Restore the previous messages if the request fails.
193
- mutate ( previousMessages , false ) ;
194
- throw new Error ( ( await res . text ( ) ) || 'Failed to fetch the chat response.' ) ;
195
- }
196
-
197
- if ( ! res . body ) {
198
- throw new Error ( 'The response body is empty.' ) ;
199
- }
200
-
201
- const isComplexMode = res . headers . get ( COMPLEX_HEADER ) === 'true' ;
202
- const reader = res . body . getReader ( ) ;
203
-
204
- if ( isComplexMode ) {
205
- return await parseComplexResponse ( {
206
- reader,
207
- abortControllerRef,
208
- update ( merged , data ) {
209
- mutate ( [ ...chatRequest . messages , ...merged ] , false ) ;
210
- mutateStreamData ( [ ...( existingData || [ ] ) , ...( data || [ ] ) ] , false ) ;
211
- } ,
212
- onFinish ( prefixMap ) {
213
- if ( onFinish && prefixMap . text != null ) {
214
- onFinish ( prefixMap . text ) ;
215
- }
216
- } ,
217
- } ) ;
218
- } else {
219
- const createdAt = new Date ( ) ;
220
- const decode = createChunkDecoder ( false ) ;
221
-
222
- // TODO-STREAMDATA: Remove this once Strem Data is not experimental
223
- let streamedResponse = '' ;
224
- const replyId = nanoid ( ) ;
225
- let responseMessage : Message = {
226
- id : replyId ,
227
- createdAt,
228
- content : '' ,
229
- role : 'assistant' ,
230
- } ;
231
-
232
- // TODO-STREAMDATA: Remove this once Strem Data is not experimental
233
- while ( true ) {
234
- const { done, value } = await reader . read ( ) ;
235
- if ( done ) {
236
- break ;
237
- }
238
- // Update the chat state with the new message tokens.
239
- streamedResponse += decode ( value ) ;
240
-
241
- if ( streamedResponse . startsWith ( '{"function_call":' ) ) {
242
- // While the function call is streaming, it will be a string.
243
- responseMessage [ 'function_call' ] = streamedResponse ;
244
- } else {
245
- responseMessage [ 'content' ] = streamedResponse ;
246
- }
247
-
248
- mutate ( [ ...chatRequest . messages , { ...responseMessage } ] , false ) ;
249
-
250
- // The request has been aborted, stop reading the stream.
251
- if ( abortControllerRef . current === null ) {
252
- reader . cancel ( ) ;
253
- break ;
254
- }
255
- }
256
-
257
- if ( streamedResponse . startsWith ( '{"function_call":' ) ) {
258
- // Once the stream is complete, the function call is parsed into an object.
259
- const parsedFunctionCall : FunctionCall =
260
- JSON . parse ( streamedResponse ) . function_call ;
261
-
262
- responseMessage [ 'function_call' ] = parsedFunctionCall ;
263
-
264
- mutate ( [ ...chatRequest . messages , { ...responseMessage } ] ) ;
265
- }
266
-
267
- if ( onFinish ) {
268
- onFinish ( responseMessage ) ;
269
- }
270
-
271
- return responseMessage ;
272
- }
273
189
} ;
274
190
275
191
export function useChat ( {
@@ -308,10 +224,9 @@ export function useChat({
308
224
null ,
309
225
) ;
310
226
311
- const { data : streamData , mutate : mutateStreamData } = useSWR < any > (
312
- [ chatId , 'streamData' ] ,
313
- null ,
314
- ) ;
227
+ const { data : streamData , mutate : mutateStreamData } = useSWR <
228
+ JSONValue [ ] | undefined
229
+ > ( [ chatId , 'streamData' ] , null ) ;
315
230
316
231
// Keep the latest messages in a ref.
317
232
const messagesRef = useRef < Message [ ] > ( messages || [ ] ) ;
@@ -348,89 +263,27 @@ export function useChat({
348
263
const abortController = new AbortController ( ) ;
349
264
abortControllerRef . current = abortController ;
350
265
351
- while ( true ) {
352
- // TODO-STREAMDATA: This should be { const { messages: streamedResponseMessages, data } =
353
- // await getStreamedResponse( } once Stream Data is not experimental
354
- const messagesAndDataOrJustMessage = await getStreamedResponse (
355
- api ,
356
- chatRequest ,
357
- mutate ,
358
- mutateStreamData ,
359
- streamData ,
360
- extraMetadataRef ,
361
- messagesRef ,
362
- abortControllerRef ,
363
- onFinish ,
364
- onResponse ,
365
- sendExtraMessageFields ,
366
- ) ;
367
-
368
- // Using experimental stream data
369
- if ( 'messages' in messagesAndDataOrJustMessage ) {
370
- let hasFollowingResponse = false ;
371
- for ( const message of messagesAndDataOrJustMessage . messages ) {
372
- if (
373
- message . function_call === undefined ||
374
- typeof message . function_call === 'string'
375
- ) {
376
- continue ;
377
- }
378
- hasFollowingResponse = true ;
379
- // Streamed response is a function call, invoke the function call handler if it exists.
380
- if ( experimental_onFunctionCall ) {
381
- const functionCall = message . function_call ;
382
-
383
- // User handles the function call in their own functionCallHandler.
384
- // The "arguments" key of the function call object will still be a string which will have to be parsed in the function handler.
385
- // If the "arguments" JSON is malformed due to model error the user will have to handle that themselves.
386
-
387
- const functionCallResponse : ChatRequest | void =
388
- await experimental_onFunctionCall (
389
- messagesRef . current ,
390
- functionCall ,
391
- ) ;
392
-
393
- // If the user does not return anything as a result of the function call, the loop will break.
394
- if ( functionCallResponse === undefined ) {
395
- hasFollowingResponse = false ;
396
- break ;
397
- }
398
-
399
- // A function call response was returned.
400
- // The updated chat with function call response will be sent to the API in the next iteration of the loop.
401
- chatRequest = functionCallResponse ;
402
- }
403
- }
404
- if ( ! hasFollowingResponse ) {
405
- break ;
406
- }
407
- } else {
408
- const streamedResponseMessage = messagesAndDataOrJustMessage ;
409
- // TODO-STREAMDATA: Remove this once Stream Data is not experimental
410
- if (
411
- streamedResponseMessage . function_call === undefined ||
412
- typeof streamedResponseMessage . function_call === 'string'
413
- ) {
414
- break ;
415
- }
416
-
417
- // Streamed response is a function call, invoke the function call handler if it exists.
418
- if ( experimental_onFunctionCall ) {
419
- const functionCall = streamedResponseMessage . function_call ;
420
- const functionCallResponse : ChatRequest | void =
421
- await experimental_onFunctionCall (
422
- messagesRef . current ,
423
- functionCall ,
424
- ) ;
425
-
426
- // If the user does not return anything as a result of the function call, the loop will break.
427
- if ( functionCallResponse === undefined ) break ;
428
- // A function call response was returned.
429
- // The updated chat with function call response will be sent to the API in the next iteration of the loop.
430
- chatRequest = functionCallResponse ;
431
- }
432
- }
433
- }
266
+ await processChatStream ( {
267
+ getStreamedResponse : ( ) =>
268
+ getStreamedResponse (
269
+ api ,
270
+ chatRequest ,
271
+ mutate ,
272
+ mutateStreamData ,
273
+ streamData ! ,
274
+ extraMetadataRef ,
275
+ messagesRef ,
276
+ abortControllerRef ,
277
+ onFinish ,
278
+ onResponse ,
279
+ sendExtraMessageFields ,
280
+ ) ,
281
+ experimental_onFunctionCall,
282
+ updateChatRequest : chatRequestParam => {
283
+ chatRequest = chatRequestParam ;
284
+ } ,
285
+ getCurrentMessages : ( ) => messagesRef . current ,
286
+ } ) ;
434
287
435
288
abortControllerRef . current = null ;
436
289
} catch ( err ) {
0 commit comments