NextJS Course (8/9) – Adding Images to Blockbuster Chat V3

Hi and welcome back to part 8! Kudos for making it this far! πŸŽ‰ It’s time to put the finishing touches on our app and add an image of the character the user is chatting with, as promised in the last part.

The first thing we’ll have to do is update our ChatMessage type to include an optional image URL property. This will allow us to display the image of the character the user is chatting with. So open up the chatMessage.ts file in the types folder:

πŸ“ app
    πŸ“ api
        πŸ“ blockbuster
            πŸ“„ route.tsx
        πŸ“ blockbuster_v2
            πŸ“„ route.tsx
    πŸ“ blockbuster_chat
        πŸ“„ page.tsx
    πŸ“ blockbuster_chat_2
        πŸ“„ page.tsx
    πŸ“ counter
        πŸ“„ page.tsx
    πŸ“ types
        πŸ“„ chatMessage.ts  πŸ› οΈ We'll be working on this file
    πŸ“„ favicon.ico
    πŸ“„ globals.css
    πŸ“„ layout.tsx
    πŸ“„ navbar.tsx
    πŸ“„ page.tsx

We have our type interface defined as follows:

export interface ChatMessage {
  text: string;
  sender: string;
  isAnswer?: boolean;
  time_sent: Date;
}

Now I’ve noticed one thing. This kind of stuff in JavaScript usually uses naming conventions like timeSent instead of time_sent. The reason I used time_sent is because I’m very used to Python naming conventions but say we want to change this to timeSent instead. If we just change the name in here we will break the code in other places where the time_sent property is used.

Luckily for us, VSCode has a feature that will solve this problem for us. Simply click on the time_sent name like so:

Now press F2 on your keyboard and you’ll be able to rename the property:

Change it to timeSent and press Enter. VSCode will now not just rename the property in the current file but also in all the other files where it’s used. Pretty neat, right?

Now let’s add the imageUrl property to our ChatMessage type:

export interface ChatMessage {
  text: string;
  sender: string;
  timeSent: Date;
  isAnswer?: boolean;
  imgUrl?: string;
}

Make sure you use the ? to make the property optional. This way we can have messages without an image URL, and messages before the image finishes generating and loading (that way we don’t have to keep the user waiting).

Adding the Image Generation API

The first thing we’ll need to do is add a backend API that can generate images. So go ahead and create a new folder named get_image inside the API folder and create a new file inside it called route.tsx:

πŸ“ app
    πŸ“ api
        πŸ“ blockbuster
            πŸ“„ route.tsx
        πŸ“ blockbuster_v2
            πŸ“„ route.tsx
        πŸ“ get_image
            πŸ“„ route.tsx  ✨ New file
    πŸ“ blockbuster_chat
        πŸ“„ page.tsx
    πŸ“ blockbuster_chat_2
        πŸ“„ page.tsx
    πŸ“ counter
        πŸ“„ page.tsx
    πŸ“ types
        πŸ“„ chatMessage.ts
    πŸ“„ favicon.ico
    πŸ“„ globals.css
    πŸ“„ layout.tsx
    πŸ“„ navbar.tsx
    πŸ“„ page.tsx

The API we’ll be using is DALLΒ·E 3 by OpenAI. It’s a powerful image generation model that can generate images based on text prompts. The nice thing is that we can use the same API key we used for ChatGPT so we have no extra setup work to be done! The pricing for the image size and quality we’ll be generating here will be $0.04 per image, see https://openai.com/api/pricing/ for more details on other sizes and pricing for the other models.

So open up get_image/route.tsx and add the following code:

import { NextRequest, NextResponse } from "next/server";
import OpenAI from "openai";

const openAIConfig = { apiKey: process.env.OPENAI_API_KEY };
const openai = new OpenAI(openAIConfig);

This is just our usual setup, nothing new here. Notice how we can use the exact same OpenAI library and key, simple!

Now let’s create the POST route/function that will generate an image and return the URL to the client. As this is fairly similar to the principles you’ve learned so far, I’ll just show the whole code in one go:

export async function POST(request: NextRequest) {
  const { movieName, movieCharacter }: { 
    movieName: string; 
    movieCharacter: string; 
  } = await request.json();

  console.log(`Generating image for ${movieCharacter} from ${movieName}`);

  if (!movieName || !movieCharacter ) {
    return NextResponse.json({ error: "Missing required fields" }, { status: 400 });
  }

  const prompt = `A character from the movie ${movieName} named ${movieCharacter}`;

  const response = await openai.images.generate({
    model: "dall-e-3",
    prompt: prompt,
    n: 1,
    size: "1024x1024",
  });
  const image_url = response.data[0].url;
  console.log(image_url);

  return NextResponse.json({ image_url });
}

We take two variables from the request body, movieName and movieCharacter, and destructure them from the JSON body. Next is a print statement for our debugging purposes and a check like we have done before to make sure we have the required data to continue.

We then create the prompt string for the image generation model and call the openai.images.generate function which is pretty similar to the chat functions we’ve been invoking so far. The model is dall-e-3 and the prompt is the string we created.

A quick tip from my future self: I have noticed that asking directly for A character from the movie ${movieName} named ${movieCharacter} can sometimes lead to the model not generating an image due to a copyright filter being triggered. I advise trying with a slightly more indirect wording like A character in the style of ${movieCharacter} from the movie ${movieName} or something similar if you run into this issue.

The n or number parameter is set to 1 to generate only one image and the size parameter is set to 1024x1024 to generate a 1024×1024 image. Images are generated at standard quality by default, which is more than good enough, so we have omitted that parameter.

We extract the image URL from the response (response.data[0].url) and return it to the client as an object with the key name image_url. We also print it to the console for testing/debugging purposes.

Testing the Image Generation API

Now let’s test it using Postman by sending the following object to the route. Note that you can skip this step if you don’t have Postman or a similar tool installed and test it using the Blockbuster Chat V3 later on:

// Send this to Postman
{
  "movieName": "The Matrix",
  "movieCharacter": "Neo"
}

As you can see you will get an image URL as the response. Open it in your browser to see the generated image:

While it doesn’t look quite like Neo (as the API doesn’t want to break copyright), it is pretty cool and has exactly the right style and vibe to it! πŸ•ΆοΈ This will work great for our chat purposes.

Updating the Navbar

Now that we have a working image generation endpoint we can move on to the frontend part and create Blockbuster Chat V3! πŸš€

First of all, let’s update our Navbar component. Open up the navbar.tsx file in the app folder:

πŸ“ app
    πŸ“ api
        πŸ“ blockbuster
            πŸ“„ route.tsx
        πŸ“ blockbuster_v2
            πŸ“„ route.tsx
        πŸ“ get_image
            πŸ“„ route.tsx
    πŸ“ blockbuster_chat
        πŸ“„ page.tsx
    πŸ“ blockbuster_chat_2
        πŸ“„ page.tsx
    πŸ“ counter
        πŸ“„ page.tsx
    πŸ“ types
        πŸ“„ chatMessage.ts
    πŸ“„ favicon.ico
    πŸ“„ globals.css
    πŸ“„ layout.tsx
    πŸ“„ navbar.tsx  πŸ› οΈ We'll be working on this file
    πŸ“„ page.tsx

Just scroll down to th