PageSourceSearch

https://fgfactory.com.au/wp-content/plugins/OpenAI-ChatKit-for-Wor…/assets/chatkit-embed.js?ver=1.0.3

js fgfactory.com.au collected 2026-10-05 00:45:31 UTC 15,420 bytes, 462 lines download raw bytes

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.