Balla & Snella – Liquid Glass
Balla & Snella – Dimagrire Ballando

Assenza o Recupero? Scrivi qui

Come usarlo:

1) Comunicami la tua assenza o la tua volontà di recupero, grazie all' Ask AI qui sopra.

2) Scrivi Nome, Cognome e Sede, poi dimmi quando non puoi venire o vuoi recuperare la lezione.

3) Analizzo la tua richiesta e ti rispondo immediatamente.

${fmtTime(Date.now())}
`; body.appendChild(typingEl); requestAnimationFrame(()=>{ body.scrollTop = body.scrollHeight; }); } function hideTyping(){ if (typingEl && typingEl.parentNode){ typingEl.parentNode.removeChild(typingEl); } typingEl = null; } function scheduleStatusMessage(){ if (statusTimer) clearTimeout(statusTimer); statusTimer = setTimeout(() => { if (!awaitingReply) return; // rimuovo eventuale bubble di typing vecchia hideTyping(); const msgText = WAIT_MESSAGES[Math.floor(Math.random()*WAIT_MESSAGES.length)]; pushHistory("bot", msgText); renderHistory(true); // dopo il messaggio intermedio ricreo la bubble coi puntini showTyping(); }, 7000); // 7 secondi } function cancelStatusMessage(){ if (statusTimer){ clearTimeout(statusTimer); statusTimer = null; } } /* Apertura input in pagina */ function openBar(){ if (!wrap.classList.contains("is-open")){ wrap.classList.add("is-open"); requestAnimationFrame(()=> { input.focus(); autoGrow(input); updateSendState(); }); } } inner.addEventListener("click", openBar); placeholder.addEventListener("click", openBar); function showToast(s, ok=true){ msg.textContent = s; msg.style.color = ok ? "#c9f7d1" : "#ffd4d4"; msg.classList.add("show"); setTimeout(()=> msg.classList.remove("show"), 2300); } /* Modal */ let untrap = null; function trapFocus(container){ const focusables = container.querySelectorAll('button, [href], input, textarea, [tabindex]:not([tabindex="-1"])'); if (!focusables.length) return ()=>{}; const first = focusables[0], last = focusables[focusables.length - 1]; function handle(e){ if(e.key !== 'Tab') return; if(e.shiftKey && document.activeElement === first){ e.preventDefault(); last.focus(); } else if(!e.shiftKey && document.activeElement === last){ e.preventDefault(); first.focus(); } } container.addEventListener('keydown', handle); return () => container.removeEventListener('keydown', handle); } function openModal(){ try{ document.activeElement && document.activeElement.blur && document.activeElement.blur(); }catch(e){} modal.classList.add("show"); modal.setAttribute("aria-hidden","false"); document.body.style.overflow = "hidden"; renderHistory(true); untrap = trapFocus(modal.querySelector(".ask-panel")); } function closeModal(){ modal.classList.remove("show"); modal.setAttribute("aria-hidden","true"); document.body.style.overflow = ""; hideTyping(); cancelStatusMessage(); awaitingReply = false; if (untrap) untrap(); } closeB.addEventListener("click", closeModal); modal.addEventListener("click", (e)=>{ if(e.target===modal) closeModal(); }); chatOpen.addEventListener("click", openModal); /* Cestino */ clearB.addEventListener("click", ()=>{ if (confirm("Svuotare tutte le chat salvate su questo dispositivo?")){ clearHistory(); renderHistory(true); setPendingIntent(null); setPendingStep(null); hideTyping(); cancelStatusMessage(); awaitingReply = false; } }); /* Storico */ function pushHistory(role, text){ const items = loadHistory(); items.push({ role, text, ts: Date.now() }); saveHistory(items); return items; } function fmtTime(ts){ return new Date(ts).toLocaleString(undefined,{dateStyle:"short",timeStyle:"short"}); } function renderHistory(scrollBottom = false){ const items = loadHistory(); body.innerHTML = ""; if (items.length === 0) { const welcome = document.createElement("div"); welcome.className = "msg bot"; welcome.innerHTML = `
Ciao, comunicaci qui le Assenze o i Recuperi. Clicca uno dei due pulsanti qui sotto e segui ciò che ti viene chiesto.
${fmtTime(Date.now())}
`; body.appendChild(welcome); if (scrollBottom){ requestAnimationFrame(()=>{ body.scrollTop = body.scrollHeight; }); } return; } items.forEach(it => { const el = document.createElement("div"); el.className = "msg " + (it.role === "you" ? "you" : "bot"); el.innerHTML = `
${escapeHTML(it.text)}
${fmtTime(it.ts)}
`; body.appendChild(el); }); if (scrollBottom){ requestAnimationFrame(()=>{ body.scrollTop = body.scrollHeight; }); } } function escapeHTML(s){ return String(s).replace(/[&"']/g, m => ({'&':'&','':'>','"':'"',"'":'''}[m])); } /* Input helpers */ function autoGrow(el){ el.style.height = "auto"; el.style.height = el.scrollHeight + "px"; } input.addEventListener("input", ()=> { autoGrow(input); updateSendState(); }); inputModal.addEventListener("input", ()=> { autoGrow(inputModal); updateSendState(); }); inputModal.addEventListener("keydown", (ev)=>{ if (ev.key === "Escape") closeModal(); }); /* Stato invio */ let sending = false; function updateSendState(){ const hasTextMain = (input.value || "").trim().length > 0; const hasTextModal = (inputModal.value || "").trim().length > 0; send.disabled = !hasTextMain || sending; sendModal.disabled = !hasTextModal || sending; } /* Payload per n8n */ function buildPayload(text, meta={}){ const sessionId = getSessionId(); return { text, query: text, sessionId, chatId: sessionId, messageId: nextMessageId(), ts: Date.now(), source: "askai-widget", origin: meta.origin || "user_reply", intent: meta.intent || getPendingIntent() || null, step: meta.step || (getPendingIntent() ? getPendingStep() || "details" : null) }; } /* Messaggi guida */ const GUIDE_RECUPERO = `Perfetto! Per organizzare il recupero mi lasci queste info? 1) Nome e Cognome 2) Sede (es. Pietralata) 3) Quando vorresti recuperare la lezione (es. Ven 21 Novembre alle 19:00)`; const GUIDE_ASSENZA = `Per segnarti assente mi servono queste informazioni: 1) Nome e Cognome 2) Sede (es. Pietralata) 3) Quando non puoi venire a lezione (es. Gio 20 Novembre alle 18:00)`; /* Intent + messaggi iniziali (HOME) */ btnRecupero.addEventListener("click", ()=>{ setPendingIntent("recupero"); setPendingStep("details"); pushHistory("you", "Recuperare una Lezione"); pushHistory("bot", GUIDE_RECUPERO); openModal(); renderHistory(true); }); btnAssenza.addEventListener("click", ()=>{ setPendingIntent("assenza"); setPendingStep("details"); pushHistory("you", "Assenza per Lezione"); pushHistory("bot", GUIDE_ASSENZA); openModal(); renderHistory(true); }); /* Intent + messaggi iniziali (POPUP) */ btnRecuperoModal.addEventListener("click", ()=>{ setPendingIntent("recupero"); setPendingStep("details"); pushHistory("you", "Recuperare una Lezione"); pushHistory("bot", GUIDE_RECUPERO); renderHistory(true); }); btnAssenzaModal.addEventListener("click", ()=>{ setPendingIntent("assenza"); setPendingStep("details"); pushHistory("you", "Assenza per Lezione"); pushHistory("bot", GUIDE_ASSENZA); renderHistory(true); }); function ensureOnline(){ if (!navigator.onLine){ showToast(ASKAI_CONFIG.messages.offline, false); return false; } return true; } /* Invio (solo bottone) */ async function handleSendFrom(whichInput){ const val = (whichInput.value || "").trim(); if (!val){ showToast(ASKAI_CONFIG.messages.empty, false); return; } if (!ensureOnline()) return; if (sending) return; whichInput.value = ""; whichInput.style.height = "auto"; autoGrow(whichInput); updateSendState(); sending = true; awaitingReply = true; hideTyping(); cancelStatusMessage(); updateSendState(); pushHistory("you", val); if (!modal.classList.contains("show")) openModal(); renderHistory(true); // mostra puntini + programma messaggio intermedio showTyping(); scheduleStatusMessage(); const payload = buildPayload(val, { origin: "user_reply" }); const wasDetails = payload.step === "details" && payload.intent; const ac = new AbortController(); const t = setTimeout(()=> ac.abort(), 60000); try{ const res = await fetch(ASKAI_CONFIG.http.endpoint, { method: ASKAI_CONFIG.http.method || "POST", headers: ASKAI_CONFIG.http.headers || {}, body: JSON.stringify(payload), signal: ac.signal }); if (!res.ok) throw new Error("bad_response"); const replyText = await ASKAI_CONFIG.http.parseReply(res); awaitingReply = false; cancelStatusMessage(); hideTyping(); pushHistory("bot", String(replyText || "(nessuna risposta)")); renderHistory(true); showToast(ASKAI_CONFIG.messages.sent, true); }catch(e){ console.error(e); awaitingReply = false; cancelStatusMessage(); hideTyping(); pushHistory("bot", "⚠️ Errore di rete o webhook"); renderHistory(true); showToast(ASKAI_CONFIG.messages.error, false); }finally{ clearTimeout(t); sending = false; updateSendState(); if (wasDetails){ setPendingIntent(null); setPendingStep(null); } } } send.addEventListener("click", ()=> handleSendFrom(input)); sendModal.addEventListener("click", ()=> handleSendFrom(inputModal)); })();