Hagamos un chatbot en realidad aumentada con Google Gemini

ingcarlosreina mostrando un cartel que dice realidad aumentada con el profesor atomo e inteligencia artificial
Hagamos un chatbot en realidad aumentada con Google Gemini – Realidad Aumentada

Imagina abrir un libro de química, apuntar con la cámara de tu celular y ver cómo un simpático profesor tridimensional emerge de la página, rodeado de átomos giratorios, listo para responder cualquier pregunta que tengas. No es ciencia ficción, es WebAR (Realidad Aumentada en la Web – Realidad Mixta), y hoy te contaré cómo desarrollamos el proyecto «Profesor Atómico AR».

Este proyecto no es solo una demostración técnica; es un ejemplo tangible de cómo la Realidad Aumentada (AR) y la Inteligencia Artificial (IA) pueden fusionarse para transformar la educación estática en una experiencia interactiva y conversacional.

¿Está Muerta la Realidad Aumentada? La Evolución Detrás del Cierre de las Grandes Compañías

El Stack Tecnológico: Herramientas de Vanguardia

Para lograr esta experiencia sin obligar al usuario a descargar una aplicación pesada, utilizamos un conjunto de tecnologías web poderosas y ligeras:

  • MindAR (Libreria para Realidad Aumentada enfocada a la web).

El corazón de nuestra experiencia. Utilizamos MindAR para el rastreo de imágenes (Image Tracking). A diferencia de ARCore o ARKit, MindAR funciona directamente en el navegador, permitiendo que cualquier estudiante con un smartphone y acceso a internet pueda vivir la experiencia.

Para renderizar nuestro mundo, elegimos A-Frame. Es un framework web que permite crear escenas de Realidad Virtual y Aumentada utilizando una estructura similar a HTML.

Función clave: Gestionar el modelo 3D del profesor (.glb), las luces, las cámaras y las animaciones de los anillos atómicos con una sintaxis sencilla y declarativa.

  • Google Gemini API

Conectamos nuestra aplicación a la API de Google Gemini Flash. Esto permite que el «Profesor Atómico» no tenga respuestas pregrabadas limitadas, sino que pueda generar explicaciones educativas, resúmenes y datos curiosos en tiempo real sobre cualquier tema que el estudiante pregunte.

  • RiveScript (El Controlador Local)

Para gestionar la lógica inmediata y comandos divertidos, usamos RiveScript. Actúa como un primer filtro: si le dices al profesor «baila, animate, canta, piensa o cualquier palabra o frase que quieras», RiveScript detecta la intención y dispara la animación en el frontend sin gastar tokens de la IA. Si la pregunta es compleja, la deriva a Gemini.

Para que tu aplicación quede mucho más robusta te invito a leer este articulo para que agregues todas las animaciones que quieras a tu modelo 3D en formato glb: Controla Animaciones 3D en apps de realidad aumentada WebAR

Comencemos con el desarrollo de la aplicación WebAR

La imagen que utilice como marcador fue esta:

Un cientifico en su laboratorio con muchos elementos de ciencia
Hagamos un Chatbot en realidad aumentada con Google Gemini

Pasemos a lo más importante de esta aplicación que es el código JavaScript:

<script>
const GEMINI_API_KEY = "AQUI VA TU API DE GOOGLE IA";

const bot = new RiveScript();

const brain = `
! version = 2.0
+ (hola|buenas)
- ¡Saludos! Soy tu Profesor Virtual. ¿Qué deseas aprender hoy?
+ quien eres
- Soy una simulación educativa.
+ (baila|festeja|activate)
- ¡Energía al máximo! <call>animar</call>
+ *
- CALL_GEMINI
`;

document.addEventListener("DOMContentLoaded", function() {

bot.stream(brain);
bot.sortReplies();

bot.setSubroutine("animar", function() {
triggerAtomicDance();
return "";
});

const inputField = document.getElementById("user-input");
const sendBtn = document.getElementById("send-btn");
const chatHistory = document.getElementById("chat-history");

function createSparkles() {
const rings = document.querySelectorAll('.atom-ring');
rings.forEach(ring => {
for(let i=0; i<12; i++) {
const sparkle = document.createElement('a-entity');
sparkle.setAttribute('geometry', 'primitive: sphere; radius: 0.03');
// shader: flat hace que brillen sin sombras
sparkle.setAttribute('material', 'color: #141100; shader: flat; transparent: true; opacity: 0.9');
sparkle.classList.add('sparkle');

const angle = Math.random() * Math.PI * 2;
const radius = 0.7 + (Math.random() * 0.15);
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius;

sparkle.setAttribute('position', `${x} ${y} ${(Math.random() - 0.5) * 0.3}`);
const dur = 1000 + Math.random() * 1000;
sparkle.setAttribute('animation', `property: scale; from: 0.5 0.5 0.5; to: 1.2 1.2 1.2; dir: alternate; loop: true; dur: ${dur}; easing: easeInOutSine`);

ring.appendChild(sparkle);
}
});
}

async function chat() {
const text = inputField.value.trim();
if (!text) return;
addMessage("Tú", text, "user-msg");
inputField.value = "";
inputField.disabled = true;
changeAtomState("thinking");
let reply = await bot.reply("local-user", text);
if (reply.includes("CALL_GEMINI")) {
addMessage("Sistema", "Procesando...", "system-msg");
const geminiReply = await askGeminiAI(text);
addMessage("Profesor", geminiReply, "bot-msg");
changeAtomState("idle");
} else {
addMessage("Profesor", reply, "bot-msg");
if(!text.match(/baila|festeja/i)) {
changeAtomState("idle");
}
}
inputField.disabled = false;
inputField.focus();
}

async function askGeminiAI(userText) {
const modelName = "gemini-flash-latest";
const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelName}:generateContent?key=${GEMINI_API_KEY}`;
const prompt = `Eres un Profesor de AR. Responde educativo y breve (max 30 palabras). Usuario: "${userText}"`;
try {
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] })
});
const data = await response.json();
if (data.error) return "Error de conexión.";
if (data.candidates) return data.candidates[0].content.parts[0].text;
return "No tengo datos.";
} catch (error) { return "Error de red."; }
}

function changeAtomState(state) {
const rings = document.querySelectorAll('.atom-ring');
const sparkles = document.querySelectorAll('.sparkle');
if (state === "thinking") {
// shader: flat hace que los colores sean puros e ignoren la oscuridad
rings.forEach(r => r.setAttribute('material', 'color: #FF4500; shader: flat; opacity: 0.9'));
sparkles.forEach(s => s.setAttribute('material', 'color: #FF4500; shader: flat'));
} else if (state === "idle") {
rings.forEach(r => r.setAttribute('material', 'color: #FFD700; shader: flat; opacity: 0.8'));
sparkles.forEach(s => s.setAttribute('material', 'color: #FFD700; shader: flat'));
}
}

function triggerAtomicDance() {
const rings = document.querySelectorAll('.atom-ring');
const sparkles = document.querySelectorAll('.sparkle');
const model = document.querySelector('#profesor-model');
const colors = ['#FFD700', '#FF8C00', '#FFFFFF'];

rings.forEach((ring, index) => {
ring.setAttribute('material', `color: ${colors[index]}; shader: flat; opacity: 1`);
ring.setAttribute('animation', 'timeScale', 8);
});

sparkles.forEach(s => {
const randomColor = colors[Math.floor(Math.random()*colors.length)];
s.setAttribute('material', `color: ${randomColor}; shader: flat`);
});

if(model) model.emit('jumpTrigger');

setTimeout(() => {
rings.forEach(ring => {
ring.setAttribute('animation', 'timeScale', 1);
});
changeAtomState("idle");
if(model) model.setAttribute('position', '0 1 0');
}, 3000);
}

function addMessage(sender, text, className) {
chatHistory.innerHTML += `<div class="${className}"><strong>${sender}:</strong> ${text}</div>`;
chatHistory.scrollTop = chatHistory.scrollHeight;
}

sendBtn.addEventListener("click", chat);
inputField.addEventListener("keypress", (e) => { if (e.key === "Enter") chat(); });

const target = document.querySelector('a-entity[mindar-image-target]');
const ui = document.getElementById("chat-interface");
const scan = document.getElementById("scan-text");
let sparklesCreated = false;
target.addEventListener("targetFound", () => {
ui.style.display = "flex"; scan.style.display = "none";
if(!sparklesCreated) { createSparkles(); sparklesCreated = true; }
});
target.addEventListener("targetLost", () => { ui.style.display = "none"; scan.style.display = "block"; });
});

Si haces todo bien con tu HTML, CSS y Javascript tu aplicación ser vera como la de este video.

Descarga los archivos de este tutorial desde mi cuenta de Patreon.

¿Qué obtendrás al hacerte miembro de Patreon con este articulo? 

  • Archivo HTML comentado línea por línea para mejorar tu aprendizaje.
  • Archivo CSS comentado línea por línea.
  • Archivo JavaScript donde te explico a detalle y paso a paso el funcionamiento de la WebAR.
  • Modelo 3D en formato .glb (Profesor.glb).
  • Archivo .mind e imagen para que pruebes tu aplicación.

Puedes hacerte miembro desde 1 dólar al mes.

Gracias por tu apoyo. Para Realidad Aumentada Empezando Desde Cero es muy importante tu aporte.

Da clic en la imagen para ir directamente a la descarga de archivo.

logo de realidad aumentada empezando desde cero y patreon
Realidad Aumentada Empezando Desde Cero y Patreon
0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Scroll al inicio
0
Would love your thoughts, please comment.x
()
x