// Configuración: Reemplaza esta URL con tu enlace de Teachable Machine const URL = "./model/"; let model, webcam, labelContainer, maxPredictions; let isStopped = false; // Cargar el modelo e iniciar la cámara async function init() { document.getElementById("status").innerText = "Cargando modelo..."; document.getElementById("start-btn").disabled = true; try { const modelURL = URL + "model.json"; const metadataURL = URL + "metadata.json"; // Cargar el modelo model = await tmImage.load(modelURL, metadataURL); maxPredictions = model.getTotalClasses(); // Configurar la webcam const flip = true; // girar la cámara webcam = new tmImage.Webcam(300, 300, flip); await webcam.setup(); // pedir permiso await webcam.play(); window.requestAnimationFrame(loop); // UI Updates document.getElementById("webcam-container").appendChild(webcam.canvas); labelContainer = document.getElementById("label-container"); document.getElementById("status").innerText = "Modelo cargado. Escaneando..."; document.getElementById("stop-btn").disabled = false; loadHistory(); } catch (e) { console.error(e); document.getElementById("status").innerText = "Error al cargar. Asegúrate de que los archivos del modelo estén en public/model/"; document.getElementById("start-btn").disabled = false; } } async function loop() { if (isStopped) return; webcam.update(); // actualizar frame de la webcam await predict(); window.requestAnimationFrame(loop); } // Realizar predicción async function predict() { const prediction = await model.predict(webcam.canvas); // Encontrar la predicción con mayor confianza let highest = { className: "", probability: 0 }; for (let i = 0; i < maxPredictions; i++) { if (prediction[i].probability > highest.probability) { highest = prediction[i]; } } labelContainer.innerText = `${highest.className}: ${(highest.probability * 100).toFixed(2)}%`; // Si la confianza es alta (> 90%), enviar al backend (cada 5 segundos para no saturar) if (highest.probability > 0.90 && !window.lastSent) { sendToBackend(highest.className, highest.probability); window.lastSent = true; setTimeout(() => window.lastSent = false, 5000); } } // Enviar datos al Backend async function sendToBackend(label, confidence) { try { const response = await fetch('/api/detections', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ label, confidence }) }); if (response.ok) { console.log("Detección guardada en backend"); loadHistory(); } } catch (error) { console.error("Error al conectar con backend", error); } } // Cargar historial del Backend async function loadHistory() { try { const response = await fetch('/api/detections'); const data = await response.json(); const list = document.getElementById("history-list"); list.innerHTML = ""; data.reverse().slice(0, 10).forEach(item => { const li = document.createElement("li"); const date = new Date(item.timestamp).toLocaleTimeString(); li.innerText = `[${date}] ${item.label} (${(item.confidence * 100).toFixed(1)}%)`; list.appendChild(li); }); } catch (error) { console.error("Error cargando historial", error); } } function stopWebcam() { isStopped = true; if (webcam) webcam.stop(); document.getElementById("status").innerText = "Cámara detenida."; document.getElementById("stop-btn").disabled = true; document.getElementById("start-btn").disabled = false; }