Nel dinamico panorama della tecnologia, dove l’innovazione modella continuamente i confini del possibile, l’intelligenza artificiale (AI, Artificial Intelligence) non smette mai di affascinare.

L’AI si riferisce alla simulazione dei processi di intelligenza umana da parte dei sistemi informatici. Questi processi includono compiti come l’apprendimento, il ragionamento, la risoluzione di problemi, la percezione, la comprensione del linguaggio e il processo decisionale.

Oggi, persone e aziende hanno sviluppato e addestrato diversi modelli di AI per svolgere determinati compiti meglio degli esseri umani in tempo reale. Tra la miriade di applicazioni dell’AI, un’area particolarmente interessante è quella della generazione di immagini tramite l’AI.

Cosa costruiremo

Questa guida spiega come costruire un’applicazione React che si integri perfettamente con l’API OpenAI DALL-E tramite un backend Node.js e che generi immagini accattivanti sulla base di richieste testuali.

Animazione di un generatore di immagini con intelligenza artificiale, che può produrre immagini vivide e creative grazie all’API di DALL-E
Generatore di immagini AI in azione, che produce immagini vivaci e creative utilizzando l’API DALL-E.

Prerequisiti

Per seguire questo progetto, è necessario avere:

Cos’è l’API OpenAI DALL-E?

L’API di OpenAI è una piattaforma basata sul cloud che consente a chi sviluppa di accedere ai modelli di intelligenza artificiale pre-addestrati di OpenAI, come DALL-E e GPT-3 (abbiamo usato questo modello per costruire un clone di ChatGPT con il codice presente in questo repository Git). Permette di aggiungere ai programmi funzioni di intelligenza artificiale come la sintesi, la traduzione, la generazione di immagini e la modifica senza dover sviluppare e addestrare i propri modelli.

Per usare OpenAI API, create un account usando il vostro account Google o la vostra email sul sito web di OpenAI e ottenete una chiave API. Per generare una chiave API, fate clic su Personal nell’angolo in alto a destra del sito web, quindi selezionate View API keys.

Schermata dell’API OpenAI da cui creare una chiave segreta
Il processo di creazione di una chiave segreta con l’API OpenAI.

Fate clic sul pulsante Create new secret key e salvate la chiave da qualche parte. La userete in questa applicazione per interagire con l’API OpenAI DALL-E.

Impostazione dell’ambiente di sviluppo

Potete creare un’applicazione React da zero e sviluppare la vostra interfaccia, oppure potete prendere il nostro modello di partenza Git seguendo questi passaggi:

  1. Visitate il repository GitHub di questo progetto.
  2. Selezionate Use this template > Create a new repository per copiare il codice di partenza in un repository del vostro account GitHub (selezionate la casella per includere tutti i branch).
  3. Estraete il repository sul vostro computer locale e passate al branch starter-files usando il comando: git checkout starter-files.
  1. Installate le dipendenze necessarie eseguendo il comando npm install.

Una volta completata l’installazione, potete lanciare il progetto sul vostro computer locale con npm run start. In questo modo il progetto sarà disponibile all’indirizzo http://localhost:3000/.

Interfaccia utente di un'applicazione di generazione di immagini AI
Interfaccia utente di un’applicazione di generazione di immagini AI che mostra la potenza dell’intelligenza artificiale nella creazione di immagini.

I file del progetto

In questo progetto abbiamo aggiunto tutte le dipendenze necessarie per la vostra applicazione React. Ecco una panoramica di ciò che è stato installato:

  • file-server: questa libreria di utility semplifica il processo di download delle immagini generate. È collegata al pulsante di download per garantire un’esperienza d’uso fluida.
  • uuid: questa libreria assegna un’identificazione unica a ogni immagine. In questo modo si evita che le immagini condividano lo stesso nome di file predefinito, mantenendo ordine e chiarezza.
  • react-icons: integrata nel progetto, questa libreria incorpora senza sforzo le icone, migliorando l’aspetto visivo della vostra applicazione.

Al centro della vostra applicazione React si trova la cartella src. È qui che si trova il codice JavaScript essenziale per Webpack. Vediamo quali sono i file e le cartelle della cartella src:

  • assets: all’interno di questa cartella si trovano le immagini e le gif del loader che vengono utilizzate in tutto il progetto.
  • data: questa cartella contiene un file index.js che esporta un array di 30 prompt. Questi prompt possono essere utilizzati per generare immagini diverse e casuali. Potete modificarli liberamente.
  • index.css: qui sono memorizzati gli stili utilizzati in questo progetto.

La cartella Utils

All’interno di questa cartella, il file index.js definisce due funzioni riutilizzabili. La prima funzione randomizza la selezione dei messaggi che descrivono le varie immagini che possono essere generate.

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

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

	return randomPrompt;
}

La seconda funzione gestisce il download delle immagini generate sfruttando la dipendenza dal file-saver. Entrambe le funzioni sono state create per offrire modularità ed efficienza e possono essere importate comodamente nei componenti quando necessario.

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`);
}

Nel codice qui sopra, la dipendenza uuid dà a ogni file immagine generato un ID unico, in modo che non abbiano lo stesso nome di file.

I componenti

Si tratta di piccoli blocchi di codice separati per facilitare la manutenzione e la comprensione del codice. Per questo progetto sono stati creati tre componenti: Header.jsx, Footer.jsx e Form.jsx. Il componente principale è il componente Form, dove l’input viene ricevuto e passato al file App.jsx con la funzione generateImage aggiunta come evento onClick al pulsante Generate Image.

Nel componente Form viene creato uno stato per memorizzare e aggiornare il prompt. Inoltre, una funzione vi permette di fare clic su un’icona casuale per generare i messaggi casuali. Questo è possibile grazie alla funzione handleRandomPrompt, che usa la funzione getRandomPrompt già impostata. Quando fate clic sull’icona, viene recuperato un messaggio casuale e lo stato viene aggiornato:

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

Il file App.jsx

È qui che risiede la maggior parte del codice. Tutti i componenti sono riuniti qui. C’è anche un’area designata per visualizzare l’immagine generata. Se non è stata generata alcuna immagine, viene visualizzata un’immagine segnaposto (immagine di anteprima).

All’interno di questo file vengono gestiti due stati:

  • isGenerating: tiene traccia del fatto che sia in corso la generazione di un’immagine. Per impostazione predefinita, è impostato su false.
  • generatedImage: questo stato memorizza le informazioni sull’immagine che è stata generata.

Inoltre, viene importata la funzione di utilità downloadImage che vi permette di attivare il download dell’immagine generata quando fate clic sul pulsante Download:

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

Ora che abbiamo analizzato i file di partenza e abbiamo configurato il progetto, iniziamo a gestire la logica di questa applicazione.

Generare immagini con l’API OpenAI DALL-E

Per sfruttare le funzionalità dell’API OpenAI DALL-E, userete Node.js per creare un server. All’interno di questo server, create una route POST. Questa route sarà responsabile di ricevere il testo di richiesta inviato dalla vostra applicazione React e di utilizzarlo per generare un’immagine.

Per iniziare, installate le dipendenze necessarie nella cartella del progetto eseguendo il seguente comando:

npm i express cors openai

Inoltre, installate le seguenti dipendenze come dipendenze dev. Questi strumenti vi aiuteranno a configurare il vostro server Node.js:

npm i -D dotenv nodemon

Le dipendenze installate sono spiegate di seguito:

  • express: questa libreria aiuta a creare un server in Node.js.
  • cors: CORS facilita la comunicazione sicura tra domini diversi.
  • openai: questa dipendenza vi permette di accedere all’API OpenAI DALL-E.
  • dotenv: dotenv aiuta a gestire le variabili d’ambiente.
  • nodemon: nodemon è uno strumento di sviluppo che monitora le modifiche ai file e riavvia automaticamente il server.

Una volta che le installazioni sono andate a buon fine, create un file server.js nella root del vostro progetto. Qui verrà memorizzato tutto il codice del server.

Nel file server.js, importate le librerie appena installate e istanziatele:

// 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();

Nel codice sopra riportato, importate le librerie necessarie. Quindi, create un’istanza dell’applicazione Express usando const app = express();. Successivamente, abilitate CORS. Express viene configurato per elaborare i dati JSON in arrivo, specificando un limite di dimensione dei dati di 50mb.

In seguito, viene creata un’istanza della classe OpenAI usando la vostra chiave API OpenAI. Create un file .env nella root del vostro progetto e aggiungete la chiave API usando la variabile OPENAI_API_KEY. Infine, definite una funzione asincrona startServer e chiamatela per mettere in moto il server.

Ora avete configurato il vostro file server.js. Creiamo una route POST da utilizzare nella vostra applicazione React per interagire con il server:

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);
	}
});

In questo codice, la route è impostata su /api ed è progettata per gestire le richieste POST in arrivo. All’interno della funzione di callback della route, ricevete i dati inviati dalla vostra applicazione React usando req.body, in particolare il valore prompt.

Successivamente, viene invocato il metodo images.generate della libreria OpenAI. Questo metodo prende il prompt fornito e genera un’immagine in risposta. Parametri come n determinano il numero di immagini da generare (in questo caso solo una), size specifica le dimensioni dell’immagine e response_format indica il formato in cui deve essere fornita la risposta (in questo casob64_json ).

Dopo aver generato l’immagine, estraete i dati dell’immagine dalla risposta e memorizzateli nella variabile image. Quindi, inviate una risposta JSON all’applicazione React con i dati dell’immagine generata, impostando lo stato HTTP a 200 (che indica il successo) utilizzando res.status(200).json({ photo: image }).

In caso di errori durante questo processo, il codice all’interno del blocco catch viene eseguito, registrando l’errore nella console per il debug.

Ora il server è pronto! Specifichiamo il comando che verrà utilizzato per eseguire il nostro server nel file package.json dell’oggetto scripts:

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

Ora, quando eseguirete npm run dev:backend, il vostro server si avvierà su http://localhost:8080/, mentre se eseguirete npm run dev:frontend, la vostra applicazione React si avvierà su http://localhost:3000/. Verificate che entrambi siano in esecuzione in terminali diversi.

Effettuare richieste HTTP da React al server Node.js

Nel file App.jsx, creerete una funzione generateImage che viene attivata quando il pulsante Generate Image viene cliccato nel componente Form.jsx. Questa funzione accetta due parametri: prompt e setPrompt dal componente Form.jsx.

Nella funzione generateImage, fate una richiesta HTTP POST al server 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');
	}
};

Nel codice precedente, si controlla se il parametro prompt ha un valore, quindi si imposta lo stato isGenerating su true poiché l’operazione sta iniziando. In questo modo il loader apparirà sullo schermo perché nel file App.jsx abbiamo questo codice che controlla la visualizzazione del loader:

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

Usiamo poi il metodo fetch() per fare una richiesta POST al server utilizzando http://localhost:8080/api: questo è il motivo per cui abbiamo installato CORS, dato che stiamo interagendo con un’API su un altro URL. Usiamo il prompt come corpo del messaggio. Quindi, estraete la risposta restituita dal server Node.js e impostatela sullo stato generatedImage.

Quando lo stato generatedImage avrà un valore, l’immagine verrà visualizzata:

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

Ecco come apparirà il file 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;

Distribuire l’applicazione full-stack su Kinsta

Finora avete realizzato con successo un’applicazione React che interagisce con Node.js, il che la rende un’applicazione full-stack. Ora distribuiamo questa applicazione su Kinsta.

Innanzitutto, configurate il server per servire i file statici generati durante il processo di creazione dell’applicazione React. Questo si ottiene importando il modulo path e usandolo per servire i file statici:

const path = require('path');

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

Quando eseguite il comando npm run build && npm run dev:backend, la vostra applicazione React full stack verrà caricata all’indirizzo http://localhost:8080/. Questo perché l’applicazione React viene compilata in file statici all’interno della cartella build. Questi file vengono poi incorporati nel vostro server Node.js come directory statica. Di conseguenza, quando eseguite il vostro server Node, l’applicazione sarà accessibile.

Prima di distribuire il codice al provider Git scelto (Bitbucket, GitHub o GitLab), ricordatevi di modificare l’URL di richiesta HTTP nel file App.jsx. Cambiate http://localhost:8080/api in /api, in quanto l’URL verrà aggiunto.

Infine, nel file package.json, aggiungete un comando di script per il server Node.js da usare per la distribuzione:

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

Successivamente, inviate il codice al vostro provider Git preferito e distribuite il repository su Kinsta seguendo questi passaggi:

  1. Accedete al vostro account Kinsta nel cruscotto MyKinsta.
  2. Selezionate Applicazione nella barra laterale di sinistra e fate clic sul pulsante Aggiungi applicazione.
  3. Nella finestra di dialogo che appare, scegliete il repository che volete distribuire. Se avete più branch, potete selezionare il branch desiderato e dare un nome alla vostra applicazione.
  4. Selezionate uno dei data center disponibili.
  5. Aggiungete il sito OPENAI_API_KEY come variabile d’ambiente. Kinsta imposterà automaticamente un file Docker per voi.
  6. Infine, nel campo del comando di avvio, aggiungete npm run build && npm run start. Kinsta installerà le dipendenze della vostra applicazione da package.json, quindi costruirà e distribuirà la vostra applicazione.

Riepilogo

In questa guida avete imparato a sfruttare la potenza dell’API OpenAI DALL-E per la generazione di immagini. Avete anche imparato a lavorare con React e Node.js per creare un’applicazione full-stack di base.

Le possibilità sono infinite con l’intelligenza artificiale: ogni giorno vengono introdotti nuovi modelli e potete creare progetti straordinari che possono essere distribuiti sull’Hosting di Applicazioni di Kinsta.

Quale modello vi piacerebbe esplorare e di quale progetto vorreste che scrivessimo prossimamente? Condividetelo nei commenti qui sotto.

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.