Generate Image with Chat Prompt
Learn how to generate an image with a chat prompt using the AI TOOLKIT and Next.js
When building a chatbot, you may want to allow the user to generate an image. This can be done by creating a tool that generates an image using the `generateImage` function from the AI TOOLKIT.
Run it locally
$ npm install aiServer
Let's create an endpoint at /api/chat that generates the assistant's response based on the conversation history. You will also define a tool called generateImage that will generate an image based on the assistant's response.
import { generateImage, tool } from 'ai-toolkit';
import z from 'zod';
export const generateImageTool = tool({
description: 'Generate an image',
inputSchema: z.object({
prompt: z.string().describe('The prompt to generate the image from'),
}),
execute: async ({ prompt }) => {
const { image } = await generateImage({
model: openai.imageModel('dall-e-3'),
prompt,
});
// in production, save this image to blob storage and return a URL
return { image: image.base64, prompt };
},
});
import {
convertToModelMessages,
type InferUITools,
stepCountIs,
streamText,
type UIMessage,
} from 'ai-toolkit';
import { generateImageTool } from '@/tools/generate-image';
const tools = {
generateImage: generateImageTool,
};
export type ChatTools = InferUITools<typeof tools>;
export async function POST(request: Request) {
const { messages }: { messages: UIMessage[] } = await request.json();
const result = streamText({
model: 'openai/gpt-4o',
messages: await convertToModelMessages(messages),
stopWhen: stepCountIs(5),
tools,
});
return result.toUIMessageStreamResponse();
}
<Note>
In production, you should save the generated image to a blob storage and
return a URL instead of the base64 image data. If you don't, the base64 image
data will be sent to the model which may cause the generation to fail.
</Note>
- 1import { generateImage, tool } from 'ai-toolkit';
- 2import z from 'zod';
- 3export const generateImageTool = tool({
- 4 description: 'Generate an image',
- 5 inputSchema: z.object({
- 6 prompt: z.string().describe('The prompt to generate the image from'),
- 7 }),
- 8 execute: async ({ prompt }) => {
- 9 const { image } = await generateImage({
- 10 model: openai.imageModel('dall-e-3'),
- 11 prompt,
- 12 });
- 13 // in production, save this image to blob storage and return a URL
- 14 return { image: image.base64, prompt };
- 15 },
- 16});
- 1import {
- 2 convertToModelMessages,
- 3 type InferUITools,
- 4 stepCountIs,
- 5 streamText,
- 6 type UIMessage,
- 7} from 'ai-toolkit';
- 8import { generateImageTool } from '@/tools/generate-image';
- 9const tools = {
- 10 generateImage: generateImageTool,
- 11};
- 12export type ChatTools = InferUITools<typeof tools>;
- 13export async function POST(request: Request) {
- 14 const { messages }: { messages: UIMessage[] } = await request.json();
- 15 const result = streamText({
- 16 model: 'openai/gpt-4o',
- 17 messages: await convertToModelMessages(messages),
- 18 stopWhen: stepCountIs(5),
- 19 tools,
- 20 });
- 21 return result.toUIMessageStreamResponse();
- 22}
Client
Let's create a simple chat interface with useChat. You will call the /api/chat endpoint to generate the assistant's response. If the assistant's response contains a generateImage tool invocation, you will display the tool result (the image in base64 format and the prompt) using the Next Image component.
'use client';
import { useChat } from '@ai-toolkit/react';
import { DefaultChatTransport, type UIMessage } from 'ai-toolkit';
import Image from 'next/image';
import { type FormEvent, useState } from 'react';
import type { ChatTools } from './api/chat/route';
type ChatMessage = UIMessage<never, never, ChatTools>;
export default function Chat() {
const [input, setInput] = useState('');
const { messages, sendMessage } = useChat<ChatMessage>({
transport: new DefaultChatTransport({
api: '/api/chat',
}),
});
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setInput(event.target.value);
};
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
sendMessage({
parts: [{ type: 'text', text: input }],
});
setInput('');
};
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
<div className="space-y-4">
{messages.map(message => (
<div key={message.id} className="whitespace-pre-wrap">
<div key={message.id}>
<div className="font-bold">{message.role}</div>
{message.parts.map((part, partIndex) => {
const { type } = part;
if (type === 'text') {
return (
<div key={${message.id}-part-${partIndex}}>
{part.text}
</div>
);
}
if (type === 'tool-generateImage') {
const { state, toolCallId } = part;
if (state === 'input-available') {
return (
<div key={${message.id}-part-${partIndex}}>
Generating image...
</div>
);
}
if (state === 'output-available') {
const { input, output } = part;
return (
<Image
key={toolCallId}
src={data:image/png;base64,${output.image}}
alt={input.prompt}
height={400}
width={400}
/>
);
}
}
})}
</div>
</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={handleInputChange}
/>
</form>
</div>
);
}
- 1'use client';
- 2import { useChat } from '@ai-toolkit/react';
- 3import { DefaultChatTransport, type UIMessage } from 'ai-toolkit';
- 4import Image from 'next/image';
- 5import { type FormEvent, useState } from 'react';
- 6import type { ChatTools } from './api/chat/route';
- 7type ChatMessage = UIMessage<never, never, ChatTools>;
- 8export default function Chat() {
- 9 const [input, setInput] = useState('');
- 10 const { messages, sendMessage } = useChat<ChatMessage>({
- 11 transport: new DefaultChatTransport({
- 12 api: '/api/chat',
- 13 }),
- 14 });
- 15 const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
- 16 setInput(event.target.value);
- 17 };
- 18 const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
- 19 event.preventDefault();
- 20 sendMessage({
- 21 parts: [{ type: 'text', text: input }],
- 22 });
- 23 setInput('');
- 24 };
- 25 return (
- 26 <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
- 27 <div className="space-y-4">
- 28 {messages.map(message => (
- 29 <div key={message.id} className="whitespace-pre-wrap">
- 30 <div key={message.id}>
- 31 <div className="font-bold">{message.role}</div>
- 32 {message.parts.map((part, partIndex) => {
- 33 const { type } = part;
- 34 if (type === 'text') {
- 35 return (
- 36 <div key={`${message.id}-part-${partIndex}`}>
- 37 {part.text}
- 38 </div>
- 39 );
- 40 }
- 41 if (type === 'tool-generateImage') {
- 42 const { state, toolCallId } = part;
- 43 if (state === 'input-available') {
- 44 return (
- 45 <div key={`${message.id}-part-${partIndex}`}>
- 46 Generating image...
- 47 </div>
- 48 );
- 49 }
- 50 if (state === 'output-available') {
- 51 const { input, output } = part;
- 52 return (
- 53 <Image
- 54 key={toolCallId}
- 55 src={`data:image/png;base64,${output.image}`}
- 56 alt={input.prompt}
- 57 height={400}
- 58 width={400}
- 59 />
- 60 );
- 61 }
- 62 }
- 63 })}
- 64 </div>
- 65 </div>
- 66 ))}
- 67 </div>
- 68 <form onSubmit={handleSubmit}>
- 69 <input
- 70 className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
- 71 value={input}
- 72 placeholder="Say something..."
- 73 onChange={handleInputChange}
- 74 />
- 75 </form>
- 76 </div>
- 77 );
- 78}