Render Visual Interface in Chat
Learn how to generate text using the AI TOOLKIT and React Server Components.
We've now seen how a language model can call a function and render a component based on a conversation with the user. When we define multiple functions in `tools`, it is possible for the model to reason out the right functions to call based on whatever the user's intent is. This means that you can write a bunch of functions without the burden of implementing complex routing logic to run them.
Run it locally
$ npm install aiClient
'use client';
import { useState } from 'react';
import { ClientMessage } from './actions';
import { useActions, useUIState } from '@ai-toolkit/rsc';
import { generateId } from 'ai-toolkit';
// Allow streaming responses up to 30 seconds
export const maxDuration = 30;
export default function Home() {
const [input, setInput] = useState<string>('');
const [conversation, setConversation] = useUIState();
const { continueConversation } = useActions();
return (
<div>
<div>
{conversation.map((message: ClientMessage) => (
<div key={message.id}>
{message.role}: {message.display}
</div>
))}
</div>
<div>
<input
type="text"
value={input}
onChange={event => {
setInput(event.target.value);
}}
/>
<button
onClick={async () => {
setConversation((currentConversation: ClientMessage[]) => [
...currentConversation,
{ id: generateId(), role: 'user', display: input },
]);
const message = await continueConversation(input);
setConversation((currentConversation: ClientMessage[]) => [
...currentConversation,
message,
]);
}}
>
Send Message
</button>
</div>
</div>
);
}
export async function Stock({ symbol, numOfMonths }) {
const data = await fetch(
https://api.example.com/stock/${symbol}/${numOfMonths},
);
return (
<div>
<div>{symbol}</div>
<div>
{data.timeline.map(data => (
<div>
<div>{data.date}</div>
<div>{data.value}</div>
</div>
))}
</div>
</div>
);
}
export async function Flight({ flightNumber }) {
const data = await fetch(https://api.example.com/flight/${flightNumber});
return (
<div>
<div>{flightNumber}</div>
<div>{data.status}</div>
<div>{data.source}</div>
<div>{data.destination}</div>
</div>
);
}
- 1'use client';
- 2import { useState } from 'react';
- 3import { ClientMessage } from './actions';
- 4import { useActions, useUIState } from '@ai-toolkit/rsc';
- 5import { generateId } from 'ai-toolkit';
- 6// Allow streaming responses up to 30 seconds
- 7export const maxDuration = 30;
- 8export default function Home() {
- 9 const [input, setInput] = useState<string>('');
- 10 const [conversation, setConversation] = useUIState();
- 11 const { continueConversation } = useActions();
- 12 return (
- 13 <div>
- 14 <div>
- 15 {conversation.map((message: ClientMessage) => (
- 16 <div key={message.id}>
- 17 {message.role}: {message.display}
- 18 </div>
- 19 ))}
- 20 </div>
- 21 <div>
- 22 <input
- 23 type="text"
- 24 value={input}
- 25 onChange={event => {
- 26 setInput(event.target.value);
- 27 }}
- 28 />
- 29 <button
- 30 onClick={async () => {
- 31 setConversation((currentConversation: ClientMessage[]) => [
- 32 ...currentConversation,
- 33 { id: generateId(), role: 'user', display: input },
- 34 ]);
- 35 const message = await continueConversation(input);
- 36 setConversation((currentConversation: ClientMessage[]) => [
- 37 ...currentConversation,
- 38 message,
- 39 ]);
- 40 }}
- 41 >
- 42 Send Message
- 43 </button>
- 44 </div>
- 45 </div>
- 46 );
- 47}
- 1export async function Stock({ symbol, numOfMonths }) {
- 2 const data = await fetch(
- 3 `https://api.example.com/stock/${symbol}/${numOfMonths}`,
- 4 );
- 5 return (
- 6 <div>
- 7 <div>{symbol}</div>
- 8 <div>
- 9 {data.timeline.map(data => (
- 10 <div>
- 11 <div>{data.date}</div>
- 12 <div>{data.value}</div>
- 13 </div>
- 14 ))}
- 15 </div>
- 16 </div>
- 17 );
- 18}
- 1export async function Flight({ flightNumber }) {
- 2 const data = await fetch(`https://api.example.com/flight/${flightNumber}`);
- 3 return (
- 4 <div>
- 5 <div>{flightNumber}</div>
- 6 <div>{data.status}</div>
- 7 <div>{data.source}</div>
- 8 <div>{data.destination}</div>
- 9 </div>
- 10 );
- 11}
Server
'use server';
import { getMutableAIState, streamUI } from '@ai-toolkit/rsc';
import { openai } from '@ai-toolkit/openai';
import { ReactNode } from 'react';
import { z } from 'zod';
import { generateId } from 'ai-toolkit';
import { Stock } from '@/components/stock';
import { Flight } from '@/components/flight';
export interface ServerMessage {
role: 'user' | 'assistant';
content: string;
}
export interface ClientMessage {
id: string;
role: 'user' | 'assistant';
display: ReactNode;
}
export async function continueConversation(
input: string,
): Promise<ClientMessage> {
'use server';
const history = getMutableAIState();
const result = await streamUI({
model: openai('gpt-3.5-turbo'),
messages: [...history.get(), { role: 'user', content: input }],
text: ({ content, done }) => {
if (done) {
history.done((messages: ServerMessage[]) => [
...messages,
{ role: 'assistant', content },
]);
}
return <div>{content}</div>;
},
tools: {
showStockInformation: {
description:
'Get stock information for symbol for the last numOfMonths months',
inputSchema: z.object({
symbol: z
.string()
.describe('The stock symbol to get information for'),
numOfMonths: z
.number()
.describe('The number of months to get historical information for'),
}),
generate: async ({ symbol, numOfMonths }) => {
history.done((messages: ServerMessage[]) => [
...messages,
{
role: 'assistant',
content: Showing stock information for ${symbol},
},
]);
return <Stock symbol={symbol} numOfMonths={numOfMonths} />;
},
},
showFlightStatus: {
description: 'Get the status of a flight',
inputSchema: z.object({
flightNumber: z
.string()
.describe('The flight number to get status for'),
}),
generate: async ({ flightNumber }) => {
history.done((messages: ServerMessage[]) => [
...messages,
{
role: 'assistant',
content: Showing flight status for ${flightNumber},
},
]);
return <Flight flightNumber={flightNumber} />;
},
},
},
});
return {
id: generateId(),
role: 'assistant',
display: result.value,
};
}
import { createAI } from '@ai-toolkit/rsc';
import { ServerMessage, ClientMessage, continueConversation } from './actions';
export const AI = createAI<ServerMessage[], ClientMessage[]>({
actions: {
continueConversation,
},
initialAIState: [],
initialUIState: [],
});
- 1'use server';
- 2import { getMutableAIState, streamUI } from '@ai-toolkit/rsc';
- 3import { openai } from '@ai-toolkit/openai';
- 4import { ReactNode } from 'react';
- 5import { z } from 'zod';
- 6import { generateId } from 'ai-toolkit';
- 7import { Stock } from '@/components/stock';
- 8import { Flight } from '@/components/flight';
- 9export interface ServerMessage {
- 10 role: 'user' | 'assistant';
- 11 content: string;
- 12}
- 13export interface ClientMessage {
- 14 id: string;
- 15 role: 'user' | 'assistant';
- 16 display: ReactNode;
- 17}
- 18export async function continueConversation(
- 19 input: string,
- 20): Promise<ClientMessage> {
- 21 'use server';
- 22 const history = getMutableAIState();
- 23 const result = await streamUI({
- 24 model: openai('gpt-3.5-turbo'),
- 25 messages: [...history.get(), { role: 'user', content: input }],
- 26 text: ({ content, done }) => {
- 27 if (done) {
- 28 history.done((messages: ServerMessage[]) => [
- 29 ...messages,
- 30 { role: 'assistant', content },
- 31 ]);
- 32 }
- 33 return <div>{content}</div>;
- 34 },
- 35 tools: {
- 36 showStockInformation: {
- 37 description:
- 38 'Get stock information for symbol for the last numOfMonths months',
- 39 inputSchema: z.object({
- 40 symbol: z
- 41 .string()
- 42 .describe('The stock symbol to get information for'),
- 43 numOfMonths: z
- 44 .number()
- 45 .describe('The number of months to get historical information for'),
- 46 }),
- 47 generate: async ({ symbol, numOfMonths }) => {
- 48 history.done((messages: ServerMessage[]) => [
- 49 ...messages,
- 50 {
- 51 role: 'assistant',
- 52 content: `Showing stock information for ${symbol}`,
- 53 },
- 54 ]);
- 55 return <Stock symbol={symbol} numOfMonths={numOfMonths} />;
- 56 },
- 57 },
- 58 showFlightStatus: {
- 59 description: 'Get the status of a flight',
- 60 inputSchema: z.object({
- 61 flightNumber: z
- 62 .string()
- 63 .describe('The flight number to get status for'),
- 64 }),
- 65 generate: async ({ flightNumber }) => {
- 66 history.done((messages: ServerMessage[]) => [
- 67 ...messages,
- 68 {
- 69 role: 'assistant',
- 70 content: `Showing flight status for ${flightNumber}`,
- 71 },
- 72 ]);
- 73 return <Flight flightNumber={flightNumber} />;
- 74 },
- 75 },
- 76 },
- 77 });
- 78 return {
- 79 id: generateId(),
- 80 role: 'assistant',
- 81 display: result.value,
- 82 };
- 83}
- 1import { createAI } from '@ai-toolkit/rsc';
- 2import { ServerMessage, ClientMessage, continueConversation } from './actions';
- 3export const AI = createAI<ServerMessage[], ClientMessage[]>({
- 4 actions: {
- 5 continueConversation,
- 6 },
- 7 initialAIState: [],
- 8 initialUIState: [],
- 9});