WEB3DEV Español

Cover image for Crea arte impulsado por IA usando prompts desde tu propio bot personalizado hecho con Discord.js
Gabriella Alexandra Martinez Viloria
Gabriella Alexandra Martinez Viloria

Posted on

Crea arte impulsado por IA usando prompts desde tu propio bot personalizado hecho con Discord.js

Este artículo es una traducción de Adrian Chrysanthou, hecha por Gabriella Martinez. Puedes encontrar el artículo original aquí.
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_eshttps://twitter.com/web3dev_es en Twitter.

En este tutorial, aprenderás a cómo usar el proveedor StableDiffusion y Discord.js para crear un bot de Discord que genera arte único desde prompts textuales.

Prerrequisitos y Software necesario

Paso 1: Clona y Configura

Primero, clona este repositorio en tu máquina y ábrelo con el editor de códigos que usas. Este repositorio es un template para cualquier Bot de Discord Discord.js. Es similar a la guía de Discord.js para construir tu primer bot.

Image description

Paso 2: Establece el Entorno

Para empezar a codear tu bot, abre tu editor de códigos preferido.

Image description

Abre tu terminal y ejecuta yarn para instalar los paquetes desde package.json

Image description

Luego, renombra .env-sample a .env y dentro de DISCORD_TOKEN= coloca el token de tu Bot desde la sección de Discord Developer.

Image description

Por último, vamos a ver el archivo package.json para desarrollarlo. Abre package.json

Te darás cuenta de dos comandos debajo de "scripts"

"deploy": "node deploy-commands.js",
"start": "node index.js"
Enter fullscreen mode Exit fullscreen mode

Estos son los comandos para desplegar nuevos comandos para tu bot e iniciarlo, subsecuentemente.

Luego crea una carpeta llamada Commands. Dentro de esa carpeta, haz un archivo generate.js y un archivo avanzado .js.

Image description

Con todo configurado, ¡Vamos a empezar a desarrollar!

Paso 3: Codeando el Bot

Arranquemos con nuestro bot. Para hacerlo, abre el terminal, ve al directorio de la carpeta de tu proyecto y ejecuta yarn start

Image description

Deberías ver un mensaje que diga algo así como “Ready!”. Si no lo ves, por favor, asegúrate que todos los paquetes estén instalados y que no tengas errores de sintaxis en package.json.

Primero, vamos a abrir generate.js y comencemos.

Coloca todos los paquetes requeridos en la parte superior del archivo. Asegúrate que dotenv esté inicializado en primer lugar.

const dotenv = require('dotenv');
dotenv.config();
const WebSocket = require('ws');
const { SlashCommandBuilder, AttachmentBuilder } = require('discord.js');
const createHash = require('hash-generator');
Enter fullscreen mode Exit fullscreen mode

Vamos a crear la función que retorna el hash generado. Este hash es específico al demo de StableDiffusion 1.5, siendo usado por el proveedor aquí

El demo de StableDiffusion 1.5 requiere que el hash sea enviado a través de Web Sockets cuando sea pedido.

Para darte un ejemplo, abajo te darás cuenta que fn_index:2 y session_hash están dentro de “session_hash”. Luego, vamos a incluir esos parámetros en nuestro archivo generate.js

Image description

function generateHash() {
 let hash = createHash(12)
 return {
   session_hash: hash,
   fn_index: 2
 }
}
Enter fullscreen mode Exit fullscreen mode

Ahora necesitamos construir SlashCommandBuilder.

Modulizaremos este comando con module.exports

module.exports = {
 data: new SlashCommandBuilder()
   .setName('generate')
   .setDescription('Generates an image from a text prompt using Stable Diffusion 1.5')
   .addStringOption(option => option
     .setName('prompt')
     .setDescription('generate image prompt')
   ),
}; 
Enter fullscreen mode Exit fullscreen mode

Pero, ¡espera! No ejecutes el código aún.

Te habrás dado cuenta que no tenemos un execute al final. Vamos a añadirlo a continuación:

async execute(interaction) {
 },
};
Enter fullscreen mode Exit fullscreen mode

Ahora, vamos a juntarlo todo.

Image description

Luego, necesitamos obtener la interacción y el string para ejecutar el comando /generate prompt

const prompt = interaction.options.getString('prompt');
console.log('What to generate?', prompt);
Enter fullscreen mode Exit fullscreen mode

Ahora vamos a terminar la siguiente pieza de código en try/catch y definir unas pocas constantes con una respuesta inmediata a la interacción.

La primera constante es para WebSockets. Se conecta directamente a /queue/join y nos coloca en la lista de espera del UI de la Web cuando se genere el prompt.

try {
     interaction.reply("I'm generating...");
     const ws = new WebSocket('wss://runwayml-stable-diffusion-v1-5.hf.space/queue/join');
     const hash = generateHash();

} catch (error) {
     interaction.reply(error);
     console.error(error);
}console.error(error);
Enter fullscreen mode Exit fullscreen mode

Ahora, ¡deberíamos aplicar nuestro Web Socket y generar cosas divertidas!

ws.on('open', () => {});
ws.on('message', async (message) => {});
ws.on('error', async (error) => {
  interaction.editReply({
     content: 'An error occurred while generating the image',
  });
  console.error(error);
});   
Enter fullscreen mode Exit fullscreen mode

Tu generate.js debería ser similar a este

module.exports = {
     data: new SlashCommandBuilder()
       .setName('generate')
       .setDescription('Generates an image from a text prompt using Stable Diffusion 1.5')
       .addStringOption(option => option
         .setName('prompt')
         .setDescription('generate image prompt')
         ),
     async execute(interaction) {
       const prompt = interaction.options.getString('prompt');
       console.log('What to generate?', prompt);

       try {
         interaction.reply("I'm generating...");

         const ws = new WebSocket('wss://runwayml-stable-diffusion-v1-5.hf.space/queue/join');
         const hash = generateHash();
         ws.on('open', () => {});

         ws.on('message', async (message) => {});

         ws.on('error', async (error) => {
           interaction.editReply({
             content: 'An error occurred while generating the image',
           });
           console.error(error);
         });
       } catch (error) {
           interaction.reply(error);
           console.error(error);
       }
     },
};
Enter fullscreen mode Exit fullscreen mode

Dentro de ws.on(‘message’) vamos primero a JSON.parse, el message regresado y determinar si msg = ‘send_data’ o si = ‘process_completed’.

Nota: más mensajes se envían, pero estos dos son el foco principal de este tutorial.

Para hacerlo, primero crearemos una constante msg que contiene un JSON.parse del mensaje regresado:

const msg = JSON.parse(`${message}`);
if (msg.msg === 'send_data') {
} else if (msg.msg === 'process_completed') {
} else {}
Enter fullscreen mode Exit fullscreen mode

Luego, necesitamos añadir la ‘data’ que queremos enviar. Crearemos una constante de datos que contenga a ambos, los prompts de los usuarios de Discord envueltos en un array, así como el hash constante que hicimos.

const data = {
  data: [prompt],
  ...hash,
};
ws.send(JSON.stringify(data));
Enter fullscreen mode Exit fullscreen mode

Te habrás dado cuenta que también añadí un ws.send. Esto es para, específicamente, enviar los paquetes de datos con todo lo que se necesita para comenzar la generación del proceso.

Ahora, ve tu código y asegúrate que coincida con este:

const dotenv = require('dotenv');
dotenv.config();
const WebSocket = require('ws');
const {
 SlashCommandBuilder,
 AttachmentBuilder
} = require('discord.js');
const createHash = require('hash-generator');

function generateHash() {
 let hash = createHash(12)
 return {
   session_hash: hash,
   fn_index: 2
 }
}

module.exports = {
 data: new SlashCommandBuilder()
   .setName('generate')
   .setDescription('Generates an image from a text prompt using Stable Diffusion 1.5')
   .addStringOption(option => option
     .setName('prompt')
     .setDescription('generate image prompt')
   ),
 async execute(interaction) {

   const prompt = interaction.options.getString('prompt');
   console.log('What to generate?', prompt);

   try {
     interaction.reply("I'm generating...");

     const ws = new WebSocket('wss://runwayml-stable-diffusion-v1-5.hf.space/queue/join');
     const hash = generateHash();
     ws.on('open', () => {});

     ws.on('message', async (message) => {
       const msg = JSON.parse(`${message}`);
       if (msg.msg === 'send_data') {
         const data = {
           data: [prompt],
           ...hash,
         };
         ws.send(JSON.stringify(data));
       } else if (msg.msg === 'process_completed') {

       } else {}
     });

     ws.on('error', async (error) => {
       console.error(error);
       interaction.editReply({
         content: 'An error occurred while generating the image',
       });
     });
   } catch (error) {
     console.error(error);
   }

 },
};
Enter fullscreen mode Exit fullscreen mode

¡Genial, ya casi estamos terminando!

Ahora vamos a añadir try/catch y obtener las imágenes enviadas de vuelta luego de msg = ‘process_completed’

try {

} catch (error) {
      console.error(error);
      interaction.editReply({
         content: 'An error occurred while generating the image',
      });
}
Enter fullscreen mode Exit fullscreen mode

Ahora, dentro de try vamos a obtener el output msg. Adicionalmente, vamos a crear una constante llamada attachments para el array. Recuerda, vamos a tener varias imágenes enviadas de vuelta.

try {
    const results = msg.output.data[0];
    const attachments = [];
} catch (error) {
      console.error(error);
      interaction.editReply({
         content: 'An error occurred while generating the image',
      });
}
Enter fullscreen mode Exit fullscreen mode

Luego, queremos que se haga un bucle a través de los resultados y dividirlos en nuevas filas para cada , encontrada.

Añade los resultados de la constante del buffer y júntalos al AttachmentBuilder de Discord.

Asegúrate de empujar cada attachment al array con attachments.push(attachment)

Tu código debería verse similar a este:

try {
    const results = msg.output.data[0];
    const attachments = [];
    for (let i = 0; i < results.length; i++) {
        const data = results[i].split(',')[1];
        const buffer = Buffer.from(data, 'base64');
        const attachment = new AttachmentBuilder(buffer, {
              name: 'generate.png',
        });
        attachments.push(attachment);
     }
} catch (error) {
      console.error(error);
      interaction.editReply({
         content: 'An error occurred while generating the image',
      });
}
Enter fullscreen mode Exit fullscreen mode

Por último, ¡debemos enviar los attachments a Discord para que el usuario lo pueda ver!

Nota: estamos usando editReply ya que originalmente enviamos un reply cuando comenzamos la generación del proceso.

interaction.editReply({
          content: `You asked me for ${prompt}`,
          files: attachments,
});
Enter fullscreen mode Exit fullscreen mode

Tu código final para el archivo generate.js debería verse similar a este:

const dotenv = require('dotenv');
dotenv.config();
const WebSocket = require('ws');
const {
 SlashCommandBuilder,
 AttachmentBuilder
} = require('discord.js');
const createHash = require('hash-generator');


function generateHash() {
 let hash = createHash(12)
 return {
   session_hash: hash,
   fn_index: 2
 }
}

module.exports = {
 data: new SlashCommandBuilder()
   .setName('generate')
   .setDescription('Generates an image from a text prompt using Stable Diffusion 1.5')
   .addStringOption(option => option
     .setName('prompt')
     .setDescription('generate image prompt')
   ),
 async execute(interaction) {

   const prompt = interaction.options.getString('prompt');
   console.log('What to generate?', prompt);

   try {
     await interaction.reply("I'm generating...");

     const ws = new WebSocket('wss://runwayml-stable-diffusion-v1-5.hf.space/queue/join');
     const hash = generateHash();
     ws.on('open', () => {});

     ws.on('message', async (message) => {
       const msg = JSON.parse(`${message}`);
       if (msg.msg === 'send_hash') {
         ws.send(JSON.stringify(hash));
       } else if (msg.msg === 'send_data') {
         const data = {
           data: [prompt],
           ...hash,
         };
         ws.send(JSON.stringify(data));
       } else if (msg.msg === 'process_completed') {
         try {
           const results = msg.output.data[0];
           const attachments = [];
           for (let i = 0; i < results.length; i++) {
             const data = results[i].split(',')[1];
             const buffer = Buffer.from(data, 'base64');
             const attachment = new AttachmentBuilder(buffer, {
               name: 'generate.png',
             });
             attachments.push(attachment);
           }
           interaction.editReply({
             content: `You asked me for ${prompt}`,
             files: attachments,
           });
         } catch (error) {
           console.error(error);
           await interaction.editReply({
             content: 'An error occurred while generating the image',
           });
         }
       }
     });

     ws.on('error', async (error) => {
       console.error(error);
       await interaction.editReply({
         content: 'An error occurred while generating the image',
       });
     });
   } catch (error) {
     console.error(error);
   }

 },
};
Enter fullscreen mode Exit fullscreen mode

Ahora, ¡vamos a ejecutar el comando y verlo en acción!

Image description

¡Eureka! ¡Has generado tu primer par de imágenes usando Stable Diffusion a través de tu propio Bot personalizado de Discord! Este es solo un ejemplo de las cosas posibles que puedes hacer con los comandos de Discord y Web Sockets. Por favor, toma en cuenta que esto es mejor usado para demostraciones y te propongo que alojes tu propia UI Stable Diffusion Web.

Puedes encontrar el código final para el bot de abajo, junto con los Comandos Avanzados que se conectan a la UI de la Web que, tu mismo, alojas en un servicio de la nube GPU o en docker image, usando el paquete request: https://github.com/f00d4tehg0dz/stable-diffusion-discord-bot-template/tree/final

Gracias por leer. Por favor revisa mi Bot de Discord, Arti e ¡intenta algunos comandos más avanzados!

Discussion (0)