// 24/7 assistant widget: floating bubble, slide-up panel, /api/chat. (function () { const root = document.createElement('div'); root.id = 'iapChat'; root.innerHTML = '' + ''; document.body.appendChild(root); const $ = id => document.getElementById(id); const msgs = $('iapChatMsgs'); const input = $('iapChatIn'); let history = []; const add = (text, who) => { const d = document.createElement('div'); d.className = 'ch-m ' + who; // linkify plain URLs d.innerHTML = String(text).replace(/[&<>]/g, c => ({ '&': '&', '<': '<', '>': '>' }[c])) .replace(/(https?:\/\/[^\s]+)/g, '$1'); msgs.appendChild(d); msgs.scrollTop = msgs.scrollHeight; return d; }; async function send() { const q = input.value.trim(); if (!q) return; input.value = ''; add(q, 'me'); const wait = add('…', 'bot'); try { const r = await (await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: q, history: history.slice(-4).join(' | ') }) })).json(); wait.remove(); add(r.reply || r.error || 'No answer came back. Try again.', 'bot'); history.push('Q: ' + q, 'A: ' + (r.reply || '')); } catch (e) { wait.remove(); add('Connection hiccup. Try that again.', 'bot'); } } $('iapChatBtn').addEventListener('click', () => { const p = $('iapChatPanel'); p.hidden = !p.hidden; if (!p.hidden) input.focus(); }); $('iapChatClose').addEventListener('click', () => { $('iapChatPanel').hidden = true; }); $('iapChatSend').addEventListener('click', send); input.addEventListener('keydown', e => { if (e.key === 'Enter') send(); }); })();