Source: public/abecedario.js

/**
 * Modelo de Teachable Machine.
 * @type {Object}
 */
let model;

/**
 * Objeto de la cámara web.
 * @type {Object}
 */
let webcam;

/**
 * Contenedor para las etiquetas de predicción.
 * @type {HTMLElement}
 */
let labelContainer;

/**
 * Número máximo de predicciones.
 * @type {number}
 */
let maxPredictions;

/**
 * Último objeto detectado.
 * @type {string}
 */
let lastDetectedObject = '';

/**
 * Letras que el modelo puede detectar.
 * @type {string[]}
 */
let letters = ["A", "B", "C", "NONE", "CH", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U / V", "W", "X", "Y", "Z"];

/**
 * Detecciones almacenadas.
 * @type {Object}
 */
let detections = {};

/**
 * Última letra detectada.
 * @type {string}
 */
let lastDetectedLetter = '';

/**
 * Temporizador de detección.
 * @type {number}
 */
let detectionTimeout;

// Inicializar las claves del objeto 'detections' con arrays vacíos para cada letra
letters.forEach(letter => {
    detections[letter] = [];
});

/**
 * Inicializa el modelo y la cámara web.
 * @async
 */
async function init() {
    const modelURL = 'model.json'; // URL del modelo
    const metadataURL = 'metadata.json'; // URL de los metadatos

    // Carga el modelo y los metadatos de Teachable Machine
    model = await tmImage.load(modelURL, metadataURL);
    maxPredictions = model.getTotalClasses(); // Obtiene el número total de clases del modelo

    // Configuración de la cámara web
    const flip = true; // Invertir la imagen horizontalmente
    webcam = new tmImage.Webcam(390, 240, flip); // Inicializa la webcam con el tamaño especificado
    await webcam.setup(); // Configura la webcam
    await webcam.play(); // Inicia la transmisión de la webcam
    window.requestAnimationFrame(loop); // Comienza el bucle de predicción

    // Añade el canvas de la webcam al contenedor en el DOM
    document.getElementById("webcam-container").appendChild(webcam.canvas);
    labelContainer = document.getElementById("label-container"); // Contenedor para las etiquetas de predicción
    for (let i = 0; i < maxPredictions; i++) {
        labelContainer.appendChild(document.createElement("div")); // Crea un div para cada predicción
    }

    // Oculta el botón de inicio una vez que la cámara esté configurada
    document.getElementById("startButton").style.display = 'none';
}

/**
 * Función de bucle para actualizar la webcam y realizar predicciones continuamente.
 * @async
 */
async function loop() {
    webcam.update(); // Actualiza la imagen de la webcam
    await predict(); // Realiza una predicción
    window.requestAnimationFrame(loop); // Solicita el próximo cuadro de animación
}

/**
 * Realiza predicciones utilizando el modelo.
 * @async
 */
async function predict() {
    const prediction = await model.predict(webcam.canvas); // Realiza una predicción sobre el canvas de la webcam
    let detectedLetter = null; // Variable para almacenar la letra detectada

    for (let i = 0; i < maxPredictions; i++) {
        // Si la probabilidad de la predicción es mayor a 0.5, considera la predicción como válida
        if (prediction[i].probability > 0.5) {
            detectedLetter = prediction[i].className; // Obtiene el nombre de la clase predicha
            // Si la letra detectada está en la lista de letras y es diferente a la última letra detectada
            if (letters.includes(detectedLetter) && detectedLetter !== lastDetectedLetter) {
                detections[detectedLetter].push(detectedLetter); // Añade la letra detectada al objeto 'detections'
                const inputElement = document.getElementById("messageInput");
                inputElement.value += detectedLetter + ''; // Añade la letra detectada al campo de entrada de mensaje
                lastDetectedLetter = detectedLetter; // Actualiza la última letra detectada
            }
            break;
        }
    }

    // Si no se detecta ninguna letra, establece un temporizador para enviar el mensaje después de 2 segundos
    if (detectedLetter === null) {
        clearTimeout(detectionTimeout);
        detectionTimeout = setTimeout(sendMessage, 2000); // Enviar mensaje después de 2 segundos sin detección
    }

    updateInterface(); // Actualiza la interfaz de usuario
}

/**
 * Envía el mensaje.
 */
function sendMessage() {
    const inputElement = document.getElementById("messageInput");
    if (inputElement.value.trim() !== '') { // Verifica que el campo de entrada no esté vacío
        const event = new Event('submit'); // Crea un nuevo evento de tipo 'submit'
        document.getElementById('messageForm').dispatchEvent(event); // Despacha el evento de envío del formulario
        inputElement.value = ''; // Limpia el campo de entrada después de enviar el mensaje
    }
}

/**
 * Actualiza la interfaz de usuario con las detecciones.
 */
function updateInterface() {
    for (const key in detections) {
        if (detections.hasOwnProperty(key)) {
            const letters = detections[key].join(''); // Junta las letras detectadas con un espacio
            const element = document.getElementById(key); // Obtiene el elemento del DOM correspondiente a la letra
            if (element) {
                element.innerText = letters; // Actualiza el texto del elemento con las letras detectadas
            }
        }
    }
}