(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);
}
})();