(function() { 'use strict'; const WIDGET_CONFIG = { tenant: 'itlive', subagent: '', project: '', page: '', apiBase: 'https://portal.itlive.nl/admin_portal/api', locale: 'nl', strings: {"header":"AI Assistent","powered":"Powered by IT Live","placeholder":"Typ je bericht...","send":"Verstuur","welcome":"\ud83d\udc4b Hallo! Ik ben de AI-assistent van IT Live. Hoe kan ik je helpen? Stel gerust je vraag over websites, hosting, SEO of neem contact op.","error":"\u274c Er ging iets mis. Probeer het opnieuw.","connectionError":"\u274c Verbindingsfout. Controleer je internet.","footer":"Powered by IT Live","whatsappUrl":"https:\/\/wa.me\/31850604017","phoneTel":"+31850604017","micLabel":"Spraak","micRecording":"Opnemen...","quickLinks":[{"label":"Offerte aanvragen","url":"https:\/\/itlive.nl\/offerte"},{"label":"Gratis kennismaking","url":"https:\/\/itlive.nl\/gratis-kennismaking"},{"label":"Contact","url":"https:\/\/itlive.nl\/contact"},{"label":"Vertel je idee","url":"https:\/\/itlive.nl\/idee"},{"label":"Klantportaal","url":"https:\/\/portal.itlive.nl\/customer_portal\/"},{"label":"Gratis website scan","url":"https:\/\/itlive.nl\/gratis-website-scan"},{"label":"Meta scan","url":"https:\/\/itlive.nl\/meta-campagne\/"},{"label":"Diensten","url":"https:\/\/itlive.nl\/diensten"},{"label":"Sitechat","url":"https:\/\/portal.itlive.nl\/sitechat\/"}]}, theme: {"primary":"#667eea","secondary":"#764ba2","background":"#0f172a","messagesBg":"#1e293b","text":"#f1f5f9","inputBg":"#1e293b","inputBorder":"#334155","botMessageBg":"#334155","botMessageText":"#f1f5f9"} }; // Create widget container const widgetHTML = ` `; // Inject widget HTML if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initWidget); } else { initWidget(); } let attachedUrls = []; function initWidget() { const container = document.createElement('div'); container.innerHTML = widgetHTML; document.body.appendChild(container); // Show widget after a delay setTimeout(() => { document.getElementById('itlive-ai-widget').style.display = 'block'; }, 1000); // Event listeners const bubble = document.getElementById('itlive-ai-chat-bubble'); const window = document.getElementById('itlive-ai-chat-window'); const closeBtn = document.getElementById('itlive-ai-close'); const sendBtn = document.getElementById('itlive-ai-send'); const input = document.getElementById('itlive-ai-input'); bubble.addEventListener('click', () => { window.style.display = 'flex'; bubble.style.transform = 'scale(0)'; document.body.classList.add('itlive-ai-widget-open'); input.focus(); }); closeBtn.addEventListener('click', () => { window.style.display = 'none'; bubble.style.transform = 'scale(1)'; document.body.classList.remove('itlive-ai-widget-open'); }); // Snelle links: standaard ingeklapt, toggle bij klik const qlWrap = document.getElementById('itlive-ai-quicklinks-wrap'); const qlToggle = document.getElementById('itlive-ai-quicklinks-toggle'); const qlList = document.getElementById('itlive-ai-quicklinks-list'); const qlChevron = document.getElementById('itlive-ai-quicklinks-chevron'); const qlinks = (WIDGET_CONFIG.strings.quickLinks || []); if (qlWrap && qlList && qlinks.length > 0) { qlWrap.style.display = 'flex'; qlWrap.style.flexDirection = 'column'; qlinks.forEach(function(link) { const a = document.createElement('a'); a.href = link.url || '#'; a.target = '_blank'; a.rel = 'noopener noreferrer'; a.className = 'itlive-quick-link'; a.textContent = link.label || 'Link'; a.style.cssText = (['prepfoodz', 'prepfoodz-klant'].includes(WIDGET_CONFIG.tenant)) ? 'display: inline-block; padding: 8px 14px; font-size: 13px; background: rgba(214,194,163,0.25); color: #D6C2A3; text-decoration: none; border-radius: 10px; border: 1px solid rgba(214,194,163,0.4); transition: background 0.2s, border-color 0.2s;' : 'display: inline-block; padding: 8px 14px; font-size: 13px; background: rgba(102,126,234,0.2); color: #818cf8; text-decoration: none; border-radius: 10px; border: 1px solid rgba(102,126,234,0.35); transition: background 0.2s, border-color 0.2s;'; qlList.appendChild(a); }); if (qlToggle && qlChevron) { qlToggle.addEventListener('click', function() { const isExpanded = qlList.style.display !== 'none'; qlList.style.display = isExpanded ? 'none' : 'flex'; qlToggle.setAttribute('aria-expanded', isExpanded ? 'false' : 'true'); qlChevron.textContent = isExpanded ? '▶' : '▼'; }); } } sendBtn.addEventListener('click', sendMessage); input.addEventListener('keypress', (e) => { if (e.key === 'Enter') sendMessage(); }); // Expose programmatic send for pages (e.g. mijn-bedrijf Start AI-rapport) window.itliveAISendMessage = function(text) { const i = document.getElementById('itlive-ai-input'); const s = document.getElementById('itlive-ai-send'); if (i && s && text != null && String(text).trim() !== '') { i.value = String(text).trim(); s.click(); } }; // Microfoon / spraakherkenning (Web Speech API) const micBtn = document.getElementById('itlive-ai-mic'); if (micBtn) { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; const micLabel = WIDGET_CONFIG.strings.micLabel || (WIDGET_CONFIG.locale === 'nl' ? 'Spraak' : 'Voice'); const micRecording = WIDGET_CONFIG.strings.micRecording || (WIDGET_CONFIG.locale === 'nl' ? 'Opnemen...' : 'Recording...'); if (!SpeechRecognition) { micBtn.style.display = 'none'; } else { let recognition = null; let isRecording = false; micBtn.addEventListener('click', function() { const inp = document.getElementById('itlive-ai-input'); if (!inp) return; if (isRecording && recognition) { recognition.stop(); return; } recognition = recognition || new SpeechRecognition(); recognition.continuous = true; recognition.interimResults = true; recognition.lang = WIDGET_CONFIG.locale === 'nl' ? 'nl-NL' : 'en-GB'; recognition.onstart = function() { isRecording = true; micBtn.style.background = '#ef4444'; micBtn.style.color = '#fff'; micBtn.title = micRecording; micBtn.setAttribute('aria-label', micRecording); micBtn.textContent = '⏹'; }; recognition.onend = function() { isRecording = false; micBtn.style.background = ''; micBtn.style.color = ''; micBtn.title = micLabel; micBtn.setAttribute('aria-label', micLabel + (WIDGET_CONFIG.locale === 'nl' ? ' opnemen' : ' input')); micBtn.textContent = '🎤'; }; recognition.onresult = function(event) { let transcript = ''; for (let i = event.resultIndex; i < event.results.length; i++) { transcript += event.results[i][0].transcript; } if (transcript) inp.value = (inp.value ? inp.value + ' ' : '') + transcript; }; recognition.onerror = function(e) { if (e.error !== 'aborted') { isRecording = false; micBtn.style.background = ''; micBtn.style.color = ''; micBtn.textContent = '🎤'; } }; recognition.start(); }); } } // Prepfoodz: file attach if (WIDGET_CONFIG.tenant === 'prepfoodz') { const attachBtn = document.getElementById('itlive-ai-attach-btn'); const fileInput = document.getElementById('itlive-ai-file-input'); const attachmentsDiv = document.getElementById('itlive-ai-attachments'); if (attachBtn && fileInput && attachmentsDiv) { attachBtn.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', async function() { const file = this.files[0]; if (!file) return; const fd = new FormData(); fd.append('file', file); try { const r = await fetch(WIDGET_CONFIG.apiBase.replace('/api', '') + '/api/prepfoodz-feedback-upload.php', { method: 'POST', body: fd }); const d = await r.json(); if (d.success && d.urls && d.urls.length) { attachedUrls = attachedUrls.concat(d.urls); attachmentsDiv.style.display = 'block'; attachmentsDiv.textContent = (attachedUrls.length === 1 ? '1 foto toegevoegd' : attachedUrls.length + ' foto\'s toegevoegd'); } } catch (e) { console.warn('Upload mislukt', e); } this.value = ''; }); } } // Welcome message setTimeout(() => { addMessage('bot', WIDGET_CONFIG.strings.welcome); }, 1500); } function sendMessage() { const input = document.getElementById('itlive-ai-input'); let message = input.value.trim(); const attachmentsDiv = document.getElementById('itlive-ai-attachments'); if (attachedUrls && attachedUrls.length) { message += (message ? '\n\n' : '') + '[Bijgevoegde foto(s): ' + attachedUrls.join(', ') + ']'; attachedUrls = []; if (attachmentsDiv) { attachmentsDiv.style.display = 'none'; attachmentsDiv.textContent = ''; } } if (!message) return; addMessage('user', message); input.value = ''; // Show typing indicator const typingId = addMessage('bot', '...', true); // Send to API fetch(WIDGET_CONFIG.apiBase + '/universal-ai-chat.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message, tenant: WIDGET_CONFIG.tenant, subagent: WIDGET_CONFIG.subagent || undefined, project: WIDGET_CONFIG.project || undefined, page: WIDGET_CONFIG.page || undefined, conversation_id: getConversationId() }) }) .then(r => r.json()) .then(data => { // Remove typing indicator const typing = document.getElementById(typingId); if (typing) typing.remove(); if (data.success) { addMessage('bot', data.response, false, data.metadata); } else { addMessage('bot', WIDGET_CONFIG.strings.error); } }) .catch(err => { const typing = document.getElementById(typingId); if (typing) typing.remove(); addMessage('bot', WIDGET_CONFIG.strings.connectionError); }); } function linkify(text) { if (typeof text !== 'string') return text; const escaped = text.replace(/&/g,'&').replace(//g,'>').replace(/\n/g, '
'); const urlRe = /(https?:\/\/[a-zA-Z0-9][a-zA-Z0-9\-._~:\/?#\[\]@!$&'()*+,;=%]*)/g; const parts = []; let lastIdx = 0; let m; while ((m = urlRe.exec(escaped)) !== null) { let url = m[0].replace(/[.,;:!?)\]]+$/, ''); let label = url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); if (label.includes('https://examples.itlive.nl/customer_portal')) label = 'Klantportaal'; else if (label.includes('gratis-website-scan')) label = 'Gratis website scan'; else if (label.includes('gratis-kennismaking')) label = 'Gratis kennismaking'; else if (label.includes('offerte')) label = 'Offerte aanvragen'; else if (label.includes('contact')) label = 'Contact'; else if (label.includes('sitechat')) label = 'Sitechat'; else if (label.length > 35) label = label.slice(0, 35) + '…'; if (lastIdx < m.index) parts.push(escaped.slice(lastIdx, m.index)); parts.push({ url: url, label: label }); lastIdx = urlRe.lastIndex; } if (lastIdx < escaped.length) parts.push(escaped.slice(lastIdx)); let out = ''; let linkWrap = []; parts.forEach(function(p) { if (typeof p === 'string') { if (linkWrap.length) { out += ''; linkWrap = []; } out += p; } else { const safeLabel = (p.label || '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); linkWrap.push('' + safeLabel + ''); } }); if (linkWrap.length) out += ''; return out; } function addMessage(type, text, isTyping = false, metadata = null) { const messagesContainer = document.getElementById('itlive-ai-messages'); const messageId = 'msg-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); const isUser = type === 'user'; const bgColor = isUser ? WIDGET_CONFIG.theme.primary : (WIDGET_CONFIG.theme.botMessageBg || '#ffffff'); const textColor = isUser ? '#ffffff' : (WIDGET_CONFIG.theme.botMessageText || WIDGET_CONFIG.theme.text); const align = isUser ? 'flex-end' : 'flex-start'; const displayText = (!isTyping && type === 'bot') ? linkify(String(text)) : String(text); const sentiment = metadata && metadata.sentiment; const thumbsHtml = (WIDGET_CONFIG.tenant === 'prepfoodz' && type === 'bot' && !isTyping) ? `
` : ''; const messageHTML = `
${displayText}
${thumbsHtml} ${''}
`; messagesContainer.insertAdjacentHTML('beforeend', messageHTML); requestAnimationFrame(function() { messagesContainer.scrollTo({ top: messagesContainer.scrollHeight, behavior: 'smooth' }); }); if (thumbsHtml) { const msgEl = document.getElementById(messageId); const wrap = msgEl && msgEl.querySelector('.itlive-ai-thumbs'); if (wrap) { const up = wrap.querySelector('.itlive-thumb-up'); const down = wrap.querySelector('.itlive-thumb-down'); if (up) up.addEventListener('click', function() { up.style.background = '#22c55e'; up.style.color = '#fff'; if (down) { down.style.background = 'rgba(255,255,255,0.1)'; down.style.color = '#ef4444'; } }); if (down) down.addEventListener('click', function() { down.style.background = '#ef4444'; down.style.color = '#fff'; if (up) { up.style.background = 'rgba(255,255,255,0.1)'; up.style.color = '#22c55e'; } }); } } return messageId; } function getConversationId() { let convId = localStorage.getItem('itlive_ai_conversation_id'); if (!convId) { convId = 'conv_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); localStorage.setItem('itlive_ai_conversation_id', convId); } return convId; } // Add CSS const style = document.createElement('style'); style.textContent = ` #itlive-ai-widget { --itlive-primary: ${WIDGET_CONFIG.theme.primary}; --itlive-secondary: ${WIDGET_CONFIG.theme.secondary}; } #itlive-ai-chat-bubble { border: none; } #itlive-ai-chat-bubble:hover { transform: scale(1.08) !important; } #itlive-ai-chat-bubble:focus-visible { outline: 2px solid white; outline-offset: 2px; } #itlive-ai-close:focus-visible, #itlive-ai-send:focus-visible, #itlive-ai-input:focus-visible, #itlive-ai-mic:focus-visible { outline: 2px solid ${WIDGET_CONFIG.theme.primary}; outline-offset: 2px; } #itlive-ai-input:focus { border-color: ${WIDGET_CONFIG.theme.primary}; } #itlive-ai-chat-window { max-height: min(600px, calc(100dvh - 120px)); } /* Fullscreen widget op mobiel – alleen site-header (~56px) boven, widget vult rest */ @media (max-width: 768px) { #itlive-ai-chat-bubble { bottom: 20px; right: 20px; width: 56px; height: 56px; } #itlive-ai-chat-window { position: fixed !important; inset: 56px 0 0 0 !important; top: 56px !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: 100% !important; max-width: none !important; height: calc(100dvh - 56px) !important; max-height: none !important; border-radius: 0 !important; box-shadow: none !important; margin: 0 !important; padding: 0 !important; box-sizing: border-box !important; } .itlive-ai-chat-window .itlive-ai-close { width: 48px; height: 48px; min-width: 48px; min-height: 48px; font-size: 28px; font-weight: 700; background: rgba(255,255,255,0.3) !important; border: 2px solid rgba(255,255,255,0.5) !important; } .itlive-ai-chat-window .itlive-ai-header { padding: 10px 14px; padding-top: calc(10px + env(safe-area-inset-top, 0)); } .itlive-ai-chat-window .itlive-ai-header h3 { font-size: 16px; } .itlive-ai-chat-window .itlive-ai-powered { font-size: 10px; } #itlive-ai-messages { flex: 1; min-height: 0; -webkit-overflow-scrolling: touch; } body.itlive-ai-widget-open { overflow: hidden; } } #itlive-ai-messages { overscroll-behavior: contain; } #itlive-ai-messages::-webkit-scrollbar { width: 6px; } #itlive-ai-messages::-webkit-scrollbar-track { background: transparent; } #itlive-ai-messages::-webkit-scrollbar-thumb { background: var(--itlive-primary); border-radius: 3px; } .itlive-quick-link:hover { background: rgba(255,255,255,0.25) !important; border-color: rgba(255,255,255,0.4) !important; } .itlive-ai-contact-strip a:hover { transform: scale(1.1); opacity: 0.9; } .itlive-thumb-up:hover, .itlive-thumb-down:hover { opacity: 0.9; transform: scale(1.05); } .itlive-msg-link-wrap { display: flex; flex-wrap: wrap; gap: 0.5rem 0.75rem; margin-top: 0.6rem; } .itlive-msg-link, .itlive-msg-link-btn { display: inline-block; margin: 0; padding: 0.45rem 0.85rem; background: #fff; border: 2px solid var(--itlive-primary); border-radius: 8px; font-size: 0.9rem; font-weight: 500; color: var(--itlive-primary); text-decoration: none; transition: background 0.15s, border-color 0.15s, color 0.15s; } .itlive-msg-link:hover, .itlive-msg-link-btn:hover { background: rgba(102,126,234,0.12); border-color: var(--itlive-primary); color: var(--itlive-primary); } @media (prefers-reduced-motion: reduce) { #itlive-ai-chat-bubble:hover, #itlive-ai-send:hover { transform: none !important; } } `; document.head.appendChild(style); console.log('🤖 IT Live AI Widget loaded for tenant:', WIDGET_CONFIG.tenant); })();