Generate Image with Chat Prompt

Learn how to generate an image with a chat prompt using the AI TOOLKIT and Next.js

3 min readnextstreamingchatimage generationtoolsView source

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 ai

Server

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>

tools/generate-image.ts
typescript
  1. 1import { generateImage, tool } from 'ai-toolkit';
  2. 2import z from 'zod';
  3. 3export const generateImageTool = tool({
  4. 4 description: 'Generate an image',
  5. 5 inputSchema: z.object({
  6. 6 prompt: z.string().describe('The prompt to generate the image from'),
  7. 7 }),
  8. 8 execute: async ({ prompt }) => {
  9. 9 const { image } = await generateImage({
  10. 10 model: openai.imageModel('dall-e-3'),
  11. 11 prompt,
  12. 12 });
  13. 13 // in production, save this image to blob storage and return a URL
  14. 14 return { image: image.base64, prompt };
  15. 15 },
  16. 16});
app/api/chat/route.ts
typescript
  1. 1import {
  2. 2 convertToModelMessages,
  3. 3 type InferUITools,
  4. 4 stepCountIs,
  5. 5 streamText,
  6. 6 type UIMessage,
  7. 7} from 'ai-toolkit';
  8. 8import { generateImageTool } from '@/tools/generate-image';
  9. 9const tools = {
  10. 10 generateImage: generateImageTool,
  11. 11};
  12. 12export type ChatTools = InferUITools<typeof tools>;
  13. 13export async function POST(request: Request) {
  14. 14 const { messages }: { messages: UIMessage[] } = await request.json();
  15. 15 const result = streamText({
  16. 16 model: 'openai/gpt-4o',
  17. 17 messages: await convertToModelMessages(messages),
  18. 18 stopWhen: stepCountIs(5),
  19. 19 tools,
  20. 20 });
  21. 21 return result.toUIMessageStreamResponse();
  22. 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>

);

}

app/page.tsx
tsx
  1. 1'use client';
  2. 2import { useChat } from '@ai-toolkit/react';
  3. 3import { DefaultChatTransport, type UIMessage } from 'ai-toolkit';
  4. 4import Image from 'next/image';
  5. 5import { type FormEvent, useState } from 'react';
  6. 6import type { ChatTools } from './api/chat/route';
  7. 7type ChatMessage = UIMessage<never, never, ChatTools>;
  8. 8export default function Chat() {
  9. 9 const [input, setInput] = useState('');
  10. 10 const { messages, sendMessage } = useChat<ChatMessage>({
  11. 11 transport: new DefaultChatTransport({
  12. 12 api: '/api/chat',
  13. 13 }),
  14. 14 });
  15. 15 const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  16. 16 setInput(event.target.value);
  17. 17 };
  18. 18 const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
  19. 19 event.preventDefault();
  20. 20 sendMessage({
  21. 21 parts: [{ type: 'text', text: input }],
  22. 22 });
  23. 23 setInput('');
  24. 24 };
  25. 25 return (
  26. 26 <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
  27. 27 <div className="space-y-4">
  28. 28 {messages.map(message => (
  29. 29 <div key={message.id} className="whitespace-pre-wrap">
  30. 30 <div key={message.id}>
  31. 31 <div className="font-bold">{message.role}</div>
  32. 32 {message.parts.map((part, partIndex) => {
  33. 33 const { type } = part;
  34. 34 if (type === 'text') {
  35. 35 return (
  36. 36 <div key={`${message.id}-part-${partIndex}`}>
  37. 37 {part.text}
  38. 38 </div>
  39. 39 );
  40. 40 }
  41. 41 if (type === 'tool-generateImage') {
  42. 42 const { state, toolCallId } = part;
  43. 43 if (state === 'input-available') {
  44. 44 return (
  45. 45 <div key={`${message.id}-part-${partIndex}`}>
  46. 46 Generating image...
  47. 47 </div>
  48. 48 );
  49. 49 }
  50. 50 if (state === 'output-available') {
  51. 51 const { input, output } = part;
  52. 52 return (
  53. 53 <Image
  54. 54 key={toolCallId}
  55. 55 src={`data:image/png;base64,${output.image}`}
  56. 56 alt={input.prompt}
  57. 57 height={400}
  58. 58 width={400}
  59. 59 />
  60. 60 );
  61. 61 }
  62. 62 }
  63. 63 })}
  64. 64 </div>
  65. 65 </div>
  66. 66 ))}
  67. 67 </div>
  68. 68 <form onSubmit={handleSubmit}>
  69. 69 <input
  70. 70 className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
  71. 71 value={input}
  72. 72 placeholder="Say something..."
  73. 73 onChange={handleInputChange}
  74. 74 />
  75. 75 </form>
  76. 76 </div>
  77. 77 );
  78. 78}