No cenário dinâmico da tecnologia, em que a inovação molda continuamente os limites do que é possível, a inteligência artificial (IA) nunca deixa de cativar nossa imaginação.

IA se refere à simulação de processos de inteligência humana em sistemas de computador. Esses processos incluem tarefas como aprendizado, raciocínio, solução de problemas, percepção, compreensão de linguagem e tomada de decisão.

Atualmente, indivíduos e empresas desenvolveram e treinaram vários modelos de IA para executar determinadas tarefas melhor do que os humanos em tempo real. Entre os inúmeros aplicativos de IA, uma área particularmente intrigante é a geração de imagens.

O que você está construindo

Este guia explica como criar um aplicativo React que se integra perfeitamente à API OpenAI DALL-E por meio de um backend Node.js e gera imagens cativantes com base em solicitações textuais.

Gerador de imagens IA em ação, produzindo imagens vívidas e criativas usando a API DALL-E.
Gerador de imagens IA em ação, produzindo imagens vívidas e criativas usando a API DALL-E.

Pré-requisitos

Para acompanhar este projeto, você deve ter:

O que é API OpenAI DALL-E?

A API da OpenAI é uma plataforma baseada em nuvem que concede aos desenvolvedores acesso aos modelos de IA pré-treinados da OpenAI, como o DALL-E e o GPT-3 (usamos esse modelo para criar um clone do ChatGPT com o código neste repositório Git). Ele permite que os desenvolvedores adicionem recursos de IA, como resumo, tradução, geração de imagens e modificação em seus programas sem precisar desenvolver e treinar seus próprios modelos.

Para usar a API da OpenAI, crie uma conta usando sua conta do Google ou e-mail no site da OpenAI e obtenha uma chave de API. Para gerar uma chave de API, clique em Personal (Pessoal) no canto superior direito do site e selecione View API keys (Exibir chaves de API).

O processo de build de uma chave secreta da API da OpenAI.
O processo de build de uma chave secreta da API da OpenAI.

Clique no botão Create new secret key (Criar nova chave secreta) e salve a chave em algum lugar. Você a usará neste aplicativo para interagir com a API DALL-E da OpenAI.

Configurando o ambiente de desenvolvimento

Você pode criar um aplicativo React do zero e desenvolver sua própria interface, ou pode pegar nosso template inicial do Git seguindo estas etapas:

  1. Visite o repositório do GitHub deste projeto.
  2. Selecione Use this template > Create a new repository para copiar o código inicial em um repositório dentro da sua conta GitHub. Marque a caixa para incluir todas as branches (include all branches).
  3. Puxe o repositório para o seu computador local e mude para o branch starter-files usando o comando: git checkout starter-files.
  4. Instale as dependências necessárias executando o comando npm install.

Após a conclusão da instalação, você pode iniciar o projeto em seu computador local com npm run start. Isso torna o projeto disponível em http://localhost:3000/.

Interface de usuário de um aplicativo gerador de imagens IA que mostra o poder da inteligência artificial na criação de imagens.
Interface de usuário de um aplicativo gerador de imagens IA que mostra o poder da inteligência artificial na criação de imagens.

Entendendo os arquivos de projeto

Neste projeto, adicionamos todas as dependências necessárias para o seu aplicativo React. Aqui está uma visão geral do que foi instalado:

  • file-server: Essa biblioteca utilitária simplifica o processo de download das imagens geradas. Está vinculada ao botão de download, garantindo uma experiência de usuário tranquila.
  • uuid: Essa biblioteca atribui uma identificação exclusiva a cada imagem. Isso evita qualquer possibilidade de as imagens compartilharem o mesmo nome de arquivo padrão, mantendo a ordem e a clareza.
  • react-icons: Integrada ao projeto, essa biblioteca incorpora ícones sem esforço, aprimorando o apelo visual do seu aplicativo.

No centro do seu aplicativo React está a pasta src. É nela que o código JavaScript essencial para o Webpack está hospedado. Vamos entender os arquivos e as pastas dentro da pasta src:

  • assets: Nesse diretório, você encontrará as imagens e o gif do carregador utilizados em todo o projeto.
  • data: Essa pasta contém um arquivo index.js que exporta um array de 30 prompts. Esses prompts podem ser usados para gerar imagens diversas e aleatórias. Você pode editá-lo à vontade.
  • index.css: É onde estão armazenados os estilos usados neste projeto.

Entendendo a pasta Utils

Dentro dessa pasta, o arquivo index.js define duas funções reutilizáveis. A primeira função randomiza a seleção de prompts que descrevem várias imagens que podem ser geradas.

import { randomPrompts } from '../data';

export const getRandomPrompt = () => {
    const randomIndex = Math.floor(Math.random() * randomPrompts.length);
    const randomPrompt = randomPrompts[randomIndex];

    return randomPrompt;
}

A segunda função lida com o download das imagens geradas, aproveitando a dependência file-saver (salvador de arquivos). Ambas as funções foram criadas para oferecer modularidade e eficiência, e podem ser convenientemente importadas para os componentes quando necessário.

import FileSaver from 'file-saver';
import { v4 as uuidv4 } from 'uuid';

export async function downloadImage(photo) {
    const _id = uuidv4();
    FileSaver.saveAs(photo, `download-${_id}.jpg`);
}

No código acima, a dependência uuid dá a cada arquivo de imagem gerado um ID exclusivo, para que eles não tenham o mesmo nome de arquivo.

Entendendo os componentes

Esses são pequenos blocos de código separados para facilitar a manutenção e a compreensão do código. Para este projeto, foram criados três componentes: Header.jsx, Footer.jsx e Form.jsx. O componente principal é o componente Form, no qual a entrada é recebida e passada para o arquivo App.jsx com a função generateImage adicionada como um evento onClick ao botão Generate Image.

No componente Form, é criado um estado para armazenar e atualizar o prompt. Além disso, um recurso permite que você clique em um ícone aleatório para gerar os prompts aleatórios. Isso é possível com a função handleRandomPrompt, que usa a função getRandomPrompt que você já configurou. Quando você clica no ícone, ela obtém um prompt aleatório e atualiza o estado com ele:

const handleRandomPrompt = () => {
    const randomPrompt = getRandomPrompt();
    setPrompt(randomPrompt)
}

Entendendo o arquivo App.jsx

É aqui que reside a maior parte do código. Todos os componentes são reunidos aqui. Há também uma área designada para exibir a imagem gerada. Se nenhuma imagem tiver sido gerada ainda, será exibida uma imagem de espaço reservado (Preview image).

Dentro desse arquivo, dois estados são gerenciados:

  • isGenerating: Mantém o controle sobre se uma imagem está sendo gerada no momento. Por padrão, ele é definido como falso.
  • generatedImage: Esse estado armazena informações sobre a imagem que foi gerada.

Além disso, a função do utilitário downloadImage é importada, permitindo que você acione o download da imagem gerada quando clicar no botão Download:

<button
    className="btn"
    onClick={() => downloadImage(generatedImage.photo)}
>

Agora que você entendeu os arquivos iniciais e configurou o projeto, vamos começar a lidar com a lógica desse aplicativo.

Geração de imagens com a API DALL-E da OpenAI

Para aproveitar os recursos da API DALL-E da OpenAI, você usará o Node.js para estabelecer um servidor. Você criará uma rota POST dentro desse servidor. Essa rota será responsável por receber o texto do prompt enviado pelo seu aplicativo React e, em seguida, utilizá-lo para gerar uma imagem.

Para começar, instale as dependências necessárias no diretório do seu projeto executando o seguinte comando:

npm i express cors openai

Além disso, instale as seguintes dependências como dependências de desenvolvimento. Essas ferramentas ajudarão você a configurar seu servidor Node.js:

npm i -D dotenv nodemon

As dependências instaladas são explicadas a seguir:

  • express: Essa biblioteca ajuda a criar um servidor no Node.js.
  • cors: O CORS facilita a comunicação segura entre domínios diferentes.
  • openai: Essa dependência concede a você acesso à API DALL-E da OpenAI.
  • dotenv: O dotenv o ajuda a gerenciar variáveis de ambiente.
  • nodemon: É uma ferramenta de desenvolvimento que monitora as alterações em seus arquivos e reinicia automaticamente o servidor.

Quando as instalações forem bem-sucedidas, crie um arquivo server.js na raiz do seu projeto. É nele que todo o código do servidor será armazenado.

No arquivo server.js, importe as bibliotecas que você acabou de instalar e as instancie:

// Import the necessary libraries
const express = require('express');
const cors = require('cors');
require('dotenv').config();
const OpenAI = require('openai');

// Create an instance of the Express application
const app = express();

// Enable Cross-Origin Resource Sharing (CORS)
app.use(cors());

// Configure Express to parse JSON data and set a data limit
app.use(express.json({ limit: '50mb' }));

// Create an instance of the OpenAI class and provide your API key
const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY,
});

// Define a function to start the server
const startServer = async () => {
    app.listen(8080, () => console.log('Server started on port 8080'));
};

// Call the startServer function to begin listening on the specified port
startServer();

No código acima, você importa as bibliotecas necessárias. Em seguida, estabelece uma instância do aplicativo Express usando const app = express();. Depois disso, habilite o CORS. Em seguida, o Express é configurado para processar dados JSON de entrada, especificando um limite de tamanho de dados de 50mb.

Depois disso, uma instância da classe OpenAI é criada utilizando sua chave de API OpenAI. Crie um arquivo .env na raiz do seu projeto e adicione sua chave de API usando a variável OPENAI_API_KEY. Por fim, você define uma função assíncrona startServer e a chama para colocar o servidor em movimento.

Agora você configurou o arquivo server.js. Vamos criar uma rota POST que você pode usar em seu aplicativo React para interagir com esse servidor:

app.post('/api', async (req, res) => {
    try {
        const { prompt } = req.body;
        const response = await openai.images.generate({
            prompt,
            n: 1,
            size: '1024x1024',
            response_format: 'b64_json',
        });
        const image = response.data[0].b64_json;
        res.status(200).json({ photo: image });
    } catch (error) {
        console.error(error);
    }
});

Neste código, a rota está definida como /api e foi projetada para lidar com solicitações POST de entrada. Dentro da função de callback da rota, você recebe os dados enviados do seu aplicativo React usando req.body — especificamente o valor prompt.

Em seguida, o método images.generate da biblioteca OpenAI é chamado. Esse método recebe o prompt fornecido e gera uma imagem em resposta. Parâmetros como n determinam o número de imagens a serem geradas (aqui, apenas uma), size especifica as dimensões da imagem e response_format indica o formato no qual a resposta deve ser fornecida (b64_json, neste caso).

Após gerar a imagem, você extrai os dados da imagem da resposta e os armazena na variável image. Em seguida, você envia uma resposta JSON de volta ao aplicativo React com os dados da imagem gerada, definindo o status HTTP como 200 (indicando sucesso) usando res.status(200).json({ photo: image }).

Em caso de erros durante esse processo, o código no bloco catch é executado, registrando o erro no console para depuração.

Agora o servidor está pronto! Vamos especificar o comando que será usado para executar nosso servidor no objeto scripts do arquivo package.json:

"scripts": {
  "dev:frontend": "react-scripts start",
  "dev:backend": "nodemon server.js",
  "build": "react-scripts build",
},

Agora, quando você executar npm run dev:backend, seu servidor será iniciado em http://localhost:8080/, ao passo que, se você executar npm run dev:frontend, seu aplicativo React será iniciado em http://localhost:3000/. Certifique-se de que ambos estejam sendo executados em terminais diferentes.

Fazer solicitações HTTP do React para o servidor Node.js

No arquivo App.jsx, você criará uma função generateImage que é acionada quando o botão Generate Image é clicado no componente Form.jsx. Essa função aceita dois parâmetros: prompt e setPrompt do componente Form.jsx.

Na função generateImage, você fará uma solicitação HTTP POST para o servidor Node.js:

const generateImage = async (prompt, setPrompt) => {
    if (prompt) {
        try {
            setIsGenerating(true);
            const response = await fetch(
                'http://localhost:8080/api',
                {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        prompt,
                    }),
                }
            );
            const data = await response.json();
            setGeneratedImage({
                photo: `data:image/jpeg;base64,${data.photo}`,
                altText: prompt,
            });
        } catch (err) {
            alert(err);
        } finally {
            setPrompt('');
            setIsGenerating(false);
        }
    } else {
        alert('Please provide proper prompt');
    }
};

No código acima, você verifica se o parâmetro prompt tem um valor e, em seguida, define o estado isGenerating como true, pois a operação está sendo iniciada. Isso fará com que o loader seja exibido na tela porque, no arquivo App.jsx, temos este código que controla a exibição do loader:

{isGenerating && (
    <div> className="loader-comp">
        <img src={Loader} alt="" className='loader-img' />
    </div>
)}

Em seguida, use o método fetch() para fazer uma solicitação POST ao servidor usando http://localhost:8080/api — é por isso que instalamos o CORS, pois estamos interagindo com uma API em outra URL. Usamos o prompt como o corpo da mensagem. Em seguida, extraia a resposta retornada do servidor Node.js e defina-a como o estado generatedImage.

Quando o estado generatedImage tiver um valor, a imagem será exibida:

{generatedImage.photo ? (
    <img
        src={generatedImage.photo}
        alt={generatedImage.altText}
        className="imgg ai-img"
    />
) : (
    <img
        src={preview}
        alt="preview"
        className="imgg preview-img"
    />
)}

É assim que você verá o arquivo App.jsx completo:

import { Form, Footer, Header } from './components';
import preview from './assets/preview.png';
import Loader from './assets/loader-3.gif'
import { downloadImage } from './utils';
import { useState } from 'react';

const App = () => {
    const [isGenerating, setIsGenerating] = useState(false);
    const [generatedImage, setGeneratedImage] = useState({
        photo: null,
        altText: null,
    });

    const generateImage = async (prompt, setPrompt) => {
        if (prompt) {
            try {
                setIsGenerating(true);
                const response = await fetch(
                    'http://localhost:8080/api',
                    {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                        },
                        body: JSON.stringify({
                            prompt,
                        }),
                    }
                );
                const data = await response.json();
                setGeneratedImage({
                    photo: `data:image/jpeg;base64,${data.photo}`,
                    altText: prompt,
                });
            } catch (err) {
                alert(err);
            } finally {
                setPrompt('');
                setIsGenerating(false);
            }
        } else {
            alert('Please provide proper prompt');
        }
    };

    return (
        <div className='container'>
            <Header />
            <main className="flex-container">
                <Form generateImage={generateImage} prompt={prompt} />
                <div className="image-container">
                    {generatedImage.photo ? (
                        <img
                            src={generatedImage.photo}
                            alt={generatedImage.altText}
                            className="imgg ai-img"
                        />
                    ) : (
                        <img
                            src={preview}
                            alt="preview"
                            className="imgg preview-img"
                        />
                    )}
                    {isGenerating && (
                        <div className="loader-comp">
                            <img src={Loader} alt="" className='loader-img' />
                        </div>
                    )}
                    <button
                        className="btn"
                        onClick={() => downloadImage(generatedImage.photo)}
                    >
                        Download
                    </button>
                </div>
            </main>
            <Footer />
        </div>
    );
};

export default App;

Implantar seu aplicativo full-stack na Kinsta

Até agora, você criou com sucesso um aplicativo React que interage com o Node.js, o que o torna um aplicativo full-stack. Agora vamos implantar esse aplicativo na Kinsta.

Primeiro, configure o servidor para servir os arquivos estáticos gerados durante o processo de build do aplicativo React. Para isso, você deve importar o módulo path e usá-lo para servir os arquivos estáticos:

const path = require('path');

app.use(express.static(path.resolve(__dirname, './build')));

Quando você executar o comando npm run build && npm run dev:backend, seu aplicativo React de full-stack será carregado em http://localhost:8080/. Isso ocorre porque o aplicativo React é compilado em arquivos estáticos dentro da pasta build. Esses arquivos são então incorporados ao seu servidor Node.js como um diretório estático. Consequentemente, quando você executar o servidor Node, o aplicativo estará acessível.

Antes de implementar o código no provedor Git que você escolheu (Bitbucket, GitHub ou GitLab), lembre-se de modificar a URL de solicitação HTTP no arquivo App.jsx. Altere http://localhost:8080/api para /api, pois a URL será anexada.

Por fim, no seu arquivo package.json, adicione um comando de script para o servidor Node.js que seria usado para a implantação:

"scripts": {
  // …
  "start": "node server.js",
},

Em seguida envie seu código para o provedor Git de sua preferência e implantar seu repositório na Kinsta seguindo estas etapas:

  1. Faça login em sua conta Kinsta no painel MyKinsta.
  2. Selecione Aplicativo na barra lateral esquerda e clique no botão Adicionar aplicativo.
  3. No modal que aparece, escolha o repositório que você deseja implantar. Se você tiver vários branches, poderá selecionar o branch desejado e dar um nome ao seu aplicativo.
  4. Selecione um dos locais de centros de dados disponíveis.
  5. Adicione o endereço OPENAI_API_KEY como uma variável de ambiente. A Kinsta configurará um Dockerfile automaticamente para você.
  6. Por fim, no campo de comando start, adicione npm run build && npm run start. A Kinsta instalará as dependências do seu aplicativo a partir do package.json e, em seguida, criará e implantará seu aplicativo.

Resumo

Neste guia você aprendeu a aproveitar o poder da API DALL-E da OpenAI para geração de imagens. Também aprendeu a trabalhar com React e Node.js para criar um aplicativo full-stack básico.

As possibilidades são infinitas com a IA, pois novos modelos são introduzidos diariamente, e você pode criar projetos incríveis que podem ser implementados na hospedagem de aplicativos da Kinsta.

Que modelo você gostaria de explorar e sobre qual projeto gostaria que escrevêssemos a seguir? Compartilhe nos comentários abaixo.

Joel Olawanle Kinsta

Joel is a Frontend developer working at Kinsta as a Technical Editor. He is a passionate teacher with love for open source and has written over 200 technical articles majorly around JavaScript and it's frameworks.