From 56bbc2a71131d607e3779760a73f92ce10480249 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 11 Jul 2024 12:54:36 +0200 Subject: [PATCH] feat (ai/ui): set body and headers directly on options for handleSubmit and append (#2239) --- .changeset/metal-mirrors-stare.md | 10 +++ content/docs/05-ai-sdk-ui/02-chatbot.mdx | 10 ++- .../07-reference/ai-sdk-ui/01-use-chat.mdx | 62 +++++++++++-------- packages/core/svelte/use-chat.ts | 24 +++++-- packages/react/src/use-chat.ts | 26 ++++++-- packages/react/src/use-chat.ui.test.tsx | 8 +-- packages/solid/src/use-chat.ts | 24 +++++-- packages/svelte/src/use-chat.ts | 24 +++++-- packages/ui-utils/src/types.ts | 47 ++++++++++++-- packages/vue/src/use-chat.ts | 21 +++++-- 10 files changed, 192 insertions(+), 64 deletions(-) create mode 100644 .changeset/metal-mirrors-stare.md diff --git a/.changeset/metal-mirrors-stare.md b/.changeset/metal-mirrors-stare.md new file mode 100644 index 00000000000..d600b49e885 --- /dev/null +++ b/.changeset/metal-mirrors-stare.md @@ -0,0 +1,10 @@ +--- +'@ai-sdk/ui-utils': patch +'@ai-sdk/svelte': patch +'@ai-sdk/react': patch +'@ai-sdk/solid': patch +'ai': patch +'@ai-sdk/vue': patch +--- + +feat (ai/ui): set body and headers directly on options for handleSubmit and append diff --git a/content/docs/05-ai-sdk-ui/02-chatbot.mdx b/content/docs/05-ai-sdk-ui/02-chatbot.mdx index 6baf74d69ca..dc054e485c8 100644 --- a/content/docs/05-ai-sdk-ui/02-chatbot.mdx +++ b/content/docs/05-ai-sdk-ui/02-chatbot.mdx @@ -224,10 +224,10 @@ In this example, the `useChat` hook sends a POST request to the `/api/custom-cha ## Setting custom body fields per request -You can configure custom `body` fields on a per-request basis using the `options` field of the `handleSubmit` function. +You can configure custom `body` fields on a per-request basis using the `body` option of the `handleSubmit` function. This is useful if you want to pass in additional information to your backend that is not part of the message list. -```tsx filename="app/page.tsx" highlight="19-21" +```tsx filename="app/page.tsx" highlight="18-20" 'use client'; import { useChat } from 'ai/react'; @@ -245,10 +245,8 @@ export default function Chat() {
{ handleSubmit(event, { - options: { - body: { - customKey: 'customValue', - }, + body: { + customKey: 'customValue', }, }); }} diff --git a/content/docs/07-reference/ai-sdk-ui/01-use-chat.mdx b/content/docs/07-reference/ai-sdk-ui/01-use-chat.mdx index c043af042af..b5fdf2d4695 100644 --- a/content/docs/07-reference/ai-sdk-ui/01-use-chat.mdx +++ b/content/docs/07-reference/ai-sdk-ui/01-use-chat.mdx @@ -160,9 +160,33 @@ Allows you to easily create a conversational user interface for your chatbot app }, { name: 'append', - type: '(message: Message | CreateMessage, chatRequestOptions: { options: { headers, body } }) => Promise', + type: '(message: Message | CreateMessage, options?: ChatRequestOptions) => Promise', description: 'Function to append a message to the chat, triggering an API call for the AI response. It returns a promise that resolves to full response message content when the API call is successfully finished, or throws an error when the API call fails.', + properties: [ + { + type: 'ChatRequestOptions', + parameters: [ + { + name: 'headers', + type: 'Record | Headers', + description: + 'Additional headers that should be to be passed to the API endpoint.', + }, + { + name: 'body', + type: 'object', + description: + 'Additional body JSON properties that should be sent to the API endpoint.', + }, + { + name: 'data', + type: 'JSONValue', + description: 'Additional data to be sent to the API endpoint.', + }, + ], + }, + ], }, { name: 'reload', @@ -199,7 +223,7 @@ Allows you to easily create a conversational user interface for your chatbot app }, { name: 'handleSubmit', - type: '(event?: { preventDefault?: () => void }, chatRequestOptions?: ChatRequestOptions) => void', + type: '(event?: { preventDefault?: () => void }, options?: ChatRequestOptions) => void', description: 'Form submission handler that automatically resets the input field and appends a user message. You can use the `options` parameter to send additional data, headers and more to the server.', properties: [ @@ -207,33 +231,21 @@ Allows you to easily create a conversational user interface for your chatbot app type: 'ChatRequestOptions', parameters: [ { - name: 'options', - type: 'RequestOptions', - description: 'The options to be passed to the fetch call.', - properties: [ - { - type: 'ChatRequestOptions', - parameters: [ - { - name: 'headers', - type: 'Record | Headers', - description: - 'An optional object of headers to be passed to the API endpoint.', - }, - { - name: 'body', - type: 'object', - description: - 'An optional object to be passed to the API endpoint.', - }, - ], - }, - ], + name: 'headers', + type: 'Record | Headers', + description: + 'Additional headers that should be to be passed to the API endpoint.', + }, + { + name: 'body', + type: 'object', + description: + 'Additional body JSON properties that should be sent to the API endpoint.', }, { name: 'data', type: 'JSONValue', - description: 'Additional data to be sent to the server.', + description: 'Additional data to be sent to the API endpoint.', }, ], }, diff --git a/packages/core/svelte/use-chat.ts b/packages/core/svelte/use-chat.ts index 505a32a079d..a24bbf2722e 100644 --- a/packages/core/svelte/use-chat.ts +++ b/packages/core/svelte/use-chat.ts @@ -119,7 +119,7 @@ const getStreamedResponse = async ( messages: constructedMessagesPayload, data: chatRequest.data, ...extraMetadata.body, - ...chatRequest.options?.body, + ...chatRequest.body, ...(chatRequest.functions !== undefined && { functions: chatRequest.functions, }), @@ -137,7 +137,7 @@ const getStreamedResponse = async ( credentials: extraMetadata.credentials, headers: { ...extraMetadata.headers, - ...chatRequest.options?.headers, + ...chatRequest.headers, }, abortController: () => abortControllerRef, restoreMessagesOnFailure() { @@ -284,15 +284,24 @@ export function useChat({ tools, tool_choice, data, + headers, + body, }: ChatRequestOptions = {}, ) => { if (!message.id) { message.id = generateId(); } + const requestOptions = { + headers: headers ?? options?.headers, + body: body ?? options?.body, + }; + const chatRequest: ChatRequest = { messages: get(messages).concat(message as Message), - options, + options: requestOptions, + headers: requestOptions.headers, + body: requestOptions.body, data, ...(functions !== undefined && { functions }), ...(function_call !== undefined && { function_call }), @@ -358,6 +367,11 @@ export function useChat({ event?.preventDefault?.(); const inputValue = get(input); + const requestOptions = { + headers: options.headers ?? options.options?.headers, + body: options.body ?? options.options?.body, + }; + const chatRequest: ChatRequest = { messages: inputValue ? get(messages).concat({ @@ -367,7 +381,9 @@ export function useChat({ createdAt: new Date(), } as Message) : get(messages), - options: options.options, + options: requestOptions, + body: requestOptions.body, + headers: requestOptions.headers, data: options.data, }; diff --git a/packages/react/src/use-chat.ts b/packages/react/src/use-chat.ts index 96b6db2dcc3..00499d843b9 100644 --- a/packages/react/src/use-chat.ts +++ b/packages/react/src/use-chat.ts @@ -134,12 +134,12 @@ const getStreamedResponse = async ( body: experimental_prepareRequestBody?.({ messages: chatRequest.messages, requestData: chatRequest.data, - requestBody: chatRequest.options?.body, + requestBody: chatRequest.body, }) ?? { messages: constructedMessagesPayload, data: chatRequest.data, ...extraMetadataRef.current.body, - ...chatRequest.options?.body, + ...chatRequest.body, ...(chatRequest.functions !== undefined && { functions: chatRequest.functions, }), @@ -157,7 +157,7 @@ const getStreamedResponse = async ( credentials: extraMetadataRef.current.credentials, headers: { ...extraMetadataRef.current.headers, - ...chatRequest.options?.headers, + ...chatRequest.headers, }, abortController: () => abortControllerRef.current, restoreMessagesOnFailure() { @@ -420,15 +420,24 @@ By default, it's set to 0, which will disable the feature. tools, tool_choice, data, + headers, + body, }: ChatRequestOptions = {}, ) => { if (!message.id) { message.id = generateId(); } + const requestOptions = { + headers: headers ?? options?.headers, + body: body ?? options?.body, + }; + const chatRequest: ChatRequest = { messages: messagesRef.current.concat(message as Message), - options, + options: requestOptions, + headers: requestOptions.headers, + body: requestOptions.body, data, ...(functions !== undefined && { functions }), ...(function_call !== undefined && { function_call }), @@ -513,6 +522,11 @@ By default, it's set to 0, which will disable the feature. event?.preventDefault?.(); + const requestOptions = { + headers: options.headers ?? options.options?.headers, + body: options.body ?? options.options?.body, + }; + const chatRequest: ChatRequest = { messages: input ? messagesRef.current.concat({ @@ -521,7 +535,9 @@ By default, it's set to 0, which will disable the feature. content: input, }) : messagesRef.current, - options: options.options, + options: requestOptions, + headers: requestOptions.headers, + body: requestOptions.body, data: options.data, }; diff --git a/packages/react/src/use-chat.ui.test.tsx b/packages/react/src/use-chat.ui.test.tsx index cb7c2e7b51f..2e6858fbf54 100644 --- a/packages/react/src/use-chat.ui.test.tsx +++ b/packages/react/src/use-chat.ui.test.tsx @@ -227,12 +227,12 @@ describe('form actions', () => { ))} - + @@ -311,9 +311,7 @@ describe('prepareRequestBody', () => { { role: 'user', content: 'hi' }, { data: { 'test-data-key': 'test-data-value' }, - options: { - body: { 'request-body-key': 'request-body-value' }, - }, + body: { 'request-body-key': 'request-body-value' }, }, ); }} diff --git a/packages/solid/src/use-chat.ts b/packages/solid/src/use-chat.ts index 06893410540..faaa667ed25 100644 --- a/packages/solid/src/use-chat.ts +++ b/packages/solid/src/use-chat.ts @@ -129,13 +129,13 @@ const getStreamedResponse = async ( messages: constructedMessagesPayload, data: chatRequest.data, ...extraMetadata.body, - ...chatRequest.options?.body, + ...chatRequest.body, }, streamMode, credentials: extraMetadata.credentials, headers: { ...extraMetadata.headers, - ...chatRequest.options?.headers, + ...chatRequest.headers, }, abortController: () => abortController, restoreMessagesOnFailure() { @@ -307,15 +307,22 @@ export function useChat( const append: UseChatHelpers['append'] = async ( message, - { options, data } = {}, + { options, data, headers, body } = {}, ) => { if (!message.id) { message.id = generateId()(); } + const requestOptions = { + headers: headers ?? options?.headers, + body: body ?? options?.body, + }; + const chatRequest: ChatRequest = { messages: messagesRef.concat(message as Message), - options, + options: requestOptions, + headers: requestOptions.headers, + body: requestOptions.body, data, }; @@ -375,6 +382,11 @@ export function useChat( event?.preventDefault?.(); const inputValue = input(); + const requestOptions = { + headers: options.headers ?? options.options?.headers, + body: options.body ?? options.options?.body, + }; + const chatRequest: ChatRequest = { messages: inputValue ? messagesRef.concat({ @@ -384,7 +396,9 @@ export function useChat( createdAt: new Date(), }) : messagesRef, - options: options.options, + options: requestOptions, + body: requestOptions.body, + headers: requestOptions.headers, data: options.data, }; diff --git a/packages/svelte/src/use-chat.ts b/packages/svelte/src/use-chat.ts index 98f3c9bbf14..6b068788fd8 100644 --- a/packages/svelte/src/use-chat.ts +++ b/packages/svelte/src/use-chat.ts @@ -119,7 +119,7 @@ const getStreamedResponse = async ( messages: constructedMessagesPayload, data: chatRequest.data, ...extraMetadata.body, - ...chatRequest.options?.body, + ...chatRequest.body, ...(chatRequest.functions !== undefined && { functions: chatRequest.functions, }), @@ -137,7 +137,7 @@ const getStreamedResponse = async ( credentials: extraMetadata.credentials, headers: { ...extraMetadata.headers, - ...chatRequest.options?.headers, + ...chatRequest.headers, }, abortController: () => abortControllerRef, restoreMessagesOnFailure() { @@ -281,15 +281,24 @@ export function useChat({ tools, tool_choice, data, + headers, + body, }: ChatRequestOptions = {}, ) => { if (!message.id) { message.id = generateId(); } + const requestOptions = { + headers: headers ?? options?.headers, + body: body ?? options?.body, + }; + const chatRequest: ChatRequest = { messages: get(messages).concat(message as Message), - options, + options: requestOptions, + headers: requestOptions.headers, + body: requestOptions.body, data, ...(functions !== undefined && { functions }), ...(function_call !== undefined && { function_call }), @@ -355,6 +364,11 @@ export function useChat({ event?.preventDefault?.(); const inputValue = get(input); + const requestOptions = { + headers: options.headers ?? options.options?.headers, + body: options.body ?? options.options?.body, + }; + const chatRequest: ChatRequest = { messages: inputValue ? get(messages).concat({ @@ -364,7 +378,9 @@ export function useChat({ createdAt: new Date(), } as Message) : get(messages), - options: options.options, + options: requestOptions, + body: requestOptions.body, + headers: requestOptions.headers, data: options.data, }; diff --git a/packages/ui-utils/src/types.ts b/packages/ui-utils/src/types.ts index 4682c559744..183b44b29cd 100644 --- a/packages/ui-utils/src/types.ts +++ b/packages/ui-utils/src/types.ts @@ -190,10 +190,33 @@ export type CreateMessage = Omit & { }; export type ChatRequest = { + /** +An optional object of headers to be passed to the API endpoint. + */ + headers?: Record | Headers; + + /** +An optional object to be passed to the API endpoint. +*/ + body?: object; + + /** +The messages of the chat. + */ messages: Message[]; - options?: RequestOptions; + + /** +Additional data to be sent to the server. + */ data?: JSONValue; + /** +The options to be passed to the fetch call. + +@deprecated use `headers` and `body` directly + */ + options?: RequestOptions; + /** * @deprecated */ @@ -244,8 +267,25 @@ An optional object to be passed to the API endpoint. }; export type ChatRequestOptions = { + /** +Additional headers that should be to be passed to the API endpoint. + */ + headers?: Record | Headers; + + /** +Additional body JSON properties that should be sent to the API endpoint. + */ + body?: object; + + /** +Additional data to be sent to the API endpoint. + */ + data?: JSONValue; + /** The options to be passed to the fetch call. + +@deprecated use `headers` and `body` directly */ options?: RequestOptions; @@ -268,11 +308,6 @@ The options to be passed to the fetch call. @deprecated */ tool_choice?: ToolChoice; - - /** -Additional data to be sent to the server. - */ - data?: JSONValue; }; export type UseChatOptions = { diff --git a/packages/vue/src/use-chat.ts b/packages/vue/src/use-chat.ts index 52f512586f2..36962a0d66a 100644 --- a/packages/vue/src/use-chat.ts +++ b/packages/vue/src/use-chat.ts @@ -119,7 +119,7 @@ export function useChat({ let abortController: AbortController | null = null; async function triggerRequest( messagesSnapshot: Message[], - { options, data }: ChatRequestOptions = {}, + { options, data, headers, body }: ChatRequestOptions = {}, ) { try { error.value = undefined; @@ -132,9 +132,16 @@ export function useChat({ const previousMessages = messagesData.value; mutate(messagesSnapshot); + const requestOptions = { + headers: headers ?? options?.headers, + body: body ?? options?.body, + }; + let chatRequest: ChatRequest = { messages: messagesSnapshot, - options, + options: requestOptions, + body: requestOptions.body, + headers: requestOptions.headers, data, }; @@ -169,12 +176,12 @@ export function useChat({ messages: constructedMessagesPayload, data: chatRequest.data, ...unref(body), // Use unref to unwrap the ref value - ...options?.body, + ...requestOptions.body, }, streamMode, headers: { ...headers, - ...options?.headers, + ...requestOptions.headers, }, abortController: () => abortController, credentials, @@ -227,6 +234,12 @@ export function useChat({ if (!message.id) { message.id = generateId(); } + + const requestOptions = { + headers: options?.headers ?? options?.options?.headers, + body: options?.body ?? options?.options?.body, + }; + return triggerRequest(messages.value.concat(message as Message), options); };