Build A Stunning Generative AI App with a React IDE

Build A Stunning Generative AI App with a React IDE

If you've ever wondered how AI applications generate images from user prompts, you've come to the right place.

In this article, you will learn how these tools work by building an AI application to generate images.

Create the application

We'll start by creating a React + TypeScript application using Vite. Run the following command in your terminal:

npm create vite@latest

You'll then have to answer three questions so Vite understands how to configure your project.

✔ Project name: aimages
✔ Select a framework: › React
✔ Select a variant: › TypeScript

Lastly, you need to install and configure TailwindCSS.

Run the 2 commands below (in that order). The first command installs tailwindcss and its peer dependencies. The second command generates the tailwind.config.js and postcss.config.js files.

npm install -D tailwindcss postcss autoprefixer

npx tailwindcss init -p

Now open the tailwind.config.js file and modify the code as follows:

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

The above code configures TailwindCSS, specifying which files the framework should scan for class usage. It's set to check the index.html file and any JavaScript or TypeScript files in the src directory. It also lets you customize the theme, colors, fonts, and many other things.

Lastly, open /src/styles.css and add the TailwindCSS directives at the top of the file:

@tailwind base;
@tailwind components;
@tailwind utilities;

You're done with the project configuration! Now, upload the project on GitHub before proceeding to the next stage. You'll see later why!

The problem with building user interfaces

Before going further, let's highlight the main issues with building user interfaces.

One of the main issues with building user interfaces is that you don't have live visual feedback when building them. You make the changes in your editor, save them, and preview them in the browser. Then, if you're not happy with the changes, you have to repeat the process.

That results in a lot of switching between the IDE and the browser. While it might seem like a minor inconvenience, those seconds of waiting for the browser to refresh, plus the mental shift required, add up over time. Over the course of a day, this switching results in a substantial amount of wasted time.

It also results in a break of flow. Every disruption (like switching between applications) breaks the flow, leading to longer task completion times and possibly more errors.

On top of that, collaborating with others can be tedious and time-consuming. The usual way of collaborating involves working on the app, pushing the changes to GitHub, and deploying them. Only after that can the other collaborators check your work. That results in a substantial amount of time lost on unproductive things, such as waiting for the changes to deploy. So, the traditional approach can be quite cumbersome and inefficient.

In this article, you'll see a complementary tool you can use with your favorite IDE to build user interfaces better and faster.

A visual React IDE

Codux is a free visual IDE for React + TypeScript applications. It enables you to work on the projects in real-time through a visual editor. All the changes you make to the component's properties, styles, and structure (JSX) are reflected automatically on the screen.

Important mention: Codux is not meant to replace your favourite IDE. It's a complementary tool that you can use alongside your IDE.

You can build and modify your interfaces using the panels and controllers provided by Codux. As the image shows, you can set CSS properties & classes and different element states, for example.

Codux Interface

And, of course, you also have the code editor at the bottom if you want to make specific changes by writing code.

Note: Whether you make the changes using the visual editor, the built-in code editor, or your usual IDE, they will always be in sync!

Codux also supports all the popular styling solutions such as CSS, Sass, CSS/Sass modules, Stylable, and TailwindCSS. The application we're building uses TailwindCSS, for example.

Component playground

Another cool feature of Codux is the playground feature that enables you to share your work (components) with others.

In the playground mode, people can view and edit your components without setting up a local development environment and the other prerequisites.

Let's see it in action. At the beginning of the article, I mentioned that you must upload the project on GitHub. The reason is that the Playground requires your project to be hosted on GitHub.

To share your board (or component), click the "Share" button on the right.

Mention: The shared board is public, which means anyone with the link can see it. Be careful.

Share Project Codux

Codux will then ask you to create a link name for the board. Choose a name and click the button "Create Link" (the first time you do it) and "Publish" (afterwards).

Mention: Each time you make a change, you need to click the "Update Link" button before sharing the board. Otherwise, your changes won't be applied & visible on the shared board.

Share Project Codux - Customize Sharing Link

You can now copy the link and share it with other people. When they access the link, they'll see the online version of Codux (see the picture below).

Shared Project Codux

Anyone with the link to the shared board can see it and make changes, including the text, colors, images, and even the code. That makes it an ideal tool for collaborating, where everyone can share & apply their feedback in a simple and direct way.

This way, developers can focus on the work that matters rather than wasting valuable time pushing and pulling changes to/from each other.

It's important to mention, though, that the changes on the shared board don't affect the original board and won't be applied to it. That would be a cool feature, though!

Add the project to Codux

Let's continue by importing the project we created earlier into Codux. Click on the "Open Local Project" option and select the project folder.

Import a local project in Codux

After that, Codux prompts you to run the configuration scripts, which install the required package to work on the project in this IDE.

Run the configuration scripts for a newly imported project in Codux

Once the installation is done, click "Scan for components" so Codux imports all the components.

Scan for the project components

Then, you can see all the available components in the sidebar. In our case, we only have one - App - since it's a new project.

You can create new components by clicking the "+ New Component" button, but more on that later.

Components page in Codux

As you might've observed, there are "components" and "boards". A "Board" is a concept introduced by Codux, and it represents a fixture for your components so that Codux can automatically render it and allow you to edit it visually.

To create a "board", click on the component and then click the button "+ Create Board". That'll open a menu where you can set the starting point, the board name, and the board path.

You can leave the default options and click "Create".

Create a board based on a component in Codux

And this is the board view, where you can edit the components visually.

The board view in Codux

Configure TailwindCSS

For TailwindCSS to work in Codux, you need to perform some customizations in addition to the Tailwind setup process you did at the beginning of the article.

Create the codux.config.js in the root folder of the project, and add the following object:

{
  "$schema": "https://wixplosives.github.io/codux-config-schema/codux.config.schema.json",
  "boardGlobalSetup": "./src/_codux/board-global-setup.ts"
}

Then, create the board-global-setup.ts in the ./src/_codux folder and add the following import:

import "../index.css";

Done! Now, you can see the TailwindCSS classes in the Codux IDE.

For more information about this configuration, check this link.

Build the app

In the following sections, we'll start building the application.

Create the components folder

Let's start by creating a component folder for all the components. You can either do it from your IDE, terminal, or Codux. This is how you can do it from Codux:

Create components in Codux

Click the highlighted icon to see the project files. After that, create the folder as you would create it on your machine. Right-click on the src folder and click New Folder.

Creating components from Codux

If you want to create components using the interactive process from Codux, you need to set the components path. You can do that by adding this code in codux.config.json:

{
  "$schema": "https://wixplosives.github.io/codux-config-schema/codux.config.schema.json",
  "boardGlobalSetup": "./src/_codux/board-global-setup.ts",
  "newComponent": {
    "componentsPath": "src/components"
  }
}

Now, you can create components using the visual interface.

Layout component

Create the Layout component in the components folder. We'll use the layout on all the pages.

You can follow the next steps using your usual IDE or Codux.

Tip 💡: You're fine either way because the code is automatically in sync between the two. Whether you use Codux or your editor, both will contain the same code without you having to do anything. That's neat!

Now open the Layout component and add the following code:

type LayoutProps = {
  children: React.ReactNode;
};

export default function Layout({ children }: LayoutProps) {
  return (
    <>
      <main>{children}</main>
    </>
  );
}

Refactor App.tsx

Then go to the App.tsx file and replace the content with the following code:

import Layout from "./components/Layout";
import "./App.css";
import { useState } from "react";

function App() {
  const [inputValue, setInputValue] = useState("");
  const [imageURL, setImageURL] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<null | string>(null);

  const fetchImage = async (prompt: string) => {
    const response = await fetch("http://localhost:3000/api/genimg", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ value: prompt }),
    });

    if (response.ok) {
      const data = await response.json();

      if (Array.isArray(data) && data.length > 0) {
        return data[0];
      }

      throw new Error("Unexpected server response");
    } else {
      throw new Error(response.statusText);
    }
  };

  const onInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    setInputValue(e.target.value);
  };

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setLoading(true);
    setError(null);

    try {
      const url = await fetchImage(inputValue);
      setImageURL(url);
    } catch (err) {
      if (err instanceof Error) {
        setError(err.message);
      } else {
        setError("An unexpected error occurred");
      }
    } finally {
      setLoading(false);
    }
  };

  return (
    <>
      <Layout>
        <section>
          <div>
            <div>
              <h1>PromptPix AI</h1>
              <p>
                Dive into the world of AI-driven creativity with PromptPix AI,
                where your words become vivid visuals. Simply input your idea,
                and watch as our advanced algorithms craft the image you
                envisioned.
              </p>
            </div>
            <form onSubmit={handleSubmit}>
              <div>
                <label htmlFor="prompt">Image prompt</label>
                <textarea
                  id="prompt"
                  placeholder="Enter your prompt"
                  value={inputValue}
                  onChange={onInputChange}
                />
              </div>
              <button type="submit" disabled={inputValue.length === 0}>
                Generate
              </button>
            </form>
          </div>
          <div>
            {loading && (
              <div>
                <p>Your image is being generated...</p>
                <div>
                  <div>
                    <div>
                      <svg
                        xmlns="http://www.w3.org/2000/svg"
                        width="44"
                        height="44"
                        viewBox="0 0 24 24"
                        strokeWidth="2"
                        stroke="currentColor"
                        fill="none"
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      >
                        <path
                          stroke="none"
                          d="M0 0h24v24H0z"
                          fill="none"
                        ></path>
                        <path d="M12 6l0 -3"></path>
                        <path d="M16.25 7.75l2.15 -2.15"></path>
                        <path d="M18 12l3 0"></path>
                        <path d="M16.25 16.25l2.15 2.15"></path>
                        <path d="M12 18l0 3"></path>
                        <path d="M7.75 16.25l-2.15 2.15"></path>
                        <path d="M6 12l-3 0"></path>
                        <path d="M7.75 7.75l-2.15 -2.15"></path>
                      </svg>
                    </div>
                  </div>
                </div>
              </div>
            )}

            {imageURL && !loading && (
              <div>
                <p>Find your generated image below</p>
                <img
                  src={imageURL}
                  width={600}
                  height={600}
                  alt="Generated image"
                />
              </div>
            )}

            {!imageURL && !loading && (
              <div>
                <p>Your image will appear below</p>
                <div>
                  <div>
                    <div>
                      <svg
                        xmlns="http://www.w3.org/2000/svg"
                        width="44"
                        height="44"
                        viewBox="0 0 24 24"
                        strokeWidth="2"
                        stroke="currentColor"
                        fill="none"
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      >
                        <path
                          stroke="none"
                          d="M0 0h24v24H0z"
                          fill="none"
                        ></path>
                        <path d="M15 8h.01"></path>
                        <path d="M11.5 21h-5.5a3 3 0 0 1 -3 -3v-12a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v5.5"></path>
                        <path d="M18 18m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"></path>
                        <path d="M20.2 20.2l1.8 1.8"></path>
                        <path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l2 2"></path>
                      </svg>
                    </div>
                  </div>
                </div>
              </div>
            )}

            {error && <div>{error}</div>}
          </div>
        </section>
      </Layout>
    </>
  );
}

export default App;

This component contains the barebone application without any style applied.

The app preview in Codux

Note: Everything is in the App file for illustrative purposes. It's easier to showcase the tool this way. You'll see the final application on GitHub.

Style the app with Codux

Let's start by styling the Layout component first. Double click App in the Elements tab (left-hand side column).

That opens a new tree, where you should see the Layout component. Double click on Layout, and then select main.

The Elements tab shows you all the HTML elements from your code.

Main div

To add Tailwind classes to HTML elements, click the "Properties" icon from the right-hand side. Then, search for the "className" property.

Once there, add the following Tailwind classes:

flex min-h-screen flex-col justify-center items-center

These classes set up a flex container with a minimum height equal to the viewport height, arrange its children in a column, and center them vertically and horizontally within the container.

You should see the changes reflected immediately, both visually and in the code.

Applying Tailwind classes in Codux

Pretty cool, right?

Now, let's switch to your favorite IDE to see how easy it is to use them together. Open the index.css file and add this block:

body {
  @apply bg-gray-50;
}

App component

Continue by adding the following classes to the section element from the App component:

container flex flex-col gap-6 py-8 md:max-w-[64rem] md:py-12 lg:py-24

These classes modify the section to be a flexible container with column direction, vertical gap between child elements of 6 units, vertical padding of 8 units (increased to 12 units on medium screens and 24 units on large screens), and a maximum width of 64rem on medium and larger screens.

Applying Tailwind classes in Codux

Similarly, apply the following classes to the first div element:

bg-white shadow-md rounded border border-slate-200 p-14 text-center

These TailwindCSS utility classes apply a white background, a medium shadow, rounded corners, a slate-colored border, padding on all sides of 14 units, and center-aligned text.

Applying Tailwind classes in Codux

This is how the application looks up to this point.

A screenshot of the Vite + React + TypeScript app built with Codux

The next steps are as follows:

  • stylize the heading + description

  • stylize the form

  • stylize the part where the image will appear

  • create the API

Let's style the heading and description through your IDE (VS Code, in my case) to see the auto-sync in action.

Replace the 2nd div element with the following code:

<div className="mx-auto flex w-full flex-col md:max-w-[58rem]">
   <h1 className="font-heading text-2xl mb-4 sm:text-4xl text-center">
      PromptPix AI
   </h1>
   <p className="text-sm sm:text-base text-center mb-4">
      Dive into the world of AI-driven creativity with PromptPix AI,
      where your words become vivid visuals. Simply input your idea,
      and watch as our advanced algorithms craft the image you
      envisioned.
   </p>
</div>

The screenshot below illustrates how your code should look:

Cursor.sh screenshot of the code built with Codux

If you navigate back to Codux, you will see the changes applied in the visual area and the code editor from the bottom of the page.

Similarly, all the changes made in Codux are available in your IDE.

Codux screenshot

As an exercise, add the following classes to these elements:

  • the form element - space-y-6

  • the div element inside the form - flex flex-col items-center gap-4

  • the label element - text-sm sm:text-base

  • the textarea element - border border-slate-200 rounded p-2 w-3/4 h-32 resize-none

  • the button element:

`${
inputValue.length === 0
? "bg-white text-gray-800 font-semibold py-2 px-4 border border-slate-400 rounded shadow opacity-50 hover:opacity-50 cursor-not-allowed"
: "bg-white hover:bg-slate-100 text-gray-800 font-semibold py-2 px-4 border border-slate-400 rounded shadow"
}`

This is how the application looks like with all the above classes applied:

Screenshot of Codux running React, TypeScript and TailwindCSS

Looks pretty good already, right?

The last step involves styling the part where the generated image will appear. We'll make the initial div element (that's displayed before entering any prompt) a flex container with its children arranged in a column, centered horizontally, with a gap of 24px between each child and a top margin of 48px - flex flex-col items-center gap-6 mt-12.

You can add them as usual using the "className" field from Codux.

Codux TailwindCSS classes

Apply the same Tailwind classes to the other 2 div elements that display the loading state and the generated image.

Lastly, stylize the error:

{error && (
<div className="text-center w-1/2 mx-auto bg-red-500 mt-2 rounded border border-red-800">
   {error}
</div>
)}

Now you're done with the user interface!

Codux offers much more

In building this application, you mostly applied classes and visualized the results in real-time. However, Codux offers much more.

With Codux, you can test your application on multiple viewports and make the changes live for each viewport.

Codux responsiveness feature

Besides that, we added the classes manually for each element. But you can also use the "Computed Styles" tab to stylize the elements. Instead of writing code manually, you can style elements interactively.

Codux computed styles

A favorite feature of mine is the ability to open the code for a specific element. You can select the particular element, right-click on it, and open it in the code editor/IDE.

The image illustrates how Codux highlights the relevant code for the selected element. Neat, right?

Open code from Codux

As you can see, there are lots of cool features in Codux. I encourage you to download it and explore it. It's free!

Some valuable resources to help you:

The Backend Code for Generating the Image

You are probably using a different programming language & framework than me. Instead of wasting your time taking you through a painful process of building the backend in a technology you're not interested in, I'll share the code for the API route straightaway.

import express, { Request, Response } from "express";
import Replicate from "replicate";
import cors from "cors";

const app = express();
const port = 3030;

app.use(express.json());
app.use(cors());

app.options("/api/genimg", (req: Request, res: Response) => {
  // Pre-flight request. Reply successfully:
  res.status(200).end();
});

app.post("/api/genimg", async (req: Request, res: Response) => {
  const { value } = req.body;

  try {
    const replicate = new Replicate({
      auth: process.env.REPLICATE_API_TOKEN || "",
    });

    const result = await replicate.run(
      "stability-ai/stable-diffusion:a00d0b7dcbb9c3fbb34ba87d2d5b46c56969c84a628bf778a7fdaec30b1b99c5",
      {
        input: {
          prompt: value,
          image_dimensions: "1024x1024",
          num_inference_steps: 50,
          num_outputs: 1,
          guideance_scale: 7.5,
          prompt_strength: 0.8,
          scheduler: "KarrasDPM",
        },
      }
    );

    res.status(200).json(result);
  } catch (error) {
    console.error("Failed to generate image. Error:", error);
    res.status(500).json({ message: "Failed to generate image" });
  }
});

app.all("/api/genimg", (req: Request, res: Response) => {
  res.status(405).json({ message: "Method Not Allowed" });
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

This TypeScript code sets up an Express server that listens on port 3030 and has CORS enabled. It defines three routes, all for the path "/api/genimg". The OPTIONS route is for handling pre-flight CORS requests. The POST route accepts a JSON body with a "value" property, uses it as a prompt to generate an image using the Replicate API, and returns the result. If there's an error during this process, it logs it and returns a 500 status code with a failure message. The ALL route is a catch-all that returns a 405 status code, indicating that the method used is not allowed. The server listens on the specified port and logs a message to the console when it's running.

This way, you can adapt the code to your programming language & framework of your choice.

Note: You can check the whole application code (both server and client) by accessing this GitHub link.

An ending note

Building applications comes with challenges that can slow down the development process and make collaboration difficult. One of the main challenges is the constant need to switch between different tools. That takes up considerable time and can break your concentration and flow. Moreover, collaborating with others is cumbersome, often requiring several steps before others can test your work.

This is where Codux steps in to make the whole process smoother and more efficient. Codux enables you to visualize changes as you make them without constantly switching between multiple tools. Moreover, it offers a visual user interface that makes it easier to build applications by allowing you to create things with a couple of clicks. But perhaps most importantly, it allows for real-time collaboration, enabling people to work together seamlessly.

Before closing, I want to mention again that Codux is not here to replace your IDE and other tools you might use. Think of Codux as another tool in your developer toolbox.

Disclaimer: This article is sponsored by Wix.

The article was originally published on my blog - catalins.tech