AI SDK UIChatbot

Chatbot

The useChat hook makes it effortless to create a conversational user interface for your chatbot application. It enables the streaming of chat messages from your AI provider, manages the chat state, and updates the UI automatically as new messages arrive.

To summarize, the useChat hook provides the following features:

  • Message Streaming: All the messages from the AI provider are streamed to the chat UI in real-time.
  • Managed States: The hook manages the states for input, messages, loading, error and more for you.
  • Seamless Integration: Easily integrate your chat AI into any design or layout with minimal effort.

In this guide, you will learn how to use the useChat hook to create a chatbot application with real-time message streaming. Let's start with the following example first.

Example

app/page.tsx
'use client';
import { useChat } from 'ai/react';
export default function Page() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: 'api/chat',
});
return (
<>
{messages.map(message => (
<div key={message.id}>
{message.role === 'user' ? 'User: ' : 'AI: '}
{message.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
name="prompt"
value={input}
onChange={handleInputChange}
id="input"
/>
<button type="submit">Submit</button>
</form>
</>
);
}
app/api/chat/route.ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
export async function POST(req: Request) {
const { prompt }: { prompt: string } = await req.json();
const result = await streamText({
model: openai('gpt-4'),
system: 'You are a helpful assistant.',
messages,
});
return result.toAIStreamResponse();
}

In the Page component, the useChat hook will request to your AI provider endpoint whenever the user submits a message. The messages are then streamed back in real-time and displayed in the chat UI.

This enables a seamless chat experience where the user can see the AI response as soon as it is available, without having to wait for the entire response to be received.

Customized UI

useChat also provides ways to manage the chat message and input states via code, show loading and error states, and update messages without being triggered by user interactions.

Loading and error states

To show a loading spinner while the chatbot is processing the user's message, you can use the isLoading state returned by the useChat hook:

const { isLoading, ... } = useChat()
return <>
{isLoading ? <Spinner /> : null}
...

Similarly, the error state reflects the error object thrown during the fetch request. It can be used to display an error message, or show a toast notification:

const { error, ... } = useChat()
useEffect(() => {
if (error) {
toast.error(error.message)
}
}, [error])
// Or display the error message in the UI:
return <>
{error ? <div>{error.message}</div> : null}
...

Modify messages

Sometimes, you may want to directly modify some existing messages. For example, a delete button can be added to each message to allow users to remove them from the chat history.

The setMessages function can help you achieve these tasks:

const { messages, setMessages, ... } = useChat()
const handleDelete = (id) => {
setMessages(messages.filter(message => message.id !== id))
}
return <>
{messages.map(message => (
<div key={message.id}>
{message.role === 'user' ? 'User: ' : 'AI: '}
{message.content}
<button onClick={() => handleDelete(message.id)}>Delete</button>
</div>
))}
...

You can think of messages and setMessages as a pair of state and setState in React.

Controlled input

In the initial example, we have handleSubmit and handleInputChange callbacks that manage the input changes and form submissions. These are handy for common use cases, but you can also use uncontrolled APIs for more advanced scenarios such as form validation or customized components.

The following example demonstrates how to use more granular APIs like setInput and append with your custom input and submit button components:

const { input, setInput, append } = useChat()
return <>
<MyCustomInput value={input} onChange={value => setInput(value)} />
<MySubmitButton onClick={() => {
// Send a new message to the AI provider
append({
role: 'user',
content: input,
})
}}/>
...

Cancelation and regeneration

It's also a common use case to abort the response message while it's still streaming back from the AI provider. You can do this by calling the stop function returned by the useChat hook.

const { stop, isLoading, ... } = useChat()
return <>
<button onClick={stop} disabled={!isLoading}>Stop</button>
...

When the user clicks the "Stop" button, the fetch request will be aborted. This avoids consuming unnecessary resources and improves the UX of your chatbot application.

Similarly, you can also request the AI provider to reprocess the last message by calling the reload function returned by the useChat hook:

const { reload, isLoading, ... } = useChat()
return <>
<button onClick={reload} disabled={isLoading}>Regenerate</button>
...

When the user clicks the "Regenerate" button, the AI provider will regenerate the last message and replace the current one correspondingly.

Event Callbacks

useChat also provides optional event callbacks that you can use to handle different stages of the chatbot lifecycle. These callbacks can be used to trigger additional actions, such as logging, analytics, or custom UI updates.

const { ... } = useChat({
onResponse: (response: Response) => {
console.log('Received response from server:', response)
},
onFinish: (message: Message) => {
console.log('Finished streaming message:', message)
},
onError: (error: Error) => {
console.error('An error occurred:', error)
},
})

It's worth nothing that you can abort the processing by throwing an error in the onResponse callback. This will trigger the onError callback and stop the message from being appended to the chat UI. This can be useful for handling unexpected responses from the AI provider.

Configure Request Options

By default, the useChat hook sends a HTTP POST request to the /api/chat endpoint with the message list as the request body. You can customize the request by passing additional options to the useChat hook:

const { messages, input, handleInputChange, handleSubmit } = useChat({
api: '/api/custom-chat',
headers: {
Authorization: 'your_token',
},
body: {
user_id: '123',
},
credentials: 'same-origin',
});

In this example, the useChat hook sends a POST request to the /api/custom-chat endpoint with the specified headers, additional body fields, and credentials for that fetch request. On your server side, you can handle the request with these additional information.