(function() {
'use strict';
const config = window.AIChatConfig || {};
const orgId = config.organizationId;
const apiKey = config.apiKey;
if (!orgId || !apiKey) {
console.error('AIChatWidget: organizationId and apiKey required');
return;
}
// Check page visibility - if visiblePages is set, only show on exact matching pages
const visiblePages = config.visiblePages || [];
if (visiblePages.length > 0) {
const currentPath = window.location.pathname;
// Remove query string and hash for clean URL comparison
const currentUrlClean = window.location.origin + window.location.pathname;
const isVisible = visiblePages.some(function(page) {
// Normalize: remove trailing slashes for comparison
const normalizedPage = page.replace(/\/+$/, '');
// If page starts with http:// or https://, match against full URL (exact match)
if (page.startsWith('http://') || page.startsWith('https://')) {
const normalizedCurrentUrl = currentUrlClean.replace(/\/+$/, '');
return normalizedCurrentUrl === normalizedPage;
}
// Otherwise, treat as path and match against pathname (exact match)
const normalizedPath = currentPath.replace(/\/+$/, '');
// Handle root path special case
if (normalizedPage === '' && normalizedPath === '') {
return true;
}
return normalizedPath === normalizedPage;
});
if (!isVisible) {
console.log('[Widget] Page not in visiblePages list, not showing widget. Current path:', currentPath);
return;
}
}
let iframe = null;
let isOpen = false;
let isInitialized = false;
let isFullscreen = false;
let iframeReady = false;
let pendingMessages = [];
// Create pre-chat messages and CTA bubbles container
const preChatMessages = config.preChatMessages || [];
const ctaButton = config.ctaButton || { enabled: false, text: '', formId: '' };
let bubblesContainer = null;
const dismissKey = 'ai_chat_prechat_dismissed_' + orgId;
const isDismissed = localStorage.getItem(dismissKey) === 'true';
const hasContent = preChatMessages.length > 0 || (ctaButton.enabled && ctaButton.text && ctaButton.formId);
if (hasContent && !isDismissed) {
bubblesContainer = document.createElement('div');
bubblesContainer.style.cssText = `
position: fixed;
${config.position === 'bottom-left' ? 'left: 20px' : 'right: 20px'};
bottom: 95px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 999999;
max-width: 280px;
`;
// Single dismiss button for all messages and CTA
const dismissBtn = document.createElement('button');
dismissBtn.innerHTML = ``;
dismissBtn.style.cssText = `
position: absolute;
top: -8px;
${config.position === 'bottom-left' ? 'right: -8px' : 'right: -8px'};
width: 22px;
height: 22px;
border: none;
background: white;
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
color: #9ca3af;
border-radius: 50%;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
transition: color 0.2s, background 0.2s, transform 0.2s;
z-index: 1;
`;
dismissBtn.onmouseover = () => {
dismissBtn.style.color = '#6b7280';
dismissBtn.style.background = '#f9fafb';
dismissBtn.style.transform = 'scale(1.1)';
};
dismissBtn.onmouseout = () => {
dismissBtn.style.color = '#9ca3af';
dismissBtn.style.background = 'white';
dismissBtn.style.transform = 'scale(1)';
};
dismissBtn.onclick = (e) => {
e.stopPropagation();
localStorage.setItem(dismissKey, 'true');
if (bubblesContainer) {
bubblesContainer.style.opacity = '0';
bubblesContainer.style.transform = 'translateY(10px)';
bubblesContainer.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out';
setTimeout(() => {
if (bubblesContainer && bubblesContainer.parentNode) {
bubblesContainer.parentNode.removeChild(bubblesContainer);
}
}, 300);
}
};
bubblesContainer.appendChild(dismissBtn);
// Add pre-chat message bubbles
preChatMessages.forEach((message, index) => {
const bubble = document.createElement('div');
bubble.style.cssText = `
background: white;
color: #1f2937;
padding: 12px 16px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.4;
opacity: 0;
transform: translateY(10px);
animation: slideUp 0.4s ease-out ${index * 0.15}s forwards;
`;
bubble.textContent = message;
bubblesContainer.appendChild(bubble);
});
// Add CTA button if enabled
if (ctaButton.enabled && ctaButton.text && ctaButton.formId) {
const ctaBtn = document.createElement('button');
ctaBtn.textContent = ctaButton.text;
ctaBtn.style.cssText = `
background: ${config.primaryColor || '#046eff'};
color: white;
padding: 10px 16px;
border-radius: 20px;
border: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
font-weight: 500;
line-height: 1.4;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
`;
ctaBtn.onmouseover = () => {
ctaBtn.style.transform = 'scale(1.02)';
ctaBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)';
};
ctaBtn.onmouseout = () => {
ctaBtn.style.transform = 'scale(1)';
ctaBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
};
ctaBtn.onclick = (e) => {
e.stopPropagation();
if (window.AIChatWidget && window.AIChatWidget.openForm) {
window.AIChatWidget.openForm(ctaButton.formId);
}
};
bubblesContainer.appendChild(ctaBtn);
}
}
// Create floating button (no server calls)
const button = document.createElement('button');
button.setAttribute('aria-label', 'Open chat');
// Determine button size
const buttonSize = config.buttonSize || 'medium';
const sizeMap = {
'very-small': { size: '40px', iconSize: '18' },
small: { size: '48px', iconSize: '20' },
medium: { size: '60px', iconSize: '24' },
large: { size: '72px', iconSize: '28' },
'very-large': { size: '84px', iconSize: '32' }
};
const { size, iconSize } = sizeMap[buttonSize] || sizeMap.medium;
button.innerHTML = `
`;
button.style.cssText = `
position: fixed;
${config.position === 'bottom-left' ? 'left: 20px' : 'right: 20px'};
bottom: 20px;
width: ${size};
height: ${size};
border-radius: 50%;
background: ${config.primaryColor || '#046eff'};
border: none;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
color: white;
display: flex;
align-items: center;
justify-content: center;
z-index: 999999;
transition: transform 0.2s, box-shadow 0.2s;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
`;
button.onmouseover = () => {
button.style.transform = 'scale(1.1)';
button.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)';
};
button.onmouseout = () => {
button.style.transform = 'scale(1)';
button.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
};
// Initialize chat ONLY when button is clicked
button.onclick = (e) => {
e.stopPropagation();
if (!isInitialized) {
initializeChat();
isInitialized = true;
}
toggleChat();
};
// Lazy initialization - creates iframe on first click
function initializeChat() {
console.log('[Widget] initializeChat() called');
// Create iframe container
const container = document.createElement('div');
container.style.cssText = `
position: fixed;
${config.position === 'bottom-left' ? 'left: 20px' : 'right: 20px'};
bottom: 90px;
width: 400px;
height: 600px;
max-width: calc(100vw - 40px);
max-height: calc(100vh - 120px);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
overflow: hidden;
display: none;
z-index: 999998;
background: white;
border: 1px solid rgba(0,0,0,0.1);
`;
// Create iframe - THIS IS THE FIRST SERVER REQUEST
iframe = document.createElement('iframe');
const baseUrl = config.baseUrl || window.location.origin;
const showBranding = config.showBranding !== false ? 'true' : 'false';
const showMenuButtons = config.showMenuButtons !== false ? 'true' : 'false';
const params = new URLSearchParams({
apiKey: apiKey,
color: config.primaryColor || '#046eff',
showBranding: showBranding,
showMenuButtons: showMenuButtons,
headerBg: config.headerBackgroundColor || '#ffffff',
chatBg: config.chatBackgroundColor || '#f9fafb',
titleColor: config.titleColor || '#046eff',
userMsgColor: config.userMessageColor || '#046eff',
assistantMsgColor: config.assistantMessageColor || '#f3f4f6',
title: config.title || 'Chat Assistant'
});
iframe.src = `${baseUrl}/embed/chat/${orgId}?${params.toString()}`;
iframe.style.cssText = `
width: 100%;
height: 100%;
border: none;
border-radius: 12px;
`;
iframe.allow = 'clipboard-write';
iframe.title = 'Chat widget';
container.appendChild(iframe);
document.body.appendChild(container);
// Store reference for toggling
window.__aiChatContainer = container;
console.log('[Widget] Container created and stored:', container);
}
function toggleChat() {
console.log('[Widget] toggleChat() called, isOpen before:', isOpen);
const container = window.__aiChatContainer;
if (!container) {
console.error('[Widget] Container not found in toggleChat!');
return;
}
isOpen = !isOpen;
console.log('[Widget] isOpen after toggle:', isOpen);
container.style.display = isOpen ? 'block' : 'none';
console.log('[Widget] Container display set to:', container.style.display);
// Toggle pre-chat bubbles visibility (swirl keeps animating)
if (bubblesContainer) {
bubblesContainer.style.display = isOpen ? 'none' : 'flex';
}
// Update button icon
button.innerHTML = isOpen
? ``
: ``;
}
// Add CSS animations for bubbles and button swirl
const style = document.createElement('style');
const buttonAnimation = config.buttonAnimation || { enabled: false, secondaryColor: '#ff6b6b' };
const primaryColor = config.primaryColor || '#046eff';
const secondaryColor = buttonAnimation.secondaryColor || '#ff6b6b';
style.textContent = `
@keyframes slideUp {
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes lavaBlob1 {
0%, 100% { transform: translate(0%, 0%) scale(1); }
25% { transform: translate(30%, 20%) scale(1.1); }
50% { transform: translate(10%, 40%) scale(0.9); }
75% { transform: translate(-20%, 15%) scale(1.05); }
}
@keyframes lavaBlob2 {
0%, 100% { transform: translate(0%, 0%) scale(1); }
25% { transform: translate(-25%, 30%) scale(0.95); }
50% { transform: translate(20%, -20%) scale(1.1); }
75% { transform: translate(35%, 25%) scale(1); }
}
@keyframes lavaBlob3 {
0%, 100% { transform: translate(0%, 0%) scale(1.05); }
33% { transform: translate(-30%, -25%) scale(0.9); }
66% { transform: translate(25%, 30%) scale(1.1); }
}
.ai-lava-container {
position: fixed;
border-radius: 50%;
pointer-events: none;
z-index: 999998;
overflow: hidden;
background: ${primaryColor};
}
.ai-lava-blob {
position: absolute;
border-radius: 50%;
filter: blur(8px);
}
.ai-lava-blob-1 {
width: 70%;
height: 70%;
top: 10%;
left: 10%;
background: ${secondaryColor};
animation: lavaBlob1 3s ease-in-out infinite;
}
.ai-lava-blob-2 {
width: 60%;
height: 60%;
top: 25%;
left: 25%;
background: ${primaryColor};
animation: lavaBlob2 4s ease-in-out infinite;
}
.ai-lava-blob-3 {
width: 50%;
height: 50%;
top: 20%;
left: 30%;
background: ${secondaryColor};
opacity: 0.8;
animation: lavaBlob3 5s ease-in-out infinite;
}
.ai-chat-button-animated {
background: transparent !important;
box-shadow: none !important;
border: none !important;
}
.ai-chat-button-animated:hover {
box-shadow: none !important;
border: none !important;
transform: scale(1.1);
}
`;
document.head.appendChild(style);
// Apply lava lamp animation to button if enabled
let lavaElement = null;
if (buttonAnimation.enabled) {
lavaElement = document.createElement('div');
lavaElement.className = 'ai-lava-container';
lavaElement.style.cssText = `
${config.position === 'bottom-left' ? 'left: 20px' : 'right: 20px'};
bottom: 20px;
width: ${size};
height: ${size};
`;
// Create the blobs
const blob1 = document.createElement('div');
blob1.className = 'ai-lava-blob ai-lava-blob-1';
const blob2 = document.createElement('div');
blob2.className = 'ai-lava-blob ai-lava-blob-2';
const blob3 = document.createElement('div');
blob3.className = 'ai-lava-blob ai-lava-blob-3';
lavaElement.appendChild(blob1);
lavaElement.appendChild(blob2);
lavaElement.appendChild(blob3);
// Make the main button transparent so lava shows through
button.classList.add('ai-chat-button-animated');
button.style.zIndex = '1000000';
}
// Add button, bubbles, and lava animation to page when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
if (lavaElement) {
document.body.appendChild(lavaElement);
}
if (bubblesContainer) {
document.body.appendChild(bubblesContainer);
}
document.body.appendChild(button);
});
} else {
if (lavaElement) {
document.body.appendChild(lavaElement);
}
if (bubblesContainer) {
document.body.appendChild(bubblesContainer);
}
document.body.appendChild(button);
}
// Note: Widget only closes when close button is clicked
// No outside click or escape key handling
// Function to send message to iframe (queues if not ready)
function sendMessageToIframe(message) {
if (iframeReady && iframe && iframe.contentWindow) {
console.log('[Widget] Sending message to iframe:', message);
iframe.contentWindow.postMessage(message, '*');
} else {
console.log('[Widget] Iframe not ready, queueing message:', message);
pendingMessages.push(message);
}
}
// Process any pending messages
function processPendingMessages() {
if (pendingMessages.length > 0 && iframe && iframe.contentWindow) {
console.log('[Widget] Processing', pendingMessages.length, 'pending messages');
pendingMessages.forEach(function(message) {
console.log('[Widget] Sending queued message:', message);
iframe.contentWindow.postMessage(message, '*');
});
pendingMessages = [];
}
}
// Listen for messages from iframe
window.addEventListener('message', (event) => {
console.log('Widget received message:', event.data);
if (!event.data || typeof event.data !== 'object') return;
if (event.data.type === 'AI_CHAT_FULLSCREEN') {
console.log('Toggling fullscreen to:', event.data.fullscreen);
toggleFullscreen(event.data.fullscreen);
}
// Handle iframe ready signal
if (event.data.type === 'AI_CHAT_IFRAME_READY') {
console.log('[Widget] Iframe signaled ready');
iframeReady = true;
processPendingMessages();
}
});
function toggleFullscreen(shouldBeFullscreen) {
console.log('toggleFullscreen called with:', shouldBeFullscreen);
const container = window.__aiChatContainer;
console.log('Container:', container);
if (!container) {
console.error('Container not found!');
return;
}
isFullscreen = shouldBeFullscreen;
if (isFullscreen) {
console.log('Applying fullscreen styles');
// Fullscreen mode
container.style.cssText = `
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
max-width: 100vw;
max-height: 100vh;
border-radius: 0;
box-shadow: none;
display: block;
z-index: 999998;
background: white;
border: none;
`;
} else {
console.log('Applying normal styles');
// Normal mode
container.style.cssText = `
position: fixed;
${config.position === 'bottom-left' ? 'left: 20px' : 'right: 20px'};
bottom: 90px;
width: 400px;
height: 600px;
max-width: calc(100vw - 40px);
max-height: calc(100vh - 120px);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
overflow: hidden;
display: block;
z-index: 999998;
background: white;
border: 1px solid rgba(0,0,0,0.1);
`;
}
}
// Expose API for programmatic control
window.AIChatWidget = {
open: function() {
if (!isInitialized) {
initializeChat();
isInitialized = true;
}
if (!isOpen) {
toggleChat();
}
},
close: function() {
if (isOpen) {
toggleChat();
}
},
openForm: function(formId) {
console.log('[Widget] openForm() called with formId:', formId);
console.log('[Widget] isInitialized:', isInitialized, 'isOpen:', isOpen, 'iframeReady:', iframeReady);
if (!isInitialized) {
console.log('[Widget] Initializing chat...');
initializeChat();
isInitialized = true;
}
if (!isOpen) {
console.log('[Widget] Chat is closed, calling toggleChat()...');
toggleChat();
} else {
console.log('[Widget] Chat is already open');
}
// Send message (will be queued if iframe not ready)
sendMessageToIframe({
type: 'AI_CHAT_OPEN_FORM',
formId: formId
});
},
openHelpCenter: function() {
console.log('[Widget] openHelpCenter() called');
if (!isInitialized) {
initializeChat();
isInitialized = true;
}
if (!isOpen) {
toggleChat();
}
// Send message (will be queued if iframe not ready)
sendMessageToIframe({
type: 'AI_CHAT_OPEN_HELP_CENTER'
});
},
openFeedbackCenter: function() {
console.log('[Widget] openFeedbackCenter() called');
if (!isInitialized) {
initializeChat();
isInitialized = true;
}
if (!isOpen) {
toggleChat();
}
// Send message (will be queued if iframe not ready)
sendMessageToIframe({
type: 'AI_CHAT_OPEN_FEEDBACK_CENTER'
});
}
};
})();