(function() { 'use strict'; try { const scriptTag = document.currentScript || document.querySelector('script[data-bot-id]'); const BOT_ID = scriptTag?.getAttribute('data-bot-id'); const ALLOWED_ORIGIN = scriptTag?.getAttribute('data-origin'); const API_BASE = (() => { try { const src = scriptTag?.src; return src ? new URL(src).origin : 'https://buildabot.ai'; } catch (e) { return 'https://buildabot.ai'; } })(); if (!BOT_ID) { console.error('[BuildABot] Missing data-bot-id attribute'); return; } console.log('[BuildABot] Initializing widget for bot:', BOT_ID, 'API_BASE:', API_BASE); let botConfig = null; let sessionId = null; let isOpen = false; let conversationHistory = []; // Chat icon map const chatIcons = { 'message-circle': '💬', 'message-square': '💬', 'headphones': '🎧', 'help-circle': '❓', 'users': '👥', 'sparkles': '✨' }; // Linkification utilities const SAFE_SCHEMES = /^(https?:|mailto:|tel:)/i; const urlRegex = /\b((https?:\/\/|www\.)[^\s<>()]+[^\s().,!?;:'"\)\]])/gi; const mdRegex = /\[([^\]]+)\]\(([^)]+)\)/g; function escapeHtml(s) { return s.replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m])); } function safeHref(href) { const trimmed = href.trim(); const normalized = trimmed.startsWith('www.') ? 'https://' + trimmed : trimmed; return SAFE_SCHEMES.test(normalized) ? normalized : '#'; } function linkify(text) { let safe = escapeHtml(text); // Markdown links [label](url) safe = safe.replace(mdRegex, function(_m, label, url) { const href = safeHref(url); const labelEsc = escapeHtml(label); if (href === '#') return labelEsc; return '' + labelEsc + ''; }); // Plain URLs safe = safe.replace(urlRegex, function(url) { const href = safeHref(url); if (href === '#') return url; const label = escapeHtml(url); return '' + label + ''; }); return safe; } async function fetchJSON(url, init) { const u = new URL(url); u.searchParams.set('_t', Date.now().toString()); const res = await fetch(u.toString(), { cache: 'no-store', credentials: 'omit', headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-cache', ...(init?.headers || {}) }, ...init }); if (!res.ok) throw new Error('HTTP ' + res.status); return res.json(); } async function fetchBotConfig() { try { const data = await fetchJSON(API_BASE + '/functions/getBotConfig', { method: 'POST', body: JSON.stringify({ bot_id: BOT_ID }) }); botConfig = data.config; console.log('[BuildABot] Config loaded successfully'); return botConfig; } catch (error) { console.error('[BuildABot] Config error:', error); return null; } } async function trackEvent(eventType, metadata = {}) { try { await fetchJSON(API_BASE + '/functions/trackAnalytics', { method: 'POST', body: JSON.stringify({ bot_id: BOT_ID, event_type: eventType, session_id: sessionId, metadata: metadata }) }); } catch (error) { console.warn('[BuildABot] Analytics failed:', error); } } async function sendMessage(message) { try { const data = await fetchJSON(API_BASE + '/functions/chatWithBot', { method: 'POST', body: JSON.stringify({ bot_id: BOT_ID, message: message, session_id: sessionId }) }); if (!sessionId) sessionId = data.session_id; await trackEvent('message_received', { message: data.message }); return data.message; } catch (error) { console.error('[BuildABot] Chat error:', error); return 'Sorry, there was a problem sending that message.'; } } function downloadTranscript() { if (conversationHistory.length === 0) { alert('No conversation to download yet.'); return; } const botName = botConfig?.name || 'Chatbot'; const timestamp = new Date().toLocaleString(); let transcript = botName + ' - Conversation Transcript\n'; transcript += 'Date: ' + timestamp + '\n'; transcript += '='.repeat(50) + '\n\n'; conversationHistory.forEach((msg, idx) => { transcript += (msg.role === 'user' ? 'You' : botName) + ': ' + msg.text + '\n\n'; }); const blob = new Blob([transcript], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'chat-transcript-' + Date.now() + '.txt'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); console.log('[BuildABot] Transcript downloaded'); } function refreshChat() { const messagesContainer = document.getElementById('buildabot-messages'); if (messagesContainer) { while (messagesContainer.children.length > 1 + (botConfig?.prompt_suggestion_1 || botConfig?.prompt_suggestion_2 || botConfig?.prompt_suggestion_3 ? 1 : 0)) { messagesContainer.removeChild(messagesContainer.lastChild); } conversationHistory = []; console.log('[BuildABot] Chat refreshed'); } } function startNewChat() { const messagesContainer = document.getElementById('buildabot-messages'); if (messagesContainer) { while (messagesContainer.children.length > 1 + (botConfig?.prompt_suggestion_1 || botConfig?.prompt_suggestion_2 || botConfig?.prompt_suggestion_3 ? 1 : 0)) { messagesContainer.removeChild(messagesContainer.lastChild); } } conversationHistory = []; sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); trackEvent('session_started'); console.log('[BuildABot] New chat session started:', sessionId); } function endChat() { isOpen = false; const panel = document.getElementById('buildabot-panel'); if (panel) panel.style.display = 'none'; const launcherCloseBtn = document.getElementById('buildabot-launcher-close'); if (launcherCloseBtn) launcherCloseBtn.style.display = 'flex'; console.log('[BuildABot] Chat ended'); } function injectStyles() { const style = document.createElement('style'); style.textContent = ` /* Prevent input zoom on iOS */ .buildabot-widget input, .buildabot-widget textarea, .buildabot-widget .chat-input, .buildabot-widget .chat-textarea, .buildabot-widget .widget-message-input { font-size: 16px !important; line-height: 1.4em; } /* Desktop font consistency */ @media (min-width: 768px) { .buildabot-widget input, .buildabot-widget textarea { font-size: 14px; } } /* Scoped reset for badge - no host site CSS interference */ .buildabot-widget .bab-powered-by { all: unset; display: block; width: 100%; text-align: center; margin: 10px 0 4px; font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, "Noto Sans", "Helvetica Neue", sans-serif; font-size: 12px; line-height: 1.2; color: #9CA3AF; } .buildabot-widget .bab-powered-by a { all: unset; font-weight: 600; color: #6B7280; cursor: pointer; text-decoration: underline; } .buildabot-widget .bab-powered-by a:hover { color: #374151; text-decoration-thickness: 2px; } /* Ensure links inside chat bubbles always look and act like links */ .buildabot-widget .message-content a { color: #2563EB !important; text-decoration: underline !important; word-break: break-word; overflow-wrap: anywhere; cursor: pointer; } .buildabot-widget .message-content a:hover, .buildabot-widget .message-content a:focus { color: #1D4ED8 !important; text-decoration: underline; } `; document.head.appendChild(style); } function createWidget() { if (!botConfig) { console.error('[BuildABot] No configuration loaded'); return; } // Inject styles first injectStyles(); const position = botConfig.widget_position || 'bottom_right'; const isRightSide = position.includes('right'); const container = document.createElement('div'); container.id = 'buildabot-widget'; container.className = 'buildabot-widget'; container.style.cssText = 'position: fixed; z-index: 2147483647; font-family: ' + (botConfig.white_label && botConfig.white_label_config?.font_family ? botConfig.white_label_config.font_family : 'system-ui, sans-serif') + '; display: flex; flex-direction: column; align-items: ' + (isRightSide ? 'flex-end' : 'flex-start') + ';'; const posMap = { bottom_right: 'bottom: 24px; right: 24px;', bottom_left: 'bottom: 24px; left: 24px;', top_right: 'top: 24px; right: 24px;', top_left: 'top: 24px; left: 24px;' }; container.style.cssText += posMap[position] || posMap.bottom_right; // Chat panel const panel = document.createElement('div'); panel.id = 'buildabot-panel'; panel.style.cssText = 'display: none; margin-bottom: 16px; width: 350px; max-width: 90vw;'; const card = document.createElement('div'); card.style.cssText = 'background: #fff; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.15); display: flex; flex-direction: column; max-height: 600px; overflow: hidden;'; // Header const header = document.createElement('div'); header.id = 'buildabot-header'; const headerColor = botConfig.white_label && botConfig.white_label_config?.header_color ? botConfig.white_label_config.header_color : (botConfig.widget_color || '#2F6FED'); const headerTextColor = botConfig.white_label && botConfig.white_label_config?.header_text_color ? botConfig.white_label_config.header_text_color : '#FFFFFF'; header.style.cssText = 'padding: 16px; display: flex; align-items: center; justify-content: space-between; background: ' + headerColor + '; color: ' + headerTextColor + '; position: relative;'; // Header background image if white-label enabled if (botConfig.white_label && botConfig.white_label_config?.header_bg_image_url) { header.style.backgroundImage = 'url(' + botConfig.white_label_config.header_bg_image_url + ')'; header.style.backgroundSize = 'cover'; header.style.backgroundPosition = 'center'; header.style.backgroundRepeat = 'no-repeat'; // Add overlay for text readability const headerOverlay = document.createElement('div'); headerOverlay.style.cssText = 'position: absolute; inset: 0; background: rgba(0,0,0,0.3);'; header.appendChild(headerOverlay); } // Header background image if custom branding enabled (legacy Pro+) else if (botConfig.custom_branding_enabled && botConfig.header_image_url) { header.style.backgroundImage = 'url(' + botConfig.header_image_url + ')'; header.style.backgroundSize = 'cover'; header.style.backgroundPosition = 'center'; // Add overlay const overlay = document.createElement('div'); overlay.style.cssText = 'position: absolute; inset: 0; background: rgba(0,0,0,0.3);'; header.appendChild(overlay); } const headerContent = document.createElement('div'); headerContent.style.cssText = 'display: flex; align-items: center; gap: 12px; position: relative; z-index: 1;'; // Header icon or avatar const iconWrapper = document.createElement('div'); if (botConfig.custom_branding_enabled && botConfig.header_icon_url) { const iconImg = document.createElement('img'); iconImg.src = botConfig.header_icon_url; iconImg.alt = 'Bot Icon'; iconImg.style.cssText = 'width: 32px; height: 32px; border-radius: 50%; object-fit: cover;'; iconWrapper.appendChild(iconImg); } else { iconWrapper.style.cssText = 'width: 32px; height: 32px; background: rgba(255,255,255,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px;'; iconWrapper.textContent = chatIcons[botConfig.chat_icon] || '💬'; } headerContent.appendChild(iconWrapper); const headerText = document.createElement('div'); headerText.innerHTML = '
' + (botConfig.name || 'Chatbot') + '
Online
'; headerContent.appendChild(headerText); header.appendChild(headerContent); // Header buttons container const headerButtons = document.createElement('div'); headerButtons.style.cssText = 'display: flex; align-items: center; gap: 8px; position: relative; z-index: 2;'; // Menu button (three dots) const menuBtn = document.createElement('button'); menuBtn.id = 'buildabot-menu'; menuBtn.innerHTML = '⋮'; menuBtn.style.cssText = 'background: none; border: none; color: ' + headerTextColor + '; cursor: pointer; padding: 4px 8px; font-size: 24px; line-height: 1; opacity: 0.9; font-weight: bold;'; headerButtons.appendChild(menuBtn); // Menu dropdown const menuDropdown = document.createElement('div'); menuDropdown.id = 'buildabot-menu-dropdown'; menuDropdown.style.cssText = 'display: none; position: absolute; top: 100%; right: 40px; margin-top: 8px; background: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); min-width: 200px; overflow: hidden; z-index: 10;'; const menuItems = [ { icon: '🔄', label: 'New chat', action: startNewChat }, { icon: '↻', label: 'Refresh', action: refreshChat }, { icon: '⬇', label: 'Download transcript', action: downloadTranscript }, { icon: '🚪', label: 'End chat', action: endChat } ]; menuItems.forEach((item, index) => { const menuItem = document.createElement('button'); menuItem.style.cssText = 'width: 100%; padding: 12px 16px; border: none; background: white; cursor: pointer; display: flex; align-items: center; gap: 12px; font-size: 14px; color: #374151; text-align: left; transition: background 0.2s;' + (index > 0 ? ' border-top: 1px solid #e5e7eb;' : ''); const icon = document.createElement('span'); icon.textContent = item.icon; icon.style.cssText = 'font-size: 16px;'; menuItem.appendChild(icon); const label = document.createElement('span'); label.textContent = item.label; menuItem.appendChild(label); menuItem.addEventListener('mouseenter', function() { this.style.background = '#f3f4f6'; }); menuItem.addEventListener('mouseleave', function() { this.style.background = 'white'; }); menuItem.addEventListener('click', function() { item.action(); menuDropdown.style.display = 'none'; }); menuDropdown.appendChild(menuItem); }); header.appendChild(menuDropdown); // Close button const closeBtn = document.createElement('button'); closeBtn.id = 'buildabot-close'; closeBtn.innerHTML = '×'; closeBtn.style.cssText = 'background: none; border: none; color: ' + headerTextColor + '; cursor: pointer; padding: 4px; font-size: 24px; line-height: 1; opacity: 0.9;'; headerButtons.appendChild(closeBtn); header.appendChild(headerButtons); // Messages const messages = document.createElement('div'); messages.id = 'buildabot-messages'; const bodyBg = botConfig.white_label && botConfig.white_label_config?.body_bg_color ? botConfig.white_label_config.body_bg_color : '#f9fafb'; messages.style.cssText = 'flex: 1; overflow-y: auto; padding: 16px; background: ' + bodyBg + '; min-height: 300px; max-height: 400px;'; // Body background image if white-label enabled if (botConfig.white_label && botConfig.white_label_config?.body_bg_image_url) { messages.style.backgroundImage = 'url(' + botConfig.white_label_config.body_bg_image_url + ')'; messages.style.backgroundSize = 'cover'; messages.style.backgroundPosition = 'center'; messages.style.backgroundRepeat = 'no-repeat'; } const greeting = document.createElement('div'); greeting.id = 'buildabot-greeting'; greeting.className = 'message-content'; greeting.textContent = botConfig.greeting_message || 'Hello! How can I help you?'; const botMsgBg = botConfig.white_label && botConfig.white_label_config?.bot_message_bg ? botConfig.white_label_config.bot_message_bg : '#fff'; greeting.style.cssText = 'background: ' + botMsgBg + '; padding: 12px 16px; border-radius: 8px; box-shadow: 0 1px 2px rgba(0,0,0,0.05); font-size: 14px; color: #374151; line-height: 1.5;'; messages.appendChild(greeting); // Prompt Suggestions const promptSuggestions = [ botConfig.prompt_suggestion_1, botConfig.prompt_suggestion_2, botConfig.prompt_suggestion_3 ].filter(p => p && p.trim()); if (promptSuggestions.length > 0) { const promptsContainer = document.createElement('div'); promptsContainer.id = 'buildabot-prompts'; promptsContainer.style.cssText = 'margin-top: 12px; display: flex; flex-wrap: wrap; gap: 8px;'; promptSuggestions.forEach(prompt => { const promptBtn = document.createElement('button'); promptBtn.textContent = prompt; promptBtn.style.cssText = 'background: ' + botMsgBg + '; border: 1px solid #e5e7eb; padding: 8px 12px; border-radius: 8px; font-size: 13px; color: #374151; cursor: pointer; transition: all 0.2s; box-shadow: 0 1px 2px rgba(0,0,0,0.05); text-align: left; flex: 1 1 auto; min-width: 0;'; promptBtn.addEventListener('mouseenter', function() { this.style.background = '#f3f4f6'; this.style.borderColor = '#d1d5db'; }); promptBtn.addEventListener('mouseleave', function() { this.style.background = botMsgBg; this.style.borderColor = '#e5e7eb'; }); promptBtn.addEventListener('click', function() { const input = document.getElementById('buildabot-input'); if (input) { input.value = prompt; input.focus(); } }); promptsContainer.appendChild(promptBtn); }); messages.appendChild(promptsContainer); } // Input area - Footer const inputArea = document.createElement('div'); // Footer background color or image const footerBg = botConfig.white_label && botConfig.white_label_config?.footer_bg_color ? botConfig.white_label_config.footer_bg_color : '#fff'; inputArea.style.cssText = 'padding: 12px; background: ' + footerBg + '; border-top: 1px solid #e5e7eb;'; // Footer background image if white-label enabled if (botConfig.white_label && botConfig.white_label_config?.footer_bg_image_url) { inputArea.style.backgroundImage = 'url(' + botConfig.white_label_config.footer_bg_image_url + ')'; inputArea.style.backgroundSize = 'cover'; inputArea.style.backgroundPosition = 'center'; inputArea.style.backgroundRepeat = 'no-repeat'; } const inputRow = document.createElement('div'); inputRow.style.cssText = 'display: flex; gap: 8px; margin-bottom: 8px;'; const input = document.createElement('input'); input.id = 'buildabot-input'; input.className = 'widget-message-input'; input.type = 'text'; input.placeholder = 'Type a message...'; // IMPORTANT: Input field always has white background regardless of footer settings input.style.cssText = 'flex: 1; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 16px; line-height: 1.4em; outline: none; background: #ffffff !important;'; inputRow.appendChild(input); const sendBtn = document.createElement('button'); sendBtn.id = 'buildabot-send'; sendBtn.textContent = 'Send'; // Use send_button_color if white-label is enabled, otherwise fall back to header color or widget color const btnBg = botConfig.white_label && botConfig.white_label_config?.send_button_color ? botConfig.white_label_config.send_button_color : (botConfig.white_label && botConfig.white_label_config?.header_color ? botConfig.white_label_config.header_color : (botConfig.widget_color || '#2F6FED')); let btnBorderRadius = '8px'; if (botConfig.white_label && botConfig.white_label_config?.button_style === 'pill') { btnBorderRadius = '9999px'; } else if (botConfig.white_label && botConfig.white_label_config?.button_style === 'square') { btnBorderRadius = '0'; } sendBtn.style.cssText = 'background: ' + btnBg + '; color: #fff; border: none; padding: 10px 20px; border-radius: ' + btnBorderRadius + '; font-size: 14px; font-weight: 500; cursor: pointer;'; inputRow.appendChild(sendBtn); inputArea.appendChild(inputRow); // Powered by badge - standardized const badge = document.createElement('div'); badge.className = 'bab-powered-by'; badge.setAttribute('aria-label', 'Powered by'); if (!botConfig.white_label) { // Not white-label mode - use custom branding or default badge if (botConfig.custom_branding_enabled && botConfig.custom_branding?.powered_by_text) { const span = document.createElement('span'); span.textContent = 'Powered by '; badge.appendChild(span); const link = document.createElement('a'); link.href = botConfig.custom_branding.powered_by_url || '#'; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.textContent = botConfig.custom_branding.powered_by_text; badge.appendChild(link); } else if (botConfig.show_badge !== false) { const span = document.createElement('span'); span.textContent = 'Powered by '; badge.appendChild(span); const link = document.createElement('a'); link.href = 'https://buildabot.ai'; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.textContent = 'BuildABot.ai'; badge.appendChild(link); } } else if (botConfig.white_label && botConfig.white_label_config?.powered_by_text) { // White-label mode with custom powered by text const span = document.createElement('span'); span.textContent = 'Powered by '; span.style.color = botConfig.white_label_config.powered_by_color || '#6B7280'; badge.appendChild(span); const link = document.createElement('a'); link.href = botConfig.white_label_config.powered_by_url || '#'; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.textContent = botConfig.white_label_config.powered_by_text; link.style.color = botConfig.white_label_config.powered_by_color || '#6B7280'; badge.appendChild(link); } // If white-label is enabled but no powered_by_text, show no badge inputArea.appendChild(badge); card.appendChild(header); card.appendChild(messages); card.appendChild(inputArea); panel.appendChild(card); // Launcher button const launcher = document.createElement('button'); launcher.id = 'buildabot-launcher'; const launcherBg = botConfig.widget_color || '#2F6FED'; // Build shadow string let shadowStyle = '0 4px 12px rgba(0,0,0,0.15)'; if (botConfig.widget_shadow && botConfig.enable_text_button && botConfig.widget_shadow_config) { const sc = botConfig.widget_shadow_config; const color = sc.color || '#000000'; const r = parseInt(color.slice(1, 3), 16); const g = parseInt(color.slice(3, 5), 16); const b = parseInt(color.slice(5, 7), 16); const opacity = sc.opacity || 0.15; const offsetX = sc.offset_x || 0; const offsetY = sc.offset_y || 0; const blur = sc.blur || 25; const spread = sc.spread || 0; shadowStyle = offsetX + 'px ' + offsetY + 'px ' + blur + 'px ' + spread + 'px rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')'; } // Check if text button is enabled if (botConfig.enable_text_button && (botConfig.chat_button_text?.trim() || botConfig.button_logo_url)) { // Text/logo button if (botConfig.button_logo_url && !botConfig.chat_button_text?.trim()) { // Logo only launcher.style.cssText = 'width: 56px; height: 56px; background: ' + launcherBg + '; border-radius: 50%; border: none; cursor: pointer; box-shadow: ' + shadowStyle + '; display: flex; align-items: center; justify-content: center; padding: 8px; transition: transform 0.2s;'; const logoImg = document.createElement('img'); logoImg.src = botConfig.button_logo_url; logoImg.alt = 'Logo'; logoImg.style.cssText = 'width: 100%; height: 100%; border-radius: 50%; object-fit: contain;'; launcher.appendChild(logoImg); } else { // Text with optional logo OR icon launcher.style.cssText = 'background: ' + launcherBg + '; color: #fff; border: none; cursor: pointer; box-shadow: ' + shadowStyle + '; padding: 12px 24px; border-radius: 9999px; font-size: 14px; font-weight: 500; display: flex; align-items: center; gap: 8px; transition: transform 0.2s;'; // Show logo if provided, otherwise show icon if (botConfig.button_logo_url) { const logoImg = document.createElement('img'); logoImg.src = botConfig.button_logo_url; logoImg.alt = 'Logo'; logoImg.style.cssText = 'width: 20px; height: 20px; border-radius: 4px; object-fit: contain;'; launcher.appendChild(logoImg); } else { // No logo URL provided, show the chat icon const iconSpan = document.createElement('span'); iconSpan.textContent = chatIcons[botConfig.chat_icon] || '💬'; iconSpan.style.cssText = 'font-size: 16px;'; launcher.appendChild(iconSpan); } // Show text if provided if (botConfig.chat_button_text && botConfig.chat_button_text.trim()) { const textSpan = document.createElement('span'); textSpan.textContent = botConfig.chat_button_text.trim(); launcher.appendChild(textSpan); } } } else { // Standard icon button launcher.style.cssText = 'width: 56px; height: 56px; background: ' + launcherBg + '; border-radius: 50%; border: none; cursor: pointer; box-shadow: ' + shadowStyle + '; font-size: 24px; display: flex; align-items: center; justify-content: center; transition: transform 0.2s;'; launcher.textContent = chatIcons[botConfig.chat_icon] || '💬'; } launcher.addEventListener('mouseenter', function() { this.style.transform = 'scale(1.1)'; }); launcher.addEventListener('mouseleave', function() { this.style.transform = 'scale(1)'; }); // Create launcher wrapper with close button const launcherWrapper = document.createElement('div'); launcherWrapper.style.cssText = 'position: relative;'; // Close button on launcher - 15% lighter color const launcherCloseBtn = document.createElement('button'); launcherCloseBtn.id = 'buildabot-launcher-close'; launcherCloseBtn.innerHTML = '×'; launcherCloseBtn.style.cssText = 'position: absolute; top: -8px; right: -8px; width: 24px; height: 24px; background: rgba(129, 135, 147, 0.7); color: white; border: none; border-radius: 50%; cursor: pointer; font-size: 18px; line-height: 1; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.15); transition: background 0.2s; z-index: 1;'; launcherCloseBtn.addEventListener('mouseenter', function() { this.style.background = 'rgba(129, 135, 147, 0.9)'; }); launcherCloseBtn.addEventListener('mouseleave', function() { this.style.background = 'rgba(129, 135, 147, 0.7)'; }); launcherWrapper.appendChild(launcher); launcherWrapper.appendChild(launcherCloseBtn); container.appendChild(panel); container.appendChild(launcherWrapper); document.body.appendChild(container); console.log('[BuildABot] Widget UI created successfully'); // Mobile keyboard handling let keyboardOpen = false; window.addEventListener('resize', function() { if (window.innerHeight < 500) { keyboardOpen = true; } else if (keyboardOpen) { keyboardOpen = false; setTimeout(function() { if (container && isOpen) { container.scrollIntoView({ behavior: 'smooth', block: 'end' }); } }, 250); } }); // Linkification observer const observer = new MutationObserver(function(mutations) { for (const m of mutations) { m.addedNodes.forEach(function(node) { if (!(node instanceof HTMLElement)) return; const targets = node.matches && node.matches('.message-content') ? [node] : node.querySelectorAll ? node.querySelectorAll('.message-content') : []; if (targets.length) { targets.forEach(function(el) { if (!el.dataset.linkified) { const original = el.textContent || ''; el.innerHTML = linkify(original); el.dataset.linkified = '1'; } }); } }); } }); observer.observe(container, { childList: true, subtree: true }); // Linkify existing greeting const greetingEl = document.getElementById('buildabot-greeting'); if (greetingEl && !greetingEl.dataset.linkified) { const original = greetingEl.textContent || ''; greetingEl.innerHTML = linkify(original); greetingEl.dataset.linkified = '1'; } // Event handlers launcher.addEventListener('click', function() { isOpen = !isOpen; panel.style.display = isOpen ? 'block' : 'none'; launcherCloseBtn.style.display = isOpen ? 'none' : 'flex'; if (isOpen && !sessionId) { sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); trackEvent('session_started'); console.log('[BuildABot] Session started:', sessionId); } }); closeBtn.addEventListener('click', function() { isOpen = false; panel.style.display = 'none'; launcherCloseBtn.style.display = 'flex'; menuDropdown.style.display = 'none'; }); menuBtn.addEventListener('click', function(e) { e.stopPropagation(); const isVisible = menuDropdown.style.display === 'block'; menuDropdown.style.display = isVisible ? 'none' : 'block'; }); document.addEventListener('click', function(e) { if (!menuBtn.contains(e.target) && !menuDropdown.contains(e.target)) { menuDropdown.style.display = 'none'; } }); launcherCloseBtn.addEventListener('click', function(e) { e.stopPropagation(); container.style.display = 'none'; console.log('[BuildABot] Widget hidden by user'); }); async function handleSend() { const text = input.value.trim(); if (!text) return; console.log('[BuildABot] Sending message:', text); conversationHistory.push({ role: 'user', text: text }); const userBubble = document.createElement('div'); userBubble.className = 'message-content'; userBubble.textContent = text; const userMsgBg = botConfig.white_label && botConfig.white_label_config?.user_message_bg ? botConfig.white_label_config.user_message_bg : (botConfig.widget_color || '#2F6FED'); userBubble.style.cssText = 'background: ' + userMsgBg + '; color: #fff; padding: 12px 16px; border-radius: 8px; margin-top: 12px; font-size: 14px; max-width: 80%; margin-left: auto; word-wrap: break-word;'; messages.appendChild(userBubble); input.value = ''; messages.scrollTop = messages.scrollHeight; await trackEvent('message_sent', { message: text }); const typing = document.createElement('div'); typing.id = 'typing-indicator'; typing.textContent = 'Typing...'; typing.style.cssText = 'background: ' + botMsgBg + '; padding: 12px 16px; border-radius: 8px; margin-top: 12px; font-size: 14px; color: #9ca3af; box-shadow: 0 1px 2px rgba(0,0,0,0.05);'; messages.appendChild(typing); messages.scrollTop = messages.scrollHeight; try { const reply = await sendMessage(text); typing.remove(); conversationHistory.push({ role: 'bot', text: reply }); const botBubble = document.createElement('div'); botBubble.className = 'message-content'; botBubble.textContent = reply; botBubble.style.cssText = 'background: ' + botMsgBg + '; padding: 12px 16px; border-radius: 8px; margin-top: 12px; font-size: 14px; color: #374151; box-shadow: 0 1px 2px rgba(0,0,0,0.05); line-height: 1.5; max-width: 80%; word-wrap: break-word;'; messages.appendChild(botBubble); messages.scrollTop = messages.scrollHeight; console.log('[BuildABot] Response received'); } catch (error) { typing.remove(); const errBubble = document.createElement('div'); errBubble.className = 'message-content'; errBubble.textContent = 'Sorry, something went wrong.'; errBubble.style.cssText = 'background: #fff; padding: 12px 16px; border-radius: 8px; margin-top: 12px; font-size: 14px; color: #b91c1c; box-shadow: 0 1px 2px rgba(0,0,0,0.05);'; messages.appendChild(errBubble); } } sendBtn.addEventListener('click', handleSend); input.addEventListener('keypress', function(e) { if (e.key === 'Enter') handleSend(); }); } async function init() { console.log('[BuildABot] Starting initialization...'); await fetchBotConfig(); if (botConfig) { createWidget(); console.log('[BuildABot] Widget initialized successfully'); } else { console.error('[BuildABot] Failed to initialize - no configuration loaded'); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } } catch (err) { console.error('[BuildABot] Initialization error:', err); } })();