const kiosk = document.querySelector(".kiosk-grid"); const video = document.getElementById("cameraStream"); const canvas = document.getElementById("snapshotCanvas"); const preview = document.getElementById("snapshotPreview"); const startButton = document.getElementById("startCamera"); const captureButton = document.getElementById("capturePhoto"); const statusText = document.getElementById("cameraStatus"); const results = document.getElementById("recognitionResults"); const frame = document.querySelector(".camera-frame"); const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute("content"); let activeStream = null; let lastFaces = []; startButton.addEventListener("click", async () => { try { activeStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "user" }, audio: false }); video.srcObject = activeStream; frame.classList.remove("has-preview"); captureButton.disabled = false; statusText.textContent = "Camera is active."; } catch (error) { statusText.textContent = `Camera access failed: ${error.name}`; } }); captureButton.addEventListener("click", async () => { if (!video.videoWidth || !video.videoHeight) { statusText.textContent = "Camera is not ready yet."; return; } canvas.width = video.videoWidth; canvas.height = video.videoHeight; canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height); const image = canvas.toDataURL("image/png"); await identify(image); }); async function identify(image) { setLoading("Identifying faces..."); const formData = new FormData(); formData.append("image", image); const response = await fetch(kiosk.dataset.identifyUrl, { method: "POST", body: formData, credentials: "same-origin", headers: { "X-CSRFToken": csrfToken, "Accept": "application/json" }, }); const payload = await response.json(); if (!response.ok) { showError(payload.error || "Recognition failed."); return; } lastFaces = payload.faces; preview.src = payload.annotated_image; frame.classList.add("has-preview"); renderFaces(payload.faces); } function renderFaces(faces) { results.innerHTML = ""; if (!faces.length) { results.innerHTML = '
No faces were detected.
'; return; } faces.forEach((face) => { const card = document.createElement("div"); card.className = "result-card"; if (!face.matched) { card.innerHTML = "

Unknown person

No enrolled person matched this face.

"; results.appendChild(card); return; } const confidence = face.confidence === null ? "n/a" : `${Math.round(face.confidence * 100)}%`; card.innerHTML = `

${escapeHtml(face.full_name)}

${escapeHtml(face.employee_id)} - Confidence ${confidence}
${escapeHtml(face.last_presence)}

`; results.appendChild(card); }); } results.addEventListener("click", async (event) => { const button = event.target.closest("button[data-event-type]"); if (!button) { return; } const face = lastFaces.find((item) => item.person_id === Number(button.dataset.personId)); const status = button.closest(".result-card").querySelector("[data-status]"); button.disabled = true; status.textContent = "Recording attendance..."; const formData = new FormData(); formData.append("person_id", button.dataset.personId); formData.append("event_type", button.dataset.eventType); if (face && face.confidence !== null) { formData.append("confidence", face.confidence); } const response = await fetch(kiosk.dataset.recordUrl, { method: "POST", body: formData, credentials: "same-origin", headers: { "X-CSRFToken": csrfToken, "Accept": "application/json" }, }); const payload = await response.json(); button.disabled = false; if (!response.ok) { status.textContent = "Could not record attendance."; return; } status.textContent = `${payload.event_type} recorded at ${payload.occurred_at}. ${payload.summary}`; }); function setLoading(message) { statusText.textContent = message; results.innerHTML = `
${escapeHtml(message)}
`; } function showError(message) { statusText.textContent = message; results.innerHTML = `
${escapeHtml(message)}
`; } function escapeHtml(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); }