@ai-sdk/react 0.0.52 → 0.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
- package/.eslintrc.js +0 -4
- package/.turbo/turbo-build.log +0 -21
- package/.turbo/turbo-clean.log +0 -4
- package/src/index.ts +0 -4
- package/src/use-assistant.ts +0 -301
- package/src/use-assistant.ui.test.tsx +0 -337
- package/src/use-chat.ts +0 -716
- package/src/use-chat.ui.test.tsx +0 -1542
- package/src/use-completion.ts +0 -213
- package/src/use-completion.ui.test.tsx +0 -166
- package/src/use-object.ts +0 -229
- package/src/use-object.ui.test.tsx +0 -218
- package/tsconfig.json +0 -9
- package/tsup.config.ts +0 -13
- package/turbo.json +0 -8
- package/vitest.config.js +0 -12
package/src/use-chat.ts
DELETED
|
@@ -1,716 +0,0 @@
|
|
|
1
|
-
import { FetchFunction } from '@ai-sdk/provider-utils';
|
|
2
|
-
import type {
|
|
3
|
-
ChatRequest,
|
|
4
|
-
ChatRequestOptions,
|
|
5
|
-
Attachment,
|
|
6
|
-
CreateMessage,
|
|
7
|
-
IdGenerator,
|
|
8
|
-
JSONValue,
|
|
9
|
-
Message,
|
|
10
|
-
UseChatOptions,
|
|
11
|
-
} from '@ai-sdk/ui-utils';
|
|
12
|
-
import {
|
|
13
|
-
callChatApi,
|
|
14
|
-
generateId as generateIdFunc,
|
|
15
|
-
processChatStream,
|
|
16
|
-
} from '@ai-sdk/ui-utils';
|
|
17
|
-
import { useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
18
|
-
import useSWR, { KeyedMutator } from 'swr';
|
|
19
|
-
|
|
20
|
-
export type { CreateMessage, Message, UseChatOptions };
|
|
21
|
-
|
|
22
|
-
export type UseChatHelpers = {
|
|
23
|
-
/** Current messages in the chat */
|
|
24
|
-
messages: Message[];
|
|
25
|
-
/** The error object of the API request */
|
|
26
|
-
error: undefined | Error;
|
|
27
|
-
/**
|
|
28
|
-
* Append a user message to the chat list. This triggers the API call to fetch
|
|
29
|
-
* the assistant's response.
|
|
30
|
-
* @param message The message to append
|
|
31
|
-
* @param options Additional options to pass to the API call
|
|
32
|
-
*/
|
|
33
|
-
append: (
|
|
34
|
-
message: Message | CreateMessage,
|
|
35
|
-
chatRequestOptions?: ChatRequestOptions,
|
|
36
|
-
) => Promise<string | null | undefined>;
|
|
37
|
-
/**
|
|
38
|
-
* Reload the last AI chat response for the given chat history. If the last
|
|
39
|
-
* message isn't from the assistant, it will request the API to generate a
|
|
40
|
-
* new response.
|
|
41
|
-
*/
|
|
42
|
-
reload: (
|
|
43
|
-
chatRequestOptions?: ChatRequestOptions,
|
|
44
|
-
) => Promise<string | null | undefined>;
|
|
45
|
-
/**
|
|
46
|
-
* Abort the current request immediately, keep the generated tokens if any.
|
|
47
|
-
*/
|
|
48
|
-
stop: () => void;
|
|
49
|
-
/**
|
|
50
|
-
* Update the `messages` state locally. This is useful when you want to
|
|
51
|
-
* edit the messages on the client, and then trigger the `reload` method
|
|
52
|
-
* manually to regenerate the AI response.
|
|
53
|
-
*/
|
|
54
|
-
setMessages: (
|
|
55
|
-
messages: Message[] | ((messages: Message[]) => Message[]),
|
|
56
|
-
) => void;
|
|
57
|
-
/** The current value of the input */
|
|
58
|
-
input: string;
|
|
59
|
-
/** setState-powered method to update the input value */
|
|
60
|
-
setInput: React.Dispatch<React.SetStateAction<string>>;
|
|
61
|
-
/** An input/textarea-ready onChange handler to control the value of the input */
|
|
62
|
-
handleInputChange: (
|
|
63
|
-
e:
|
|
64
|
-
| React.ChangeEvent<HTMLInputElement>
|
|
65
|
-
| React.ChangeEvent<HTMLTextAreaElement>,
|
|
66
|
-
) => void;
|
|
67
|
-
/** Form submission handler to automatically reset input and append a user message */
|
|
68
|
-
handleSubmit: (
|
|
69
|
-
event?: { preventDefault?: () => void },
|
|
70
|
-
chatRequestOptions?: ChatRequestOptions,
|
|
71
|
-
) => void;
|
|
72
|
-
metadata?: Object;
|
|
73
|
-
/** Whether the API request is in progress */
|
|
74
|
-
isLoading: boolean;
|
|
75
|
-
/** Additional data added on the server via StreamData */
|
|
76
|
-
data?: JSONValue[];
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const getStreamedResponse = async (
|
|
80
|
-
api: string,
|
|
81
|
-
chatRequest: ChatRequest,
|
|
82
|
-
mutate: KeyedMutator<Message[]>,
|
|
83
|
-
mutateStreamData: KeyedMutator<JSONValue[] | undefined>,
|
|
84
|
-
existingData: JSONValue[] | undefined,
|
|
85
|
-
extraMetadataRef: React.MutableRefObject<any>,
|
|
86
|
-
messagesRef: React.MutableRefObject<Message[]>,
|
|
87
|
-
abortControllerRef: React.MutableRefObject<AbortController | null>,
|
|
88
|
-
generateId: IdGenerator,
|
|
89
|
-
streamProtocol: UseChatOptions['streamProtocol'],
|
|
90
|
-
onFinish: UseChatOptions['onFinish'],
|
|
91
|
-
onResponse: ((response: Response) => void | Promise<void>) | undefined,
|
|
92
|
-
onToolCall: UseChatOptions['onToolCall'] | undefined,
|
|
93
|
-
sendExtraMessageFields: boolean | undefined,
|
|
94
|
-
experimental_prepareRequestBody:
|
|
95
|
-
| ((options: {
|
|
96
|
-
messages: Message[];
|
|
97
|
-
requestData?: JSONValue;
|
|
98
|
-
requestBody?: object;
|
|
99
|
-
}) => JSONValue)
|
|
100
|
-
| undefined,
|
|
101
|
-
fetch: FetchFunction | undefined,
|
|
102
|
-
keepLastMessageOnError: boolean,
|
|
103
|
-
) => {
|
|
104
|
-
// Do an optimistic update to the chat state to show the updated messages immediately:
|
|
105
|
-
const previousMessages = messagesRef.current;
|
|
106
|
-
mutate(chatRequest.messages, false);
|
|
107
|
-
|
|
108
|
-
const constructedMessagesPayload = sendExtraMessageFields
|
|
109
|
-
? chatRequest.messages
|
|
110
|
-
: chatRequest.messages.map(
|
|
111
|
-
({
|
|
112
|
-
role,
|
|
113
|
-
content,
|
|
114
|
-
experimental_attachments,
|
|
115
|
-
name,
|
|
116
|
-
data,
|
|
117
|
-
annotations,
|
|
118
|
-
toolInvocations,
|
|
119
|
-
function_call,
|
|
120
|
-
tool_calls,
|
|
121
|
-
tool_call_id,
|
|
122
|
-
}) => ({
|
|
123
|
-
role,
|
|
124
|
-
content,
|
|
125
|
-
...(experimental_attachments !== undefined && {
|
|
126
|
-
experimental_attachments,
|
|
127
|
-
}),
|
|
128
|
-
...(name !== undefined && { name }),
|
|
129
|
-
...(data !== undefined && { data }),
|
|
130
|
-
...(annotations !== undefined && { annotations }),
|
|
131
|
-
...(toolInvocations !== undefined && { toolInvocations }),
|
|
132
|
-
// outdated function/tool call handling (TODO deprecate):
|
|
133
|
-
tool_call_id,
|
|
134
|
-
...(function_call !== undefined && { function_call }),
|
|
135
|
-
...(tool_calls !== undefined && { tool_calls }),
|
|
136
|
-
}),
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
return await callChatApi({
|
|
140
|
-
api,
|
|
141
|
-
body: experimental_prepareRequestBody?.({
|
|
142
|
-
messages: chatRequest.messages,
|
|
143
|
-
requestData: chatRequest.data,
|
|
144
|
-
requestBody: chatRequest.body,
|
|
145
|
-
}) ?? {
|
|
146
|
-
messages: constructedMessagesPayload,
|
|
147
|
-
data: chatRequest.data,
|
|
148
|
-
...extraMetadataRef.current.body,
|
|
149
|
-
...chatRequest.body,
|
|
150
|
-
...(chatRequest.functions !== undefined && {
|
|
151
|
-
functions: chatRequest.functions,
|
|
152
|
-
}),
|
|
153
|
-
...(chatRequest.function_call !== undefined && {
|
|
154
|
-
function_call: chatRequest.function_call,
|
|
155
|
-
}),
|
|
156
|
-
...(chatRequest.tools !== undefined && {
|
|
157
|
-
tools: chatRequest.tools,
|
|
158
|
-
}),
|
|
159
|
-
...(chatRequest.tool_choice !== undefined && {
|
|
160
|
-
tool_choice: chatRequest.tool_choice,
|
|
161
|
-
}),
|
|
162
|
-
},
|
|
163
|
-
streamProtocol,
|
|
164
|
-
credentials: extraMetadataRef.current.credentials,
|
|
165
|
-
headers: {
|
|
166
|
-
...extraMetadataRef.current.headers,
|
|
167
|
-
...chatRequest.headers,
|
|
168
|
-
},
|
|
169
|
-
abortController: () => abortControllerRef.current,
|
|
170
|
-
restoreMessagesOnFailure() {
|
|
171
|
-
if (!keepLastMessageOnError) {
|
|
172
|
-
mutate(previousMessages, false);
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
onResponse,
|
|
176
|
-
onUpdate(merged, data) {
|
|
177
|
-
mutate([...chatRequest.messages, ...merged], false);
|
|
178
|
-
mutateStreamData([...(existingData || []), ...(data || [])], false);
|
|
179
|
-
},
|
|
180
|
-
onToolCall,
|
|
181
|
-
onFinish,
|
|
182
|
-
generateId,
|
|
183
|
-
fetch,
|
|
184
|
-
});
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
export function useChat({
|
|
188
|
-
api = '/api/chat',
|
|
189
|
-
id,
|
|
190
|
-
initialMessages,
|
|
191
|
-
initialInput = '',
|
|
192
|
-
sendExtraMessageFields,
|
|
193
|
-
experimental_onFunctionCall,
|
|
194
|
-
experimental_onToolCall,
|
|
195
|
-
onToolCall,
|
|
196
|
-
experimental_prepareRequestBody,
|
|
197
|
-
experimental_maxAutomaticRoundtrips = 0,
|
|
198
|
-
maxAutomaticRoundtrips = experimental_maxAutomaticRoundtrips,
|
|
199
|
-
maxToolRoundtrips = maxAutomaticRoundtrips,
|
|
200
|
-
streamMode,
|
|
201
|
-
streamProtocol,
|
|
202
|
-
onResponse,
|
|
203
|
-
onFinish,
|
|
204
|
-
onError,
|
|
205
|
-
credentials,
|
|
206
|
-
headers,
|
|
207
|
-
body,
|
|
208
|
-
generateId = generateIdFunc,
|
|
209
|
-
fetch,
|
|
210
|
-
keepLastMessageOnError = false,
|
|
211
|
-
}: UseChatOptions & {
|
|
212
|
-
key?: string;
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
@deprecated Use `maxToolRoundtrips` instead.
|
|
216
|
-
*/
|
|
217
|
-
experimental_maxAutomaticRoundtrips?: number;
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
@deprecated Use `maxToolRoundtrips` instead.
|
|
221
|
-
*/
|
|
222
|
-
maxAutomaticRoundtrips?: number;
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Experimental (React only). When a function is provided, it will be used
|
|
226
|
-
* to prepare the request body for the chat API. This can be useful for
|
|
227
|
-
* customizing the request body based on the messages and data in the chat.
|
|
228
|
-
*
|
|
229
|
-
* @param messages The current messages in the chat.
|
|
230
|
-
* @param requestData The data object passed in the chat request.
|
|
231
|
-
* @param requestBody The request body object passed in the chat request.
|
|
232
|
-
*/
|
|
233
|
-
experimental_prepareRequestBody?: (options: {
|
|
234
|
-
messages: Message[];
|
|
235
|
-
requestData?: JSONValue;
|
|
236
|
-
requestBody?: object;
|
|
237
|
-
}) => JSONValue;
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
Maximal number of automatic roundtrips for tool calls.
|
|
241
|
-
|
|
242
|
-
An automatic tool call roundtrip is a call to the server with the
|
|
243
|
-
tool call results when all tool calls in the last assistant
|
|
244
|
-
message have results.
|
|
245
|
-
|
|
246
|
-
A maximum number is required to prevent infinite loops in the
|
|
247
|
-
case of misconfigured tools.
|
|
248
|
-
|
|
249
|
-
By default, it's set to 0, which will disable the feature.
|
|
250
|
-
*/
|
|
251
|
-
maxToolRoundtrips?: number;
|
|
252
|
-
} = {}): UseChatHelpers & {
|
|
253
|
-
/**
|
|
254
|
-
* @deprecated Use `addToolResult` instead.
|
|
255
|
-
*/
|
|
256
|
-
experimental_addToolResult: ({
|
|
257
|
-
toolCallId,
|
|
258
|
-
result,
|
|
259
|
-
}: {
|
|
260
|
-
toolCallId: string;
|
|
261
|
-
result: any;
|
|
262
|
-
}) => void;
|
|
263
|
-
addToolResult: ({
|
|
264
|
-
toolCallId,
|
|
265
|
-
result,
|
|
266
|
-
}: {
|
|
267
|
-
toolCallId: string;
|
|
268
|
-
result: any;
|
|
269
|
-
}) => void;
|
|
270
|
-
} {
|
|
271
|
-
// streamMode is deprecated, use streamProtocol instead.
|
|
272
|
-
if (streamMode) {
|
|
273
|
-
streamProtocol ??= streamMode === 'text' ? 'text' : undefined;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Generate a unique id for the chat if not provided.
|
|
277
|
-
const hookId = useId();
|
|
278
|
-
const idKey = id ?? hookId;
|
|
279
|
-
const chatKey = typeof api === 'string' ? [api, idKey] : idKey;
|
|
280
|
-
|
|
281
|
-
// Store a empty array as the initial messages
|
|
282
|
-
// (instead of using a default parameter value that gets re-created each time)
|
|
283
|
-
// to avoid re-renders:
|
|
284
|
-
const [initialMessagesFallback] = useState([]);
|
|
285
|
-
|
|
286
|
-
// Store the chat state in SWR, using the chatId as the key to share states.
|
|
287
|
-
const { data: messages, mutate } = useSWR<Message[]>(
|
|
288
|
-
[chatKey, 'messages'],
|
|
289
|
-
null,
|
|
290
|
-
{ fallbackData: initialMessages ?? initialMessagesFallback },
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
// We store loading state in another hook to sync loading states across hook invocations
|
|
294
|
-
const { data: isLoading = false, mutate: mutateLoading } = useSWR<boolean>(
|
|
295
|
-
[chatKey, 'loading'],
|
|
296
|
-
null,
|
|
297
|
-
);
|
|
298
|
-
|
|
299
|
-
const { data: streamData, mutate: mutateStreamData } = useSWR<
|
|
300
|
-
JSONValue[] | undefined
|
|
301
|
-
>([chatKey, 'streamData'], null);
|
|
302
|
-
|
|
303
|
-
const { data: error = undefined, mutate: setError } = useSWR<
|
|
304
|
-
undefined | Error
|
|
305
|
-
>([chatKey, 'error'], null);
|
|
306
|
-
|
|
307
|
-
// Keep the latest messages in a ref.
|
|
308
|
-
const messagesRef = useRef<Message[]>(messages || []);
|
|
309
|
-
useEffect(() => {
|
|
310
|
-
messagesRef.current = messages || [];
|
|
311
|
-
}, [messages]);
|
|
312
|
-
|
|
313
|
-
// Abort controller to cancel the current API call.
|
|
314
|
-
const abortControllerRef = useRef<AbortController | null>(null);
|
|
315
|
-
|
|
316
|
-
const extraMetadataRef = useRef({
|
|
317
|
-
credentials,
|
|
318
|
-
headers,
|
|
319
|
-
body,
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
useEffect(() => {
|
|
323
|
-
extraMetadataRef.current = {
|
|
324
|
-
credentials,
|
|
325
|
-
headers,
|
|
326
|
-
body,
|
|
327
|
-
};
|
|
328
|
-
}, [credentials, headers, body]);
|
|
329
|
-
|
|
330
|
-
const triggerRequest = useCallback(
|
|
331
|
-
async (chatRequest: ChatRequest) => {
|
|
332
|
-
const messageCount = messagesRef.current.length;
|
|
333
|
-
|
|
334
|
-
try {
|
|
335
|
-
mutateLoading(true);
|
|
336
|
-
setError(undefined);
|
|
337
|
-
|
|
338
|
-
const abortController = new AbortController();
|
|
339
|
-
abortControllerRef.current = abortController;
|
|
340
|
-
|
|
341
|
-
await processChatStream({
|
|
342
|
-
getStreamedResponse: () =>
|
|
343
|
-
getStreamedResponse(
|
|
344
|
-
api,
|
|
345
|
-
chatRequest,
|
|
346
|
-
mutate,
|
|
347
|
-
mutateStreamData,
|
|
348
|
-
streamData!,
|
|
349
|
-
extraMetadataRef,
|
|
350
|
-
messagesRef,
|
|
351
|
-
abortControllerRef,
|
|
352
|
-
generateId,
|
|
353
|
-
streamProtocol,
|
|
354
|
-
onFinish,
|
|
355
|
-
onResponse,
|
|
356
|
-
onToolCall,
|
|
357
|
-
sendExtraMessageFields,
|
|
358
|
-
experimental_prepareRequestBody,
|
|
359
|
-
fetch,
|
|
360
|
-
keepLastMessageOnError,
|
|
361
|
-
),
|
|
362
|
-
experimental_onFunctionCall,
|
|
363
|
-
experimental_onToolCall,
|
|
364
|
-
updateChatRequest: chatRequestParam => {
|
|
365
|
-
chatRequest = chatRequestParam;
|
|
366
|
-
},
|
|
367
|
-
getCurrentMessages: () => messagesRef.current,
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
abortControllerRef.current = null;
|
|
371
|
-
} catch (err) {
|
|
372
|
-
// Ignore abort errors as they are expected.
|
|
373
|
-
if ((err as any).name === 'AbortError') {
|
|
374
|
-
abortControllerRef.current = null;
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (onError && err instanceof Error) {
|
|
379
|
-
onError(err);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
setError(err as Error);
|
|
383
|
-
} finally {
|
|
384
|
-
mutateLoading(false);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// auto-submit when all tool calls in the last assistant message have results:
|
|
388
|
-
const messages = messagesRef.current;
|
|
389
|
-
const lastMessage = messages[messages.length - 1];
|
|
390
|
-
if (
|
|
391
|
-
// ensure we actually have new messages (to prevent infinite loops in case of errors):
|
|
392
|
-
messages.length > messageCount &&
|
|
393
|
-
// ensure there is a last message:
|
|
394
|
-
lastMessage != null &&
|
|
395
|
-
// check if the feature is enabled:
|
|
396
|
-
maxToolRoundtrips > 0 &&
|
|
397
|
-
// check that roundtrip is possible:
|
|
398
|
-
isAssistantMessageWithCompletedToolCalls(lastMessage) &&
|
|
399
|
-
// limit the number of automatic roundtrips:
|
|
400
|
-
countTrailingAssistantMessages(messages) <= maxToolRoundtrips
|
|
401
|
-
) {
|
|
402
|
-
await triggerRequest({ messages });
|
|
403
|
-
}
|
|
404
|
-
},
|
|
405
|
-
[
|
|
406
|
-
mutate,
|
|
407
|
-
mutateLoading,
|
|
408
|
-
api,
|
|
409
|
-
extraMetadataRef,
|
|
410
|
-
onResponse,
|
|
411
|
-
onFinish,
|
|
412
|
-
onError,
|
|
413
|
-
setError,
|
|
414
|
-
mutateStreamData,
|
|
415
|
-
streamData,
|
|
416
|
-
streamProtocol,
|
|
417
|
-
sendExtraMessageFields,
|
|
418
|
-
experimental_onFunctionCall,
|
|
419
|
-
experimental_onToolCall,
|
|
420
|
-
experimental_prepareRequestBody,
|
|
421
|
-
onToolCall,
|
|
422
|
-
maxToolRoundtrips,
|
|
423
|
-
messagesRef,
|
|
424
|
-
abortControllerRef,
|
|
425
|
-
generateId,
|
|
426
|
-
fetch,
|
|
427
|
-
keepLastMessageOnError,
|
|
428
|
-
],
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
const append = useCallback(
|
|
432
|
-
async (
|
|
433
|
-
message: Message | CreateMessage,
|
|
434
|
-
{
|
|
435
|
-
options,
|
|
436
|
-
functions,
|
|
437
|
-
function_call,
|
|
438
|
-
tools,
|
|
439
|
-
tool_choice,
|
|
440
|
-
data,
|
|
441
|
-
headers,
|
|
442
|
-
body,
|
|
443
|
-
}: ChatRequestOptions = {},
|
|
444
|
-
) => {
|
|
445
|
-
if (!message.id) {
|
|
446
|
-
message.id = generateId();
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const requestOptions = {
|
|
450
|
-
headers: headers ?? options?.headers,
|
|
451
|
-
body: body ?? options?.body,
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
const chatRequest: ChatRequest = {
|
|
455
|
-
messages: messagesRef.current.concat(message as Message),
|
|
456
|
-
options: requestOptions,
|
|
457
|
-
headers: requestOptions.headers,
|
|
458
|
-
body: requestOptions.body,
|
|
459
|
-
data,
|
|
460
|
-
...(functions !== undefined && { functions }),
|
|
461
|
-
...(function_call !== undefined && { function_call }),
|
|
462
|
-
...(tools !== undefined && { tools }),
|
|
463
|
-
...(tool_choice !== undefined && { tool_choice }),
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
return triggerRequest(chatRequest);
|
|
467
|
-
},
|
|
468
|
-
[triggerRequest, generateId],
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
const reload = useCallback(
|
|
472
|
-
async ({
|
|
473
|
-
options,
|
|
474
|
-
functions,
|
|
475
|
-
function_call,
|
|
476
|
-
tools,
|
|
477
|
-
tool_choice,
|
|
478
|
-
data,
|
|
479
|
-
headers,
|
|
480
|
-
body,
|
|
481
|
-
}: ChatRequestOptions = {}) => {
|
|
482
|
-
if (messagesRef.current.length === 0) return null;
|
|
483
|
-
|
|
484
|
-
const requestOptions = {
|
|
485
|
-
headers: headers ?? options?.headers,
|
|
486
|
-
body: body ?? options?.body,
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
// Remove last assistant message and retry last user message.
|
|
490
|
-
const lastMessage = messagesRef.current[messagesRef.current.length - 1];
|
|
491
|
-
if (lastMessage.role === 'assistant') {
|
|
492
|
-
const chatRequest: ChatRequest = {
|
|
493
|
-
messages: messagesRef.current.slice(0, -1),
|
|
494
|
-
options: requestOptions,
|
|
495
|
-
headers: requestOptions.headers,
|
|
496
|
-
body: requestOptions.body,
|
|
497
|
-
data,
|
|
498
|
-
...(functions !== undefined && { functions }),
|
|
499
|
-
...(function_call !== undefined && { function_call }),
|
|
500
|
-
...(tools !== undefined && { tools }),
|
|
501
|
-
...(tool_choice !== undefined && { tool_choice }),
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
return triggerRequest(chatRequest);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const chatRequest: ChatRequest = {
|
|
508
|
-
messages: messagesRef.current,
|
|
509
|
-
options: requestOptions,
|
|
510
|
-
headers: requestOptions.headers,
|
|
511
|
-
body: requestOptions.body,
|
|
512
|
-
data,
|
|
513
|
-
...(functions !== undefined && { functions }),
|
|
514
|
-
...(function_call !== undefined && { function_call }),
|
|
515
|
-
...(tools !== undefined && { tools }),
|
|
516
|
-
...(tool_choice !== undefined && { tool_choice }),
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
return triggerRequest(chatRequest);
|
|
520
|
-
},
|
|
521
|
-
[triggerRequest],
|
|
522
|
-
);
|
|
523
|
-
|
|
524
|
-
const stop = useCallback(() => {
|
|
525
|
-
if (abortControllerRef.current) {
|
|
526
|
-
abortControllerRef.current.abort();
|
|
527
|
-
abortControllerRef.current = null;
|
|
528
|
-
}
|
|
529
|
-
}, []);
|
|
530
|
-
|
|
531
|
-
const setMessages = useCallback(
|
|
532
|
-
(messages: Message[] | ((messages: Message[]) => Message[])) => {
|
|
533
|
-
if (typeof messages === 'function') {
|
|
534
|
-
messages = messages(messagesRef.current);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
mutate(messages, false);
|
|
538
|
-
messagesRef.current = messages;
|
|
539
|
-
},
|
|
540
|
-
[mutate],
|
|
541
|
-
);
|
|
542
|
-
|
|
543
|
-
// Input state and handlers.
|
|
544
|
-
const [input, setInput] = useState(initialInput);
|
|
545
|
-
|
|
546
|
-
const handleSubmit = useCallback(
|
|
547
|
-
async (
|
|
548
|
-
event?: { preventDefault?: () => void },
|
|
549
|
-
options: ChatRequestOptions = {},
|
|
550
|
-
metadata?: Object,
|
|
551
|
-
) => {
|
|
552
|
-
event?.preventDefault?.();
|
|
553
|
-
|
|
554
|
-
if (!input && !options.allowEmptySubmit) return;
|
|
555
|
-
|
|
556
|
-
if (metadata) {
|
|
557
|
-
extraMetadataRef.current = {
|
|
558
|
-
...extraMetadataRef.current,
|
|
559
|
-
...metadata,
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
const attachmentsForRequest: Attachment[] = [];
|
|
564
|
-
const attachmentsFromOptions = options.experimental_attachments;
|
|
565
|
-
|
|
566
|
-
if (attachmentsFromOptions) {
|
|
567
|
-
if (attachmentsFromOptions instanceof FileList) {
|
|
568
|
-
for (const attachment of Array.from(attachmentsFromOptions)) {
|
|
569
|
-
const { name, type } = attachment;
|
|
570
|
-
|
|
571
|
-
const dataUrl = await new Promise<string>((resolve, reject) => {
|
|
572
|
-
const reader = new FileReader();
|
|
573
|
-
reader.onload = readerEvent => {
|
|
574
|
-
resolve(readerEvent.target?.result as string);
|
|
575
|
-
};
|
|
576
|
-
reader.onerror = error => reject(error);
|
|
577
|
-
reader.readAsDataURL(attachment);
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
attachmentsForRequest.push({
|
|
581
|
-
name,
|
|
582
|
-
contentType: type,
|
|
583
|
-
url: dataUrl,
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
} else if (Array.isArray(attachmentsFromOptions)) {
|
|
587
|
-
for (const file of attachmentsFromOptions) {
|
|
588
|
-
const { name, url, contentType } = file;
|
|
589
|
-
|
|
590
|
-
attachmentsForRequest.push({
|
|
591
|
-
name,
|
|
592
|
-
contentType,
|
|
593
|
-
url,
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
} else {
|
|
597
|
-
throw new Error('Invalid attachments type');
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
const requestOptions = {
|
|
602
|
-
headers: options.headers ?? options.options?.headers,
|
|
603
|
-
body: options.body ?? options.options?.body,
|
|
604
|
-
};
|
|
605
|
-
|
|
606
|
-
const messages =
|
|
607
|
-
!input && !attachmentsForRequest.length && options.allowEmptySubmit
|
|
608
|
-
? messagesRef.current
|
|
609
|
-
: messagesRef.current.concat({
|
|
610
|
-
id: generateId(),
|
|
611
|
-
createdAt: new Date(),
|
|
612
|
-
role: 'user',
|
|
613
|
-
content: input,
|
|
614
|
-
experimental_attachments:
|
|
615
|
-
attachmentsForRequest.length > 0
|
|
616
|
-
? attachmentsForRequest
|
|
617
|
-
: undefined,
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
const chatRequest: ChatRequest = {
|
|
621
|
-
messages,
|
|
622
|
-
options: requestOptions,
|
|
623
|
-
headers: requestOptions.headers,
|
|
624
|
-
body: requestOptions.body,
|
|
625
|
-
data: options.data,
|
|
626
|
-
};
|
|
627
|
-
|
|
628
|
-
triggerRequest(chatRequest);
|
|
629
|
-
|
|
630
|
-
setInput('');
|
|
631
|
-
},
|
|
632
|
-
[input, generateId, triggerRequest],
|
|
633
|
-
);
|
|
634
|
-
|
|
635
|
-
const handleInputChange = (e: any) => {
|
|
636
|
-
setInput(e.target.value);
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
const addToolResult = ({
|
|
640
|
-
toolCallId,
|
|
641
|
-
result,
|
|
642
|
-
}: {
|
|
643
|
-
toolCallId: string;
|
|
644
|
-
result: any;
|
|
645
|
-
}) => {
|
|
646
|
-
const updatedMessages = messagesRef.current.map((message, index, arr) =>
|
|
647
|
-
// update the tool calls in the last assistant message:
|
|
648
|
-
index === arr.length - 1 &&
|
|
649
|
-
message.role === 'assistant' &&
|
|
650
|
-
message.toolInvocations
|
|
651
|
-
? {
|
|
652
|
-
...message,
|
|
653
|
-
toolInvocations: message.toolInvocations.map(toolInvocation =>
|
|
654
|
-
toolInvocation.toolCallId === toolCallId
|
|
655
|
-
? { ...toolInvocation, result }
|
|
656
|
-
: toolInvocation,
|
|
657
|
-
),
|
|
658
|
-
}
|
|
659
|
-
: message,
|
|
660
|
-
);
|
|
661
|
-
|
|
662
|
-
mutate(updatedMessages, false);
|
|
663
|
-
|
|
664
|
-
// auto-submit when all tool calls in the last assistant message have results:
|
|
665
|
-
const lastMessage = updatedMessages[updatedMessages.length - 1];
|
|
666
|
-
if (isAssistantMessageWithCompletedToolCalls(lastMessage)) {
|
|
667
|
-
triggerRequest({ messages: updatedMessages });
|
|
668
|
-
}
|
|
669
|
-
};
|
|
670
|
-
|
|
671
|
-
return {
|
|
672
|
-
messages: messages || [],
|
|
673
|
-
error,
|
|
674
|
-
append,
|
|
675
|
-
reload,
|
|
676
|
-
stop,
|
|
677
|
-
setMessages,
|
|
678
|
-
input,
|
|
679
|
-
setInput,
|
|
680
|
-
handleInputChange,
|
|
681
|
-
handleSubmit,
|
|
682
|
-
isLoading,
|
|
683
|
-
data: streamData,
|
|
684
|
-
addToolResult,
|
|
685
|
-
experimental_addToolResult: addToolResult,
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
/**
|
|
690
|
-
Check if the message is an assistant message with completed tool calls.
|
|
691
|
-
The message must have at least one tool invocation and all tool invocations
|
|
692
|
-
must have a result.
|
|
693
|
-
*/
|
|
694
|
-
function isAssistantMessageWithCompletedToolCalls(message: Message) {
|
|
695
|
-
return (
|
|
696
|
-
message.role === 'assistant' &&
|
|
697
|
-
message.toolInvocations &&
|
|
698
|
-
message.toolInvocations.length > 0 &&
|
|
699
|
-
message.toolInvocations.every(toolInvocation => 'result' in toolInvocation)
|
|
700
|
-
);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
/**
|
|
704
|
-
Returns the number of trailing assistant messages in the array.
|
|
705
|
-
*/
|
|
706
|
-
function countTrailingAssistantMessages(messages: Message[]) {
|
|
707
|
-
let count = 0;
|
|
708
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
709
|
-
if (messages[i].role === 'assistant') {
|
|
710
|
-
count++;
|
|
711
|
-
} else {
|
|
712
|
-
break;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
return count;
|
|
716
|
-
}
|