Luis Carlos Reyes Vanegas
June 1, 2024
Actualizado el June 3, 2024
Aprende a crear un chatbot
Aprende a construir chatbots conversacionales con IA usando Gemini, Next.js y Firebase. Implementa respuestas inteligentes y mejora la interacción en tu sitio web.
article cover
Paso a Paso para Crear un Chatbot Inteligente con Next.js y Firebase
1. Instalación de Next.js

Primero, instala Next.js en tu proyecto.

shell
bashCopiar código
npx create-next-app@latest my-chatbot --ts
cd my-chatbot
2. Instalación de Dependencias

Instala las dependencias necesarias:

shell
bashCopiar código
npm install firebase @heroicons/react
3. Crea tu cuenta de Firebase

Para ello debemos ir al siguiente link: https://firebase.google.com/ e iniciar sesión con una cuenta de Gmail.

A visual depiction of what is being written about

Luego seleccionaremos la opción Go To Console (arriba a la derecha) que nos llevará a la vista donde tenemos nuestros proyectos de Firebase, allí seleccionaremos la opción Agregar Proyecto:

A visual depiction of what is being written about

Esa opción nos llevará a la siguiente vista, donde crearemos el proyecto:

El paso 1 de la creación de un proyecto en Firebase nos consultará el nombre de nuestro proyecto:

A visual depiction of what is being written about

Luego, deshabilitaremos la configuración de Analytics ya que para efectos del taller no será necesario y le damos en Continuar:

A visual depiction of what is being written about

Luego, esperamos mientras se crea el proyecto (puede tardar unos minutos):

A visual depiction of what is being written about

Una vez termina su creación se ve de la siguiente manera, allí daremos click en Continuar:

A visual depiction of what is being written about

Y así se verá nuestro dashboard del nuevo proyecto

A visual depiction of what is being written about

En dicho dashboard, vamos a seleccionar el botón con los símbolos </> , ésto nos permitirá crear las credenciales para conectarlo con nuestra app web, y allí nos pedirá registrar el nombre de nuestra aplicación web y haremos click en el botón Registrar app:

A visual depiction of what is being written about

El siguiente paso, nos mostrará sobre la instalación de firebase (sin embargo no será necesario por que nuestro proyecto base ya tiene firebase instalado) y luego nos arrojará las credenciales de nuestra app.

A visual depiction of what is being written about

Por ultimo vamos a agregar las credenciales en el archivo .env en la raíz de tu proyecto donde las usaremos mas adelante.

plain text
envCopiar código
NEXT_PUBLIC_FIREBASE_API_KEY=your-api-key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-auth-domain
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-storage-bucket
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your-messaging-sender-id
NEXT_PUBLIC_FIREBASE_APP_ID=your-app-id

3.1 Instalación de Gemini en Firebase

En la consola de Firebase, iremos a la opción que dice Extensions dentro del menú Compilación.

A visual depiction of what is being written about

Al dar click en Extensions nos muestra las diferentes extensiones que podemos usar, en nuestro caso, buscamos la que dice Build Chatbot with the Gemini API y presionaremos Instalar

A visual depiction of what is being written about

Ésto nos redirigirá a la siguiente vista, en donde seleccionaremos una opción que se muestra más abajo llamada Actualizar proyecto para continuar:

A visual depiction of what is being written about

Allí seleccionaremos la Cuenta de Facturación creada en el primer paso del workshop, seleccionar dicha opción y luego Continuar.

A visual depiction of what is being written about

Colocaremos un presupuesto de 5 USD y presionamos continuar:

A visual depiction of what is being written about

Y seleccionamos la opción Comprar

A visual depiction of what is being written about

Deberá aparecerte el siguiente mensaje, podremos cerrarla y continuar:

A visual depiction of what is being written about

Deberemos Habilitar las siguientes opciones

A visual depiction of what is being written about

Luego, se habilitará el botón de Siguiente para continuar.

A visual depiction of what is being written about

Nuevamente presionamos Siguiente:

A visual depiction of what is being written about

Y ahora nos pide configurar nuestra extensión:

A visual depiction of what is being written about

Allí cambiaremos la siguiente información:

  • Gemini API Provider seleccionaremos la opción Vertex AI
  • Firestore Collection Path y colocaremos conversations (el nombre de nuestra colección en Firestore).
  • Cloud Functions Location, la opción de zona preferida.
  • Luego presionaremos la opción de Instalar la extensión.

    Allí esperaremos unos minutos mientras se instala…

    A visual depiction of what is being written about

    Y luego haremos click en Comenzar

    A visual depiction of what is being written about

    Aquí veremos cómo podemos probar dicha extensión en nuestro proyecto web, sin embargo, nosotros/as ya lo tomamos en cuenta en pasos anteriores.

    A visual depiction of what is being written about

    Para probar que nuestra extensión funciona correctamente, podemos ir a firestore, prueba presionando en el botón de Agregar Documento y agregarle al documento un parámetro llamado prompt con un saludo cómo: “Hola cómo estás?” y verás que en cuestión de segundos Gemini generará la respuesta.

    A visual depiction of what is being written about

    Una vez vista la respuesta ya estamos listos para crear nuestro código fuente!

    4. Estructura del Proyecto

    Organiza tu proyecto con la siguiente estructura:

    lua
    luaCopiar código
    my-chatbot/
    ├── public/
    │ ├── bot.png
    │ └── man.png
    ├── src/
    │ ├── app/
    │ │ ├── chat/
    │ │ │ └── page.tsx
    │ │ └── page.tsx
    │ ├── components/
    │ │ └── FloatingChatbot.tsx
    │ ├── hooks/
    │ │ └── useChat.ts
    │ └── lib/
    │ └── firebaseConfig.ts
    ├── .env.local
    ├── package.json
    └── tailwind.config.js
    5. Configuración de Firebase en el Proyecto

    Crea el archivo firebaseConfig.ts en src/lib:

    typescript
    typescriptCopiar código
    // src/lib/firebaseConfig.ts
    import { initializeApp } from 'firebase/app';
    import { getFirestore } from 'firebase/firestore';
    import { getAuth } from 'firebase/auth';
    const firebaseConfig = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
    projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
    };
    const app = initializeApp(firebaseConfig);
    const db = getFirestore(app);
    const auth = getAuth(app);
    export { db, auth };
    6. Crear Hook Personalizado para el Chat

    Crea el archivo useChat.ts en src/hooks:

    typescript
    typescriptCopiar código
    // src/hooks/useChat.ts
    import { useState, useEffect } from 'react';
    import { db } from '../lib/firebaseConfig';
    import { collection, addDoc, query, orderBy, onSnapshot, serverTimestamp, doc } from 'firebase/firestore';
    const useChat = (userId: string, conversationId: string, userProfilePicUrl: string, botProfilePicUrl: string) => {
    const [message, setMessage] = useState('');
    const [chat, setChat] = useState([]);
    const [isTyping, setIsTyping] = useState(false);
    useEffect(() => {
    const q = query(
    collection(db, 'conversations', conversationId, 'messages'),
    orderBy('timestamp', 'asc')
    );
    const unsubscribe = onSnapshot(q, (querySnapshot) => {
    const messages = querySnapshot.docs.map((doc) => doc.data());
    setChat(messages);
    });
    return () => unsubscribe();
    }, [conversationId]);
    const handleSendMessage = async () => {
    if (message.trim()) {
    const userMessage = {
    prompt: message,
    sender: userId,
    timestamp: serverTimestamp(),
    profilePicUrl: userProfilePicUrl,
    };
    setChat((prevChat) => [...prevChat, userMessage]);
    setMessage('');
    setIsTyping(true);
    try {
    await addDoc(collection(db, 'conversations', conversationId, 'messages'), userMessage);
    const botResponseRef = await addDoc(collection(db, 'generate'), {
    prompt: message,
    });
    const unsubscribe = onSnapshot(doc(db, 'generate', botResponseRef.id), async (snap) => {
    if (snap.get('response')) {
    const botMessage = {
    prompt: snap.get('response'),
    sender: 'bot',
    timestamp: serverTimestamp(),
    profilePicUrl: botProfilePicUrl,
    };
    await addDoc(collection(db, 'conversations', conversationId, 'messages'), botMessage);
    setChat((prevChat) => [...prevChat, botMessage]);
    setIsTyping(false);
    unsubscribe();
    }
    });
    } catch (error) {
    console.error("Error adding document: ", error);
    }
    }
    };
    const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') {
    handleSendMessage();
    }
    };
    return {
    message,
    setMessage,
    chat,
    isTyping,
    handleSendMessage,
    handleKeyDown,
    };
    };
    export default useChat;
    7. Crear la Página del Chat

    Crea el archivo page.tsx en src/app/chat:

    typescript
    tsxCopiar código
    // src/app/chat/page.tsx
    "use client";
    import useChat from '../../hooks/useChat';
    import Image from 'next/image';
    const USER_ID = '1';
    const CONVERSATION_ID = '1';
    const USER_PROFILE_PIC_URL = '/man.png';
    const BOT_PROFILE_PIC_URL = '/bot.png';
    export default function Chat() {
    const {
    message,
    setMessage,
    chat,
    isTyping,
    handleSendMessage,
    handleKeyDown,
    } = useChat(USER_ID, CONVERSATION_ID, USER_PROFILE_PIC_URL, BOT_PROFILE_PIC_URL);
    return (
    <div className="min-h-screen flex flex-col items-center bg-gray-50 p-4">
    <div className="w-full max-w-2xl bg-white shadow-lg rounded-lg p-6 flex flex-col">
    <div className="flex-1 overflow-y-auto mb-4">
    {chat.map((msg, index) => (
    <div key={index} className={`flex items-end mb-2 ${msg.sender === USER_ID ? 'justify-end' : 'justify-start'}`}>
    <Image src={msg.profilePicUrl} alt="profile picture" width={40} height={40} className="rounded-full mr-2" />
    <div className={`p-2 rounded-lg ${msg.sender === USER_ID ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-800'}`}>
    {msg.prompt}
    </div>
    </div>
    ))}
    {isTyping && (
    <div className="flex items-end mb-2 justify-start">
    <Image src={BOT_PROFILE_PIC_URL} alt="bot typing" width={40} height={40} className="rounded-full mr-2" />
    <div className="p-2 rounded-lg bg-gray-200 text-gray-800">
    El bot está escribiendo...
    </div>
    </div>
    )}
    </div>
    <div className="flex">
    <input
    type="text"
    value={message}
    onChange={(e) => setMessage(e.target.value)}
    onKeyDown={handleKeyDown}
    placeholder="Escribe tu mensaje..."
    className="flex-1 border border-gray-300 p-2 rounded-lg"
    />
    <button
    onClick={handleSendMessage}
    className="ml-2 bg-blue-600 text-white p-2 rounded-lg hover:bg-blue-700 transition"
    >
    Enviar
    </button>
    </div>
    </div>
    </div>
    );
    }
    8. Crear el Componente de Chatbot Flotante

    Crea el archivo FloatingChatbot.tsx en src/components:

    typescript
    tsxCopiar código
    // src/components/FloatingChatbot.tsx
    "use client";
    import { useState, useEffect, useRef } from 'react';
    import { ChatBubbleLeftEllipsisIcon, XMarkIcon } from '@heroicons/react/24/solid';
    import useChat from '../hooks/useChat';
    import Image from 'next/image';
    const USER_ID = '1';
    const CONVERSATION_ID = '1';
    const USER_PROFILE_PIC_URL = '/man.png';
    const BOT_PROFILE_PIC_URL = '/bot.png';
    const FloatingChatbot = () => {
    const [isOpen, setIsOpen] = useState(false);
    const {
    message,
    setMessage,
    chat,
    isTyping,
    handleSendMessage,
    handleKeyDown,
    } = useChat(USER_ID, CONVERSATION_ID, USER_PROFILE_PIC_URL, BOT_PROFILE_PIC_URL);
    const messagesEndRef = useRef<HTMLDivElement>(null);
    const toggleChat = () => {
    setIsOpen(!isOpen);
    };
    const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
    };
    useEffect(() => {
    if (isOpen) {
    scrollToBottom();
    }
    }, [isOpen, chat]);
    return (
    <div>
    <div className="fixed bottom-4 right-4 z-50">
    <button
    onClick={toggleChat}
    className="bg-blue-600 text-white p-4 rounded-full shadow-lg hover:bg-blue-700 transition"
    >
    {isOpen ? <XMarkIcon className="h-6 w-6" /> : <ChatBubbleLeftEllipsisIcon className="h-6 w-6" />}
    </button>
    </div>
    {isOpen && (
    <div className="fixed bottom-16 right-4 z-50 bg-white border border-gray-300 rounded-lg shadow-lg w-80 h-96">
    <div className="flex flex-col h-full">
    <div className="flex items-center justify-between p-4 border-b">
    <h2 className="text-lg font-bold">Chatbot</h2>
    <button
    onClick={toggleChat}
    className="text-gray-500 hover:text-gray-700"
    >
    <XMarkIcon className="h-6 w-6" />
    </button>
    </div>
    <div className="flex-1 p-4 overflow-y-auto">
    {chat.map((msg, index) => (
    <div key={index} className={`flex items-end mb-2 ${msg.sender === USER_ID ? 'justify-end' : 'justify-start'}`}>
    <Image src={msg.profilePicUrl} alt="profile picture" width={30} height={30} className="rounded-full mr-2" />
    <div className={`p-2 rounded-lg ${msg.sender === USER_ID ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-800'}`}>
    {msg.prompt}
    </div>
    </div>
    ))}
    {isTyping && (
    <div className="flex items-end mb-2 justify-start">
    <Image src={BOT_PROFILE_PIC_URL} alt="bot typing" width={30} height={30} className="rounded-full mr-2" />
    <div className="p-2 rounded-lg bg-gray-200 text-gray-800">
    El bot está escribiendo...
    </div>
    </div>
    )}
    <div ref={messagesEndRef} />
    </div>
    <div className="flex p-2 border-t">
    <input
    type="text"
    value={message}
    onChange={(e) => setMessage(e.target.value)}
    onKeyDown={handleKeyDown}
    placeholder="Escribe tu mensaje..."
    className="flex-1 border border-gray-300 p-2 rounded-lg"
    />
    <button
    onClick={handleSendMessage}
    className="ml-2 bg-blue-600 text-white p-2 rounded-lg hover:bg-blue-700 transition"
    >
    Enviar
    </button>
    </div>
    </div>
    </div>
    )}
    </div>
    );
    };
    export default FloatingChatbot;
    9. Crear la Página Principal

    Actualiza page.tsx en src/app:

    typescript
    tsxCopiar código
    // src/app/page.tsx
    import Image from 'next/image';
    import Link from 'next/link';
    import FloatingChatbot from '../components/FloatingChatbot';
    export default function Home() {
    return (
    <div className="min-h-screen flex flex-col items-center justify-center bg-gray-50">
    <div className="bg-white shadow-lg rounded-lg p-8 max-w-lg text-center">
    <h1 className="text-4xl font-bold text-gray-900 mb-4">¡Bienvenidos al Taller de Chatbots Inteligentes!</h1>
    <p className="text-gray-700 mb-6">
    Aprende a crear un chatbot inteligente utilizando Google Cloud, Gemini, Next.js y Firebase.
    </p>
    <Image
    src="/images/chatbot-welcome.png"
    alt="Welcome to the Chatbot Workshop"
    width={400}
    height={250}
    className="mx-auto mb-6"
    />
    <Link href="/chat" className="inline-block bg-blue-600 text-white py-2 px-4 rounded hover:bg-blue-700 transition">
    Comenzar
    </Link>
    </div>
    <FloatingChatbot />
    </div>
    );
    }
    Conclusión

    Con estos pasos, has creado un chatbot inteligente utilizando Next.js, Firebase y la extensión Gemini. El chatbot incluye una página de chat dedicada y un componente flotante que permite a los usuarios interactuar con el bot desde cualquier página.

    Recuerda:

  • Estructura del Proyecto: Mantén una estructura clara y organizada.
  • Firebase: Configura correctamente las credenciales.
  • Componentes Reutilizables: Utiliza hooks personalizados para lógica compartida.
  • Diseño Responsivo: Asegúrate de que el chatbot sea accesible y fácil de usar.
  • A visual depiction of what is being written about

    ¡Felicidades! si tu proyecto luce como la imagen anterior, has completado el proyecto. Si tienes alguna pregunta o necesitas más ayuda, no dudes en contactarme.

    Blogs recientes
    Más blogs ➜
    ;