1(function() { 2 'use strict'; 3 4 const config = typeof chatkitConfig !== 'undefined' ? chatkitConfig : {}; 5 6 let isOpen = false; 7 let retryCount = 0; 8 const MAX_RETRIES = 3; 9 10 // Helper to convert WordPress boolean strings to actual booleans 11 function toBool(value) { 12 if (typeof value === 'boolean') return value; 13 if (typeof value === 'string') return value === '1' || value.toLowerCase() === 'true'; 14 if (typeof value === 'number') return value === 1; 15 return !!value; 16 } 17 18 function loadChatkitScript() { 19 return new Promise((resolve, reject) => { 20 if (customElements.get('openai-chatkit')) { 21 resolve(); 22 return; 23 } 24 25 const script = document.createElement('script'); 26 script.src = 'https://cdn.platform.openai.com/deployments/chatkit/chatkit.js'; 27 script.defer = true; 28 script.onload = resolve; 29 script.onerror = () => reject(new Error('Failed to load ChatKit CDN')); 30 document.head.appendChild(script); 31 }); 32 } 33 34 async function getClientSecret() { 35 try { 36 if (!config.restUrl) { 37 throw new Error('Missing configuration'); 38 } 39 40 const headers = { 41 'Content-Type': 'application/json' 42 }; 43 44 const controller = new AbortController(); 45 const timeoutId = setTimeout(() => controller.abort(), 10000); 46 47 const response = await fetch(config.restUrl, { 48 method: 'POST', 49 headers: headers, 50 signal: controller.signal, 51 credentials: 'same-origin' 52 }); 53 54 clearTimeout(timeoutId); 55 56 if (!response.ok) { 57 const errorData = await response.json().catch(() => ({})); 58 59 console.error('ChatKit Session Error:', { 60 status: response.status, 61 statusText: response.statusText, 62 error: errorData 63 }); 64 65 throw new Error(errorData.message || `HTTP ${response.status}`); 66 } 67 68 const data = await response.json(); 69 70 if (!data.client_secret) { 71 throw new Error('Invalid response: missing client_secret'); 72 } 73 74 return data.client_secret; 75 76 } catch (error) { 77 console.error('Fetch Session Error:', error); 78 79 const errorMessage = config.i18n?.unableToStart || 'â ï¸ Unable to start chat. Please try again later.'; 80 81 const el = document.getElementById('myChatkit'); 82 if (el && el.parentNode) { 83 const errorDiv = document.createElement('div'); 84 errorDiv.style.cssText = 'padding: 20px; text-align: center; color: #721c24; background: #f8d7da; border-radius: 8px; margin: 20px;'; 85 errorDiv.setAttribute('role', 'alert'); 86 errorDiv.innerHTML = `<p style="margin: 0; font-size: 14px;">${errorMessage}</p>`; 87 el.parentNode.insertBefore(errorDiv, el); 88 } 89 90 if (typeof gtag !== 'undefined') { 91 gtag('event', 'exception', { 92 description: 'ChatKit session error: ' + error.message, 93 fatal: false 94 }); 95 } 96 97 return null; 98 } 99 } 100 101 function setupToggle() { 102 const button = document.getElementById('chatToggleBtn'); 103 const chatkit = document.getElementById('myChatkit'); 104 105 if (!button || !chatkit) { 106 console.warn('ChatKit toggle elements not found'); 107 return; 108 } 109 110 const originalText = button.textContent || config.buttonText || 'Chat now'; 111 const closeText = config.closeText || 'â'; 112 const accentColor = config.accentColor || '#FF4500'; 113 114 button.addEventListener('click', () => { 115 isOpen = !isOpen; 116 chatkit.style.display = isOpen ? 'block' : 'none'; 117 button.setAttribute('aria-expanded', isOpen); 118 chatkit.setAttribute('aria-modal', isOpen); 119 120 if (isOpen) { 121 button.classList.add('chatkit-open'); 122 button.textContent = closeText; 123 button.style.backgroundColor = accentColor; 124 chatkit.style.animation = 'chatkit-slide-up 0.3s ease-out'; 125 126 setTimeout(() => chatkit.focus(), 100); 127 128 if (window.innerWidth <= 768) { 129 document.body.style.overflow = 'hidden'; 130 } 131 } else { 132 button.classList.remove('chatkit-open'); 133 button.textContent = originalText; 134 button.style.backgroundColor = accentColor; 135 button.focus(); 136 document.body.style.overflow = ''; 137 } 138 });
139 140 document.addEventListener('keydown', (e) => { 141 if (e.key === 'Escape' && isOpen) { 142 button.click(); 143 } 144 }); 145 146 document.addEventListener('click', (e) => { 147 if (isOpen && 148 !chatkit.contains(e.target) && 149 !button.contains(e.target)) { 150 button.click(); 151 } 152 }); 153 } 154 155 function buildPrompts() { 156 const prompts = []; 157 158 // Support for new array format 159 if (config.prompts && Array.isArray(config.prompts) && config.prompts.length > 0) { 160 config.prompts.forEach(prompt => { 161 if (prompt && prompt.label && prompt.text) { 162 prompts.push({ 163 icon: prompt.icon || 'circle-question', 164 label: prompt.label, 165 prompt: prompt.text 166 }); 167 } 168 }); 169 } 170 // Fallback to old format 171 else { 172 if (config.defaultPrompt1 && config.defaultPrompt1Text) { 173 prompts.push({ 174 icon: 'circle-question', 175 label: config.defaultPrompt1, 176 prompt: config.defaultPrompt1Text 177 }); 178 } 179 180 if (config.defaultPrompt2 && config.defaultPrompt2Text) { 181 prompts.push({ 182 icon: 'circle-question', 183 label: config.defaultPrompt2, 184 prompt: config.defaultPrompt2Text 185 }); 186 } 187 188 if (config.defaultPrompt3 && config.defaultPrompt3Text) { 189 prompts.push({ 190 icon: 'circle-question', 191 label: config.defaultPrompt3, 192 prompt: config.defaultPrompt3Text 193 }); 194 } 195 } 196 197 // Fallback default 198 if (prompts.length === 0) { 199 prompts.push({ 200 icon: 'circle-question', 201 label: 'How can I assist you?', 202 prompt: 'Hi! How can I assist you today?' 203 }); 204 } 205 206 return prompts; 207 } 208 209 function showUserError(message) { 210 const errorDiv = document.createElement('div'); 211 errorDiv.style.cssText = 'position: fixed; bottom: 20px; right: 20px; padding: 15px 20px; background: #f8d7da; color: #721c24; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.15); z-index: 9999; max-width: 300px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;'; 212 errorDiv.setAttribute('role', 'alert'); 213 errorDiv.innerHTML = `<p style="margin: 0; font-size: 14px;">${message}</p>`; 214 document.body.appendChild(errorDiv); 215 216 setTimeout(() => { 217 if (errorDiv.parentNode) { 218 errorDiv.style.opacity = '0'; 219 errorDiv.style.transition = 'opacity 0.3s ease'; 220 setTimeout(() => errorDiv.remove(), 300); 221 } 222 }, 5000); 223 } 224 225 async function initChatKit() { 226 try { 227 if (!config.restUrl) { 228 console.error('ChatKit configuration missing: restUrl not defined'); 229 const errorMsg = config.i18n?.configError || 'â ï¸ Chat configuration error. Please contact support.'; 230 showUserError(errorMsg); 231 return; 232 } 233 234 await loadChatkitScript(); 235 236 if (!customElements.get('openai-chatkit')) { 237 await customElements.whenDefined('openai-chatkit'); 238 } 239 240 const chatkitElement = document.getElementById('myChatkit'); 241 if (!chatkitElement) { 242 console.error('Element #myChatkit not found in DOM'); 243 244 if (retryCount < MAX_RETRIES) { 245 retryCount++; 246 console.log(`Retrying ChatKit initialization (${retryCount}/${MAX_RETRIES})...`); 247 setTimeout(initChatKit, 1000); 248 } else { 249 const errorMsg = config.i18n?.loadFailed || 'â ï¸ Chat widget failed to load. Please refresh the page.'; 250 showUserError(errorMsg); 251 } 252 return; 253 } 254 255 setupToggle(); 256 257 console.log('ð ChatKit Config Received:', { 258 showHeader: config.showHeader, 259 headerTitleText: config.headerTitleText, 260 historyEnabled: config.historyEnabled, 261 enableAttachments: config.enableAttachments, 262 disclaimerText: config.disclaimerText ? 'Set' : 'Not set' 263 }); 264 265 // â BUILD BASE OPTIONS with SAFE values 266 const options = { 267 api: { 268 getClientSecret: getClientSecret 269 }, 270 theme: { 271 colorScheme: config.themeMode || 'dark', 272 // â ALWAYS FIXED (CSS handles visual customization) 273 radius: 'round', 274 density: 'normal', 275 color: { 276 accent: { 277 primary: config.accentColor || '#FF4500', 278 level: parseInt(config.accentLevel) || 2 279 } 280 }, 281 // â ALWAYS present (will be overridden if custom) 282 typography: { 283 baseSize: 16, 284 fontFamily: '"OpenAI Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' 285 } 286 }, 287 composer: { 288 attachments: { 289 enabled: toBool(config.enableAttachments) 290 }, 291 placeholder: config.placeholderText || 'Send a message...' 292 }, 293 startScreen: { 294 greeting: config.greetingText || 'How can I help you today?', 295 prompts: buildPrompts() 296 }, 297 widgets: { 298 async onAction(action, widgetItem) { 299 // console.log('[ChatKit] onAction:', action, widgetItem); 300 301 window.dispatchEvent(new CustomEvent('chatkit:open-url', { 302 detail: { data:action } 303 })); 304 // if (window.parent && window.parent !== window) { 305 // window.parent.postMessage({ type: 'chatkit:open-url', data:action }, '*'); 306 // } 307 } 308 } 309 }; 310 311 // â FILE UPLOAD with extra params (if enabled) 312 if (toBool(config.enableAttachments)) { 313 try { 314 const maxSize = parseInt(config.attachmentMaxSize) || 20; 315 const maxCount = parseInt(config.attachmentMaxCount) || 3; 316 317 options.composer.attachments = { 318 enabled: true, 319 maxSize: maxSize * 1024 * 1024, 320 maxCount: maxCount, 321 accept: { 322 'application/pdf': ['.pdf'], 323 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.webp'], 324 'text/plain': ['.txt'] 325 } 326 }; 327 328 console.log('â Attachments enabled with params:', { maxSize: maxSize + 'MB', maxCount }); 329 } catch (e) { 330 console.warn('Attachments config error, using basic mode:', e); 331 } 332 } 333 334 // â INITIAL THREAD ID 335 if (config.initialThreadId && config.initialThreadId.trim() !== '') { 336 options.initialThread = config.initialThreadId; 337 console.log('â Initial thread set:', config.initialThreadId); 338 } 339 340 // â DISCLAIMER 341 if (config.disclaimerText && config.disclaimerText.trim() !== '') { 342 options.disclaimer = { 343 text: config.disclaimerText, 344 highContrast: toBool(config.disclaimerHighContrast) 345 }; 346 console.log('â Disclaimer configured'); 347 } 348 349 // â CUSTOM TYPOGRAPHY (overrides default) 350 if (config.customFont && config.customFont.fontFamily && config.customFont.fontFamily.trim() !== '') { 351 try { 352 options.theme.typography = { 353 fontFamily: config.customFont.fontFamily, 354 baseSize: parseInt(config.customFont.baseSize) || 16 355 }; 356 console.log('â Custom typography applied'); 357 } catch (e) { 358 console.warn('Typography config error, using default:', e); 359 } 360 } 361 362 // â HEADER 363 if (toBool(config.showHeader)) { 364 const headerConfig = { enabled: true }; 365 366 // Custom title 367 if (config.headerTitleText && config.headerTitleText.trim() !== '') { 368 headerConfig.title = { 369 enabled: true, 370 text: config.headerTitleText 371 }; 372 console.log('â Header custom title:', config.headerTitleText); 373 } 374 375 // Left action button 376 if (config.headerLeftIcon && config.headerLeftUrl && config.headerLeftUrl.trim() !== '') { 377 try { 378 new URL(config.headerLeftUrl); 379 headerConfig.leftAction = { 380 icon: config.headerLeftIcon, 381 onClick: () => { 382 window.location.href = config.headerLeftUrl; 383 } 384 }; 385 console.log('â Header left button configured:', config.headerLeftIcon); 386 } catch (e) { 387 console.warn('â ï¸ Invalid left button URL, skipping'); 388 } 389 } 390 391 // Right action button 392 if (config.headerRightIcon && config.headerRightUrl && config.headerRightUrl.trim() !== '') { 393 try { 394 new URL(config.headerRightUrl); 395 headerConfig.rightAction = { 396 icon: config.headerRightIcon, 397 onClick: () => { 398 window.location.href = config.headerRightUrl; 399 } 400 }; 401 console.log('â Header right button configured:', config.headerRightIcon); 402 } catch (e) { 403 console.warn('â ï¸ Invalid right button URL, skipping'); 404 } 405 } 406 407 options.header = headerConfig; 408 console.log('â Header enabled'); 409 } else { 410 options.header = { enabled: false }; 411 console.log('â Header disabled'); 412 } 413 414 // â HISTORY 415 options.history = { 416 enabled: toBool(config.historyEnabled) 417 }; 418 console.log('â History:', toBool(config.historyEnabled) ? 'enabled' : 'disabled'); 419 420 // â LOCALE 421 if (config.locale && config.locale.trim() !== '') { 422 options.locale = config.locale; 423 console.log('â Locale set to:', config.locale); 424 } 425 426 // Initialize ChatKit 427 console.log('ð Initializing ChatKit with final config:', options); 428 chatkitElement.setOptions(options); 429 430 431 432 if (typeof gtag !== 'undefined') { 433 gtag('event', 'chatkit_initialized', { 434 event_category: 'engagement', 435 event_label: 'ChatKit Ready' 436 }); 437 } 438 439 } catch (error) { 440 console.error('â ChatKit Initialization Error:', error); 441 442 if (retryCount < MAX_RETRIES) { 443 retryCount++; 444 console.log(`Retrying after error (${retryCount}/${MAX_RETRIES})...`); 445 setTimeout(initChatKit, 2000); 446 } else { 447 const errorMsg = config.i18n?.loadFailed || 'â ï¸ Chat initialization failed. Please refresh the page.'; 448 showUserError(errorMsg); 449 } 450 } 451 } 452 453 if (document.readyState === 'loading') { 454 document.addEventListener('DOMContentLoaded', initChatKit); 455 } else { 456 setTimeout(initChatKit, 0); 457 } 458 459 window.addEventListener('beforeunload', () => { 460 document.body.style.overflow = ''; 461 }); 462})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.