Use an AI text-to-image Generator model in Replicate with Nextjs and TypeScript | Step-by-Step guide

September 23, 2023 (1y ago)

22 min read

Hello, today we will develop a simple application with a text-to-image mechanism using the sdxl model and Nextjs in Replicate.

  1. Create a Nextjs app
npx create-next-app@latest
  1. Run the app locally

Now run your app locally to make sure everything is working:

cd my-app
npm run dev
  1. Configure environment

We need to API token to be able to run models. So we will create an .env file and add an API token to it.

touch .env

When it's created, add your token to it. If you don't know how to get API_TOKEN, you can create and get your token in the below link.

REPLICATE_API_TOKEN="XXXXXXXX"
  1. Create Backend After doing that, we create index.ts file in pages/api/predictions folder.
mkdir -p pages/api/predictions

Good to know: If you are using the App Router, you can use Server Components or Route Handlers instead of API Routes. Let's write the backend!

import { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const response = await fetch("https://api.replicate.com/v1/predictions", {
    method: "POST",
    headers: {
      Authorization: `Token ${process.env.REPLICATE_API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      version:
        "2b017d9b67edd2ee1401238df49d75da53c523f36e363881e057f5dc3ed3c5b2",

      // This is the text prompt that will be submitted by a form on the frontend
      input: { prompt: req.body.prompt },
    }),

});
  • Firstly create a handler to get the prompt in frontend.
  • Add API token to headers
  • Input value from front-end

And create an error handler for this response:

if (response.status !== 201) {
let error = await response.json();
res.statusCode = 500;
res.end(JSON.stringify({ detail: error.detail }));
return;
}

const prediction = await response.json();
res.statusCode = 201;
res.end(JSON.stringify(prediction));
}

In the final index.ts file:

import { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const response = await fetch("https://api.replicate.com/v1/predictions", {
    method: "POST",
    headers: {
      Authorization: `Token ${process.env.REPLICATE_API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      version:
        "2b017d9b67edd2ee1401238df49d75da53c523f36e363881e057f5dc3ed3c5b2",

      // This is the text prompt that will be submitted by a form on the frontend
      input: { prompt: req.body.prompt },
    }),

});

if (response.status !== 201) {
let error = await response.json();
res.statusCode = 500;
res.end(JSON.stringify({ detail: error.detail }));
return;
}

const prediction = await response.json();
res.statusCode = 201;
res.end(JSON.stringify(prediction));
} 5. Create Front-end
import { FormEvent, useState } from "react";
import Head from "next/head";
import Image from "next/image";

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

interface Prediction {
id: string;
status: string;
output: string[];
detail: string;
}

export default function Home() {
  const [prediction, setPrediction] = useState<Prediction>();
  const [error, setError] = useState(null);

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const response = await fetch("/api/predictions", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: e.currentTarget.prompt.value,
}),
});
let prediction = await response.json();
if (response.status !== 201) {
setError(prediction.detail);
return;
}
setPrediction(prediction);

    while (
      prediction.status !== "succeeded" &&
      prediction.status !== "failed"
    ) {
      await sleep(1000);
      const response = await fetch("/api/predictions/" + prediction.id);
      prediction = await response.json();
      if (response.status !== 200) {
        setError(prediction.detail);
        return;
      }
      console.log({ prediction });
      setPrediction(prediction);
    }

};
}
  • The function called sleep allows it to wait for a certain period of time by embedding a number value it takes as a parameter, for example 1000, into setTimeout.
  • handleSubmit function helps me send the POST request that will initially trigger the handler function in the api/predictions/index.ts file.

In the continuation of the above code:

return (
  <div>
    <Head>
      <title>Replicate + Next.js</title>
    </Head>

    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="prompt"
        placeholder="Enter a prompt to display an image"
      />
      <button type="submit">Go!</button>
    </form>

    {error && <div>{error}</div>}

    {prediction && (
      <div>
        {prediction.output && (
          <div>
            <Image
              fill
              src={prediction.output[prediction.output.length - 1]}
              alt="output"
              sizes="100vw"
            />
          </div>
        )}
        <p>status: {prediction.status}</p>
      </div>
    )}
  </div>
);
  • There is an input field to enter the prompt and a submit button.
  • If there is a prediction, the output is added to an image and presented as output.
  • There is a status field so that the user can follow the stage of Prediction. [Can be improved with loading circle or progress bar]

On the final front-end file:

import { FormEvent, useState } from "react";
import Head from "next/head";
import Image from "next/image";

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

interface Prediction {
  id: string;
  status: string;
  output: string[];
  detail: string;
}

export default function Home() {
  const [prediction, setPrediction] = useState<Prediction>();
  const [error, setError] = useState(null);

  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const response = await fetch("/api/predictions", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        prompt: e.currentTarget.prompt.value,
      }),
    });
    let prediction = await response.json();
    if (response.status !== 201) {
      setError(prediction.detail);
      return;
    }
    setPrediction(prediction);

    while (
      prediction.status !== "succeeded" &&
      prediction.status !== "failed"
    ) {
      await sleep(1000);
      const response = await fetch("/api/predictions/" + prediction.id);
      prediction = await response.json();
      if (response.status !== 200) {
        setError(prediction.detail);
        return;
      }
      console.log({ prediction });
      setPrediction(prediction);
    }
  };

  return (
    <div>
      <Head>
        <title>Replicate + Next.js</title>
      </Head>

      <form onSubmit={handleSubmit}>
        <input
          type="text"
          name="prompt"
          placeholder="Enter a prompt to display an image"
        />
        <button type="submit">Go!</button>
      </form>

      {error && <div>{error}</div>}

      {prediction && (
        <div>
          {prediction.output && (
            <div>
              <Image
                fill
                src={prediction.output[prediction.output.length - 1]}
                alt="output"
                sizes="100vw"
              />
            </div>
          )}
          <p>status: {prediction.status}</p>
        </div>
      )}
    </div>
  );
}

The console output of Prediction in below.

Console output image

However, it can reflect or use a lot of information to the user.

  1. Finalize! Congratulations!
Replicate generated image output

We got our first output by running the text-to-image generator model.