PageSourceSearch

https://thodupuzha.com/wp-content/plugins/ai-chat-search/assets/js/ai-floating-chat-widget.js?ver=2.4.2

js thodupuzha.com collected 2026-09-25 10:56:24 UTC 49,606 bytes, 1,363 lines download raw bytes

1(function () {
2    'use strict';
3
4    function getStorageNamespace() {
5        var floatingConfig = (typeof listeoAiFloatingChatConfig !== 'undefined')
6            ? listeoAiFloatingChatConfig
7            : {};
8        var chatConfig = (typeof listeoAiChatConfig !== 'undefined')
9            ? listeoAiChatConfig
10            : {};
11        var namespace = chatConfig.storageNamespace || floatingConfig.storageNamespace || '';
12
13        return namespace ? String(namespace) : '';
14    }
15
16    function getScopedStorageKey(key) {
17        var namespace = getStorageNamespace();
18        return namespace ? key + '_' + namespace : key;
19    }
20
21    var STORAGE_KEY_BUBBLE_DISMISSED = getScopedStorageKey(
22        'listeo_floating_chat_bubble_dismissed'
23    );
24    var STORAGE_KEY_CHAT_OPENED = getScopedStorageKey('listeo_floating_chat_opened');
25    var STORAGE_KEY_USER_INTERACTED = getScopedStorageKey(
26        'listeo_floating_chat_interacted'
27    );
28    var STORAGE_KEY_PROACTIVE_COOLDOWN = getScopedStorageKey(
29        'listeo_floating_proactive_cooldown'
30    );
31    var FADE_DURATION_MS = 300;
32    var MINI_CHAT_ANIMATION_MS = 220;
33
34    function debugLog() {
35        if (typeof listeoAiChatConfig !== 'undefined' && listeoAiChatConfig.debugMode) {
36            var args = Array.prototype.slice.call(arguments);
37            args.unshift('[AI Chat Widget]');
38            console.log.apply(console, args);
39        }
40    }
41
42    function dispatchReady(chatId) {
43        if (typeof window.jQuery !== 'undefined') {
44            try {
45                window.jQuery(document).trigger('listeo-floating-chat-ready', { chatId: chatId });
46            } catch (e) {}
47        }
48        try {
49            document.dispatchEvent(new CustomEvent('listeo-floating-chat-ready', {
50                detail: { chatId: chatId }
51            }));
52        } catch (e) {}
53    }
54
55    function dispatchWidgetState(popup, eventName, chatId) {
56        if (!popup) return;
57        try {
58            popup.dispatchEvent(new CustomEvent(eventName, {
59                bubbles: true,
60                detail: { chatId: chatId }
61            }));
62        } catch (e) {}
63    }
64
65    function ListeoFloatingChatWidget() {
66        this.button = document.getElementById('listeo-floating-chat-button');
67        this.popup = document.getElementById('listeo-floating-chat-popup');
68        this.bubbleStack = document.getElementById('listeo-floating-bubble-stack');
69        this.welcomeBubble = document.getElementById('listeo-floating-welcome-bubble');
70        this.iconOpen = document.querySelector('.listeo-floating-icon-open');
71        this.iconClose = document.querySelector('.listeo-floating-icon-close');
72        this.isOpen = false;
73        this.chatInitialized = false;
74        this.scriptsLoaded = false;
75        this.closeTimeoutId = null;
76        this.proactiveTimeoutIds = [];
77        this.proactiveScrollActions = [];
78        this.proactiveScrollHandler = null;
79        this.proactiveActionQueue = [];
80        this.proactiveQueueSequence = 0;
81        this.proactiveQueueProcessTimeoutId = null;
82        this.activeProactiveActionId = '';
83        this.activeProactiveActionType = '';
84        this.pendingProactiveMessages = [];
85        this.proactiveMessageTimeoutId = null;
86        this.proactiveMessageAttempts = 0;
87        this.miniChat = null;
88        this.miniChatMessage = null;
89        this.miniChatQuickActions = null;
90        this.miniChatInput = null;
91        this.miniChatCloseTimeoutId = null;
92        this.pendingMagicQuestion = null;
93        this.magicQuestionTimeoutId = null;
94        this.magicQuestionAttempts = 0;
95
96        var cfg = (typeof listeoAiFloatingChatConfig !== 'undefined') ? listeoAiFloatingChatConfig : {};
97        var chatCfg = (typeof listeoAiChatConfig !== 'undefined') ? listeoAiChatConfig : {};
98        this.lazyScripts = (cfg && cfg.lazyScripts) ? cfg.lazyScripts : [];
99        this.scriptVersion = (cfg && cfg.scriptVersion) ? cfg.scriptVersion : '';
100        this.assistantName = String(chatCfg.chatName || '').trim();
101        this.assistantAvatarUrl = String(chatCfg.chatAvatarUrl || '').trim();
102        this.proactiveActions = (cfg && Array.isArray(cfg.proactiveActions))
103            ? cfg.proactiveActions
104            : ((cfg && cfg.proactiveAction) ? [cfg.proactiveAction] : []);
105        this.proactiveStrings = (cfg && cfg.proactiveStrings) ? cfg.proactiveStrings : {};
106        var proactiveSettings = (cfg && cfg.proactiveSettings)
107            ? cfg.proactiveSettings
108            : {};
109        var configuredCooldown = proactiveSettings.interaction_cooldown ||
110            cfg.proactiveCooldown;
111        var configuredDisplayMode = proactiveSettings.display_mode ||
112            cfg.proactiveDisplayMode;
113        this.proactiveCooldown = [
114            'none',
115            'session',
116            '30_minutes',
117            '1_hour',
118            '24_hours',
119            '7_days'
120        ].indexOf(String(configuredCooldown || '')) !== -1
121            ? String(configuredCooldown)
122            : 'session';
123        this.proactiveDisplayMode = String(configuredDisplayMode || '') === 'highest_priority'
124            ? 'highest_priority'
125            : 'all';
126        this.hasUserInteracted = this.isProactiveCooldownActive();
127
128        this.init();
129    }
130
131    ListeoFloatingChatWidget.prototype.init = function () {
132        this.checkWelcomeBubbleStatus();
133        this.bindEvents();
134        this.bindProactiveStorageSync();
135        this.restoreChatState();
136        this.scheduleProactiveActions();
137        debugLog('Widget initialized', this.lazyScripts.length > 0 ? '(lazy load enabled)' : '');
138    };
139
140    ListeoFloatingChatWidget.prototype.checkWelcomeBubbleStatus = function () {
141        if (!this.welcomeBubble) return;
142        var dismissed = localStorage.getItem(STORAGE_KEY_BUBBLE_DISMISSED);
143        if (dismissed === 'true') {
144            this.welcomeBubble.classList.add('hidden');
145        } else {
146            this.welcomeBubble.classList.remove('hidden');
147        }
148    };
149
150    ListeoFloatingChatWidget.prototype.restoreChatState = function () {
151        var cfg = (typeof listeoAiFloatingChatConfig !== 'undefined') ? listeoAiFloatingChatConfig : {};
152        if (!cfg.keepChatOpened) return;
153
154        var wasOpen = localStorage.getItem(STORAGE_KEY_CHAT_OPENED);
155        if (wasOpen === 'true') {
156            if (this.popup) {
157                this.popup.classList.add('listeo-no-animation');
158            }
159            this.openChat({ restore: true });
160            debugLog('Restored chat open state from localStorage');
161        }
162    };
163
164    ListeoFloatingChatWidget.prototype.bindEvents = function () {
165        var self = this;
166
167        if (this.button) {
168            this.button.addEventListener('click', function (e) {
169                e.preventDefault();
170                self.toggleChat();
171            });
172        }
173
174        if (this.bubbleStack) {
175            this.bubbleStack.addEventListener('click', function (e) {
176                if (!e.target || typeof e.target.closest !== 'function') return;
177
178                var bubble = e.target.closest('.listeo-floating-welcome-bubble');
179                if (!bubble || !self.bubbleStack.contains(bubble)) return;
180
181                e.stopPropagation();
182                var openProactiveBubble = bubble.classList.contains(
183                    'listeo-floating-proactive-bubble'
184                );
185                self.dismissWelcomeBubble();
186                if (openProactiveBubble) {
187                    self.openChat();
188                }
189            });
190        }
191
192        document.addEventListener('click', function (e) {
193            if (!e.target || typeof e.target.closest !== 'function') return;
194
195            var magicLink = e.target.closest('[data-chat-magic-link]');
196            if (!magicLink) return;
197
198            e.preventDefault();
199            self.activateMagicLink(magicLink);
200        });
201
202        document.addEventListener('listeo-floating-chat-ready', function () {
203            self.sendPendingMagicQuestion();
204            self.showPendingProactiveMessages();
205        });
206
207        document.addEventListener('listeo-ai-chat-user-message-sent', function (e) {
208            if (self.popup && e.target && self.popup.contains(e.target)) {
209                self.markUserInteracted();
210            }
211        });
212
213        document.addEventListener('keydown', function (e) {
214            if (e.key === 'Escape' && self.isOpen) {
215                self.closeChat();
216            } else if (
217                e.key === 'Escape' &&
218                self.miniChat &&
219                self.miniChat.classList.contains('is-visible')
220            ) {
221                self.dismissActiveMiniChat();
222            }
223        });
224    };
225
226    ListeoFloatingChatWidget.prototype.toggleChat = function () {
227        if (this.isOpen) {
228            this.closeChat();
229        } else {
230            if (this.popup) {
231                this.popup.classList.remove('listeo-no-animation');
232            }
233            this.openChat();
234        }
235    };
236
237    ListeoFloatingChatWidget.prototype.activateMagicLink = function (magicLink) {
238        var question = (magicLink.getAttribute('data-chat-magic-link') || '').trim();
239
240        this.pendingMagicQuestion = question ? question.substring(0, 1000) : null;
241        this.magicQuestionAttempts = 0;
242
243        if (this.magicQuestionTimeoutId) {
244            clearTimeout(this.magicQuestionTimeoutId);
245            this.magicQuestionTimeoutId = null;
246        }
247
248        if (!this.isOpen) {
249            this.openChat();
250        }
251
252        if (question) {
253            this.sendPendingMagicQuestion();
254        }
255    };
256
257    ListeoFloatingChatWidget.prototype.sendPendingMagicQuestion = function () {
258        var self = this;
259        if (!this.pendingMagicQuestion) return;
260
261        var chatWrapper = document.getElementById('listeo-floating-chat-instance');
262        var chatInstance = null;
263
264        if (chatWrapper && typeof window.jQuery !== 'undefined') {
265            chatInstance = window.jQuery(chatWrapper).data('listeo-ai-chat-instance');
266        }
267
268        if (chatInstance && typeof chatInstance.sendMagicMessage === 'function') {
269            var question = this.pendingMagicQuestion;
270            this.pendingMagicQuestion = null;
271            this.magicQuestionAttempts = 0;
272
273            if (this.magicQuestionTimeoutId) {
274                clearTimeout(this.magicQuestionTimeoutId);
275                this.magicQuestionTimeoutId = null;
276            }
277
278            chatInstance.sendMagicMessage(question);
279            return;
280        }
281
282        if (!this.magicQuestionTimeoutId && this.magicQuestionAttempts < 100) {
283            this.magicQuestionAttempts++;
284            this.magicQuestionTimeoutId = setTimeout(function () {
285                self.magicQuestionTimeoutId = null;
286                self.sendPendingMagicQuestion();
287            }, 50);
288        }
289    };
290
291    ListeoFloatingChatWidget.prototype.openChat = function (options) {
292        var self = this;
293        options = options || {};
294
295        this.hideMiniChat(true);
296        this.clearPendingProactiveActions();
297        this.activeProactiveActionId = '';
298        this.activeProactiveActionType = '';
299
300        if (options.proactive && options.message) {
301            this.queueProactiveMessage(options.actionId, options.message);
302        }
303        this.dismissWelcomeBubble();
304
305        if (typeof ListeoSilkWave !== 'undefined') {
306            ListeoSilkWave.start();
307        }
308
309        if (this.closeTimeoutId) {
310            clearTimeout(this.closeTimeoutId);
311            this.closeTimeoutId = null;
312        }
313
314        if (this.popup) {
315            this.popup.style.opacity = '';
316            this.popup.style.transition = '';
317            this.popup.style.display = 'block';
318            setTimeout(function () { self.scrollToBottom(); }, FADE_DURATION_MS);
319        }
320
321        if (this.iconOpen) this.iconOpen.style.display = 'none';
322        if (this.iconClose) this.iconClose.style.display = '';
323
324        this.isOpen = true;
325        dispatchWidgetState(this.popup, 'listeo-floating-chat-opened', 'listeo-floating-chat-instance');
326
327        if (!this.chatInitialized) {
328            this.chatInitialized = true;
329            if (this.lazyScripts.length > 0 && !this.scriptsLoaded) {
330                this.lazyLoadAndInit();
331            } else {
332                this.initializeChat();
333            }
334        }
335
336        if (options.proactive && options.message) {
337            this.showPendingProactiveMessages();
338        }
339
340        try {
341            localStorage.setItem(STORAGE_KEY_CHAT_OPENED, 'true');
342        } catch (e) {}
343
344        debugLog('Chat opened');
345    };
346
347    ListeoFloatingChatWidget.prototype.scrollToBottom = function () {
348        var messagesContainer = document.getElementById('listeo-floating-chat-instance-messages');
349        if (messagesContainer && messagesContainer.scrollHeight) {
350            messagesContainer.scrollTop = messagesContainer.scrollHeight;
351        }
352    };
353
354    ListeoFloatingChatWidget.prototype.closeChat = function () {
355        var self = this;
356        this.isOpen = false;
357        dispatchWidgetState(this.popup, 'listeo-floating-chat-closed', 'listeo-floating-chat-instance');
358
359        if (this.popup) {
360            this.popup.style.transition = 'opacity ' + FADE_DURATION_MS + 'ms';
361            this.popup.style.opacity = '0';
362
363            this.closeTimeoutId = setTimeout(function () {
364                // reopen guard
365                if (!self.isOpen && self.popup) {
366                    self.popup.style.display = 'none';
367                    self.popup.style.opacity = '';
368                    self.popup.style.transition = '';
369                }
370                self.closeTimeoutId = null;
371
372                if (!self.isOpen && !self.hasUserInteracted) {
373                    if (self.bubbleStack) {
374                        var proactiveBubbles = self.bubbleStack.querySelectorAll(
375                            '.listeo-floating-proactive-bubble'
376                        );
377                        Array.prototype.forEach.call(proactiveBubbles, function (bubble) {
378                            bubble.remove();
379                        });
380                    }
381                    self.scheduleProactiveActions();
382                }
383            }, FADE_DURATION_MS);
384        }
385
386        if (typeof ListeoSilkWave !== 'undefined') {
387            ListeoSilkWave.stop();
388        }
389
390        if (this.iconClose) this.iconClose.style.display = 'none';
391        if (this.iconOpen) this.iconOpen.style.display = '';
392
393        try {
394            localStorage.removeItem(STORAGE_KEY_CHAT_OPENED);
395        } catch (e) {}
396
397        debugLog('Chat closed');
398    };
399
400    ListeoFloatingChatWidget.prototype.dismissWelcomeBubble = function () {
401        var bubbles = this.bubbleStack
402            ? this.bubbleStack.querySelectorAll('.listeo-floating-welcome-bubble')
403            : (this.welcomeBubble ? [this.welcomeBubble] : []);
404
405        Array.prototype.forEach.call(bubbles, function (bubble) {
406            if (bubble.classList.contains('hidden')) return;
407
408            bubble.style.transition = 'opacity 200ms';
409            bubble.style.opacity = '0';
410
411            if (bubble.listeoDismissTimeoutId) {
412                clearTimeout(bubble.listeoDismissTimeoutId);
413            }
414            bubble.listeoDismissTimeoutId = setTimeout(function () {
415                bubble.classList.add('hidden');
416                bubble.style.opacity = '';
417                bubble.style.transition = '';
418                bubble.listeoDismissTimeoutId = null;
419            }, 200);
420        });
421
422        try {
423            localStorage.setItem(STORAGE_KEY_BUBBLE_DISMISSED, 'true');
424        } catch (e) {}
425    };
426
427    ListeoFloating
427ChatWidget.prototype.getProactiveCooldownDuration = function () {
428        var durations = {
429            '30_minutes': 30 * 60 * 1000,
430            '1_hour': 60 * 60 * 1000,
431            '24_hours': 24 * 60 * 60 * 1000,
432            '7_days': 7 * 24 * 60 * 60 * 1000
433        };
434
435        return durations[this.proactiveCooldown] || 0;
436    };
437
438    ListeoFloatingChatWidget.prototype.getProactiveActionCooldownKey = function (actionId) {
439        return getScopedStorageKey(
440            'listeo_floating_proactive_action_cooldown_' + String(actionId || '')
441        );
442    };
443
444    ListeoFloatingChatWidget.prototype.isProactiveCooldownKeyActive = function (key) {
445        if (this.proactiveCooldown === 'none') return false;
446
447        if (this.proactiveCooldown === 'session') {
448            try {
449                return sessionStorage.getItem(key) === 'true';
450            } catch (e) {
451                return false;
452            }
453        }
454
455        try {
456            var expiresAt = parseInt(localStorage.getItem(key), 10);
457            if (Number.isFinite(expiresAt) && expiresAt > Date.now()) {
458                return true;
459            }
460            localStorage.removeItem(key);
461        } catch (e) {}
462
463        return false;
464    };
465
466    ListeoFloatingChatWidget.prototype.isProactiveCooldownActive = function () {
467        var key = this.proactiveCooldown === 'session'
468            ? STORAGE_KEY_USER_INTERACTED
469            : STORAGE_KEY_PROACTIVE_COOLDOWN;
470
471        return this.isProactiveCooldownKeyActive(key);
472    };
473
474    ListeoFloatingChatWidget.prototype.isProactiveActionCooldownActive = function (actionId) {
475        return this.isProactiveCooldownKeyActive(
476            this.getProactiveActionCooldownKey(actionId)
477        );
478    };
479
480    ListeoFloatingChatWidget.prototype.setProactiveCooldownKey = function (key) {
481        if (this.proactiveCooldown === 'none') return;
482
483        if (this.proactiveCooldown === 'session') {
484            try {
485                sessionStorage.setItem(key, 'true');
486            } catch (e) {}
487            return;
488        }
489
490        var duration = this.getProactiveCooldownDuration();
491        if (!duration) return;
492
493        try {
494            localStorage.setItem(key, String(Date.now() + duration));
495        } catch (e) {}
496    };
497
498    ListeoFloatingChatWidget.prototype.clearPendingProactiveActions = function () {
499        this.proactiveTimeoutIds.forEach(function (timeoutId) {
500            clearTimeout(timeoutId);
501        });
502        this.proactiveTimeoutIds = [];
503        this.proactiveScrollActions = [];
504        this.proactiveActionQueue = [];
505        if (this.proactiveQueueProcessTimeoutId) {
506            clearTimeout(this.proactiveQueueProcessTimeoutId);
507            this.proactiveQueueProcessTimeoutId = null;
508        }
509        this.removeProactiveScrollListener();
510    };
511
512    ListeoFloatingChatWidget.prototype.markUserInteracted = function () {
513        this.hasUserInteracted = true;
514        this.clearPendingProactiveActions();
515        this.dismissWelcomeBubble();
516        this.setProactiveCooldownKey(
517            this.proactiveCooldown === 'session'
518                ? STORAGE_KEY_USER_INTERACTED
519                : STORAGE_KEY_PROACTIVE_COOLDOWN
520        );
521    };
522
523    ListeoFloatingChatWidget.prototype.dismissActiveMiniChat = function () {
524        var actionId = this.activeProactiveActionId;
525
526        if (actionId) {
527            this.setProactiveCooldownKey(
528                this.getProactiveActionCooldownKey(actionId)
529            );
530        }
531
532        this.activeProactiveActionId = '';
533        this.activeProactiveActionType = '';
534        this.hideMiniChat(true, true);
535        this.processProactiveActionQueue();
536    };
537
538    ListeoFloatingChatWidget.prototype.bindProactiveStorageSync = function () {
539        var self = this;
540        if (!this.getProactiveCooldownDuration()) return;
541
542        window.addEventListener('storage', function (event) {
543            if (!event.key) return;
544
545            if (
546                event.key === STORAGE_KEY_PROACTIVE_COOLDOWN &&
547                self.isProactiveCooldownActive()
548            ) {
549                self.hasUserInteracted = true;
550                self.clearPendingProactiveActions();
551                if (self.activeProactiveActionType === 'mini_chat') {
552                    self.activeProactiveActionId = '';
553                    self.activeProactiveActionType = '';
554                    self.hideMiniChat(true);
555                }
556                self.dismissWelcomeBubble();
557                return;
558            }
559
560            var activeActionId = self.activeProactiveActionId;
561            if (
562                activeActionId &&
563                event.key === self.getProactiveActionCooldownKey(activeActionId) &&
564                self.isProactiveActionCooldownActive(activeActionId)
565            ) {
566                self.activeProactiveActionId = '';
567                self.activeProactiveActionType = '';
568                self.hideMiniChat(true);
569                self.processProactiveActionQueue();
570            }
571
572            if (event.newValue) {
573                self.proactiveActionQueue = self.proactiveActionQueue.filter(function (item) {
574                    return event.key !== self.getProactiveActionCooldownKey(item.actionId);
575                });
576            }
577        });
578    };
579
580    ListeoFloatingChatWidget.prototype.scheduleProactiveActions = function () {
581        var self = this;
582
583        if (this.hasUserInteracted || this.isOpen) return;
584
585        var actions = this.proactiveActions.filter(function (action) {
586            return action &&
587                !self.isProactiveActionCooldownActive(String(action.id || '').trim());
588        });
589        if (this.proactiveDisplayMode === 'highest_priority') {
590            actions.sort(function (a, b) {
591                return self.getProactiveActionPriority(a) -
592                    self.getProactiveActionPriority(b);
593            });
594            actions = actions.slice(0, 1);
595        }
596
597        actions.forEach(function (action) {
598            self.scheduleProactiveAction(action);
599        });
600    };
601
602    ListeoFloatingChatWidget.prototype.scheduleProactiveAction = function (action) {
603        var self = this;
604
605        if (!action || this.hasUserInteracted) return;
606
607        var actionId = String(action.id || '').trim();
608        var actionType = String(action.action || '');
609        var trigger = String(action.trigger || '') === 'scroll_depth'
610            ? 'scroll_depth'
611            : 'time';
612
613        if (
614            !actionId ||
615            (
616                actionType !== 'show_bubble' &&
617                actionType !== 'open_chat' &&
618                actionType !== 'mini_chat'
619            )
620        ) {
621            return;
622        }
623        if (this.isProactiveActionCooldownActive(actionId)) return;
624
625        var firedKey = getScopedStorageKey(
626            'listeo_floating_proactive_action_' + actionId
627        );
628
629        try {
630            var storedStatus = sessionStorage.getItem(firedKey);
631            if (storedStatus === 'shown' || storedStatus === 'true') {
632                sessionStorage.removeItem(firedKey);
633            }
634        } catch (e) {}
635
636        if (trigger === 'scroll_depth') {
637            var scrollDepth = parseInt(action.scroll_depth, 10);
638            if (!Number.isFinite(scrollDepth) || scrollDepth < 1 || scrollDepth > 100) {
639                return;
640            }
641
642            this.proactiveScrollActions.push({
643                action: action,
644                actionId: actionId,
645                actionType: actionType,
646                scrollDepth: scrollDepth
647            });
648            this.addProactiveScrollListener();
649            this.checkProactiveScrollActions();
650            return;
651        }
652
653        var delay = parseInt(action.delay, 10);
654        if (!Number.isFinite(delay) || delay < 0) return;
655
656        var timeoutId = setTimeout(function () {
657            self.proactiveTimeoutIds = self.proactiveTimeoutIds.filter(function (id) {
658                return id !== timeoutId;
659            });
660            if (self.hasUserInteracted) return;
661
662            self.handleReadyProactiveAction(action, actionId, actionType);
663        }, delay * 1000);
664
665        this.proactiveTimeoutIds.push(timeoutId);
666    };
667
668    ListeoFloatingChatWidget.prototype.getProactiveActionPriority = function (action) {
669        var priority = parseInt(action && action.priority, 10);
670        return Number.isFinite(priority) ? priority : 10;
671    };
672
673    ListeoFloatingChatWidget.prototype.handleReadyProactiveAction = function (
674        action,
675        actionId,
676        actionType
677    ) {
678        if (
679            this.hasUserInteracted ||
680            this.isProactiveActionCooldownActive(actionId)
681        ) {
682            return;
683        }
684
685        if (actionType === 'show_bubble') {
686            this.executeProactiveAction(action, actionId, actionType);
687            return;
688        }
689
690        if (this.proactiveActionQueue.some(function (item) {
691            return item.actionId === actionId;
692        })) {
693            return;
694        }
695
696        this.proactiveActionQueue.push({
697            action: action,
698            actionId: actionId,
699            actionType: actionType,
700            priority: this.getProactiveActionPriority(action),
701            sequence: this.proactiveQueueSequence++
702        });
703        this.proactiveActionQueue.sort(function (a, b) {
704            return a.priority === b.priority
705                ? a.sequence - b.sequence
706                : a.priority - b.priority;
707        });
708        this.scheduleProactiveActionQueue();
709    };
710
711    ListeoFloatingChatWidget.prototype.scheduleProactiveActionQueue = function () {
712        var self = this;
713        if (this.proactiveQueueProcessTimeoutId) return;
714
715        this.proactiveQueueProcessTimeoutId = setTimeout(function () {
716            self.proactiveQueueProcessTimeoutId = null;
717            self.processProactiveActionQueue();
718        }, 0);
719    };
720
721    ListeoFloatingChatWidget.prototype.processProactiveActionQueue = function () {
722        if (this.hasUserInteracted || this.isOpen) {
723            this.proactiveActionQueue = [];
724            return;
725        }
726        if (
727            this.activeProactiveActionType === 'mini_chat' &&
728            this.miniChat &&
729            this.miniChat.classList.contains('is-visible')
730        ) {
731            return;
732        }
733
734        var item = null;
735        while (this.proactiveActionQueue.length && !item) {
736            var candidate = this.proactiveActionQueue.shift();
737            if (!this.isProactiveActionCooldownActive(candidate.actionId)) {
738                item = candidate;
739            }
740        }
741        if (!item) return;
742
743        this.executeProactiveAction(item.action, item.actionId, item.actionType);
744    };
745
746    ListeoFloatingChatWidget.prototype.executeProactiveAction = function (
747        action,
748        actionId,
749        actionType
750    ) {
751        if (
752            this.hasUserInteracted ||
753            this.isProactiveActionCooldownActive(actionId)
754        ) {
755            return;
756        }
757
758        if (actionType === 'open_chat') {
759            this.openChat({
760                proactive: true,
761                actionId: actionId,
762                message: String(action.message || '')
763            });
764            return;
765        }
766
767        if (actionType === 'mini_chat') {
768            if (this.showMiniChat(
769                actionId,
770                String(action.message || ''),
771                action.quick_actions
772            )) {
773                this.activeProactiveActionId = actionId;
774                this.activeProactiveActionType = 'mini_chat';
775            } else {
776                this.processProactiveActionQueue();
777            }
778            return;
779        }
780
781        this.showProactiveBubble(
782            actionId,
783            String(action.message || ''),
784            this.getProactiveActionPriority(action)
785        );
786    };
787
788    ListeoFloatingChatWidget.prototype.addProactiveScrollListener = function () {
789        var self = this;
790        if (this.proactiveScrollHandler) return;
791
792        this.proactiveScrollHandler = function () {
793            self.checkProactiveScrollActions();
794        };
795        window.addEventListener('scroll', this.proactiveScrollHandler, { passive: true });
796    };
797
798    ListeoFloatingChatWidget.prototype.removeProactiveScrollListener = function () {
799        if (!this.proactiveScrollHandler) return;
800
801        window.removeEventListener('scroll', this.proactiveScrollHandler);
802        this.proactiveScrollHandler = null;
803    };
804
805    ListeoFloatingChatWidget.prototype.getPageScrollPercentage = function () {
806        var documentElement = document.documentElement;
807        var body = document.body;
808        var scrollTop = window.pageYOffset ||
809            (documentElement ? documentElement.scrollTop : 0) ||
810            (body ? body.scrollTop : 0);
811        var viewportHeight = window.innerHeight ||
812            (documentElement ? documentElement.clientHeight : 0);
813        var scrollHeight = Math.max(
814            body ? body.scrollHeight : 0,
815            body ? body.offsetHeight : 0,
816            documentElement ? documentElement.clientHeight : 0,
817            documentElement ? documentElement.scrollHeight : 0,
818            documentElement ? documentElement.offsetHeight : 0
819        );
820        var scrollableHeight = scrollHeight - viewportHeight;
821
822        if (scrollableHeight <= 0) return 0;
823
824        return Math.min(100, Math.max(0, (scrollTop / scrollableHeight) * 100));
825    };
826
827    ListeoFloatingChatWidget.prototype.checkProactiveScrollActions = function () {
828        var self = this;
829
830        if (this.hasUserInteracted) {
831            this.proactiveScrollActions = [];
832            this.removeProactiveScrollListener();
833            return;
834        }
835
836        var scrollPercentage = this.getPageScrollPercentage();
837        var readyActions = [];
838
839        this.proactiveScrollActions = this.proactiveScrollActions.filter(function (item) {
840            if (scrollPercentage >= item.scrollDepth) {
841                readyActions.push(item);
842                return false;
843            }
844            return true;
845        });
846
847        if (!this.proactiveScrollActions.length) {
848            this.removeProactiveScrollListener();
849        }
850
851        readyActions.forEach(function (item) {
852            self.handleReadyProactiveAction(item.action, item.actionId, item.actionType);
853        });
854    };
855
856    ListeoFloatingChatWidget.prototype.queueProactiveMessage = function (actionId, message) {
857        actionId = String(actionId || '').trim();
858        message = String(message || '').trim();
859
860        if (!actionId || !message) return;
861
862        this.pendingProactiveMessages.push({
863            id: actionId,
864            message: message
865        });
866        this.proactiveMessageAttempts = 0;
867    };
868
869    ListeoFloatingChatWidget.prototype.showPendingProactiveMessages = function () {
870        var self = this;
871        if (!this.pendingProactiveMessages.length) return;
872
873        var chatWrapper = document.getElementById('listeo-floating-chat-instance');
874        var messagesContainer = document.getElementById('listeo-floating-chat-instance-messages');
875        var chatInstance = null;
876
877        if (chatWrapper && typeof window.jQuery !== 'undefined') {
878            chatInstance = window.jQuery(chatWrapper).data('listeo-ai-chat-instance');
879        }
880
881        if (!messagesContainer || !chatInstance || typeof chatInstance.addMessage !== 'function') {
882            if (!this.proactiveMessageTimeoutId && this.proactiveMessageAttempts < 100) {
883                this.proactiveMessageAttempts++;
884                this.proactiveMessageTimeoutId = setTimeout(function () {
885                    self.proactiveMessageTimeoutId = null;
886                    self.showPendingProactiveMessages();
887                }, 50);
888            }
889            return;
890        }
891
892        var normalizeText = function (value) {
893            return String(value || '').replace(/\s+/g, ' ').trim();
894        };
895        var chatConfig = (typeof listeoAiChatConfig !== 'undefined') ? listeoAiChatConfig : {};
896        var welcomeMessage = chatConfig.strings ? chatConfig.strings.welcomeMessage : '';
897        var welcomeHolder = document.createElement('div');
898        welcomeHolder.innerHTML = welcomeMessage;
899        var welcomeText = normalizeText(welcomeHolder.textContent);
900        var messagesChanged = false;
901
902        this.pendingProactiveMessages.forEach(function (item) {
903            var messageId = 'listeo-proactive-message-' + item.id;
904            if (document.getElementById(messageId)) return;
905
906            var hasConversation = messagesContainer.querySelector(
907                '.listeo-ai-chat-message-user, .listeo-ai-chat-message-assistant'
908            );
909            var hasProactiveMessage = messagesContainer.querySelector(
910                '[data-proactive-action-id]'
911            );
912            var systemMessages = messagesContainer.querySelectorAll(
913                '.listeo-ai-chat-message-system:not([data-proactive-action-id])'
914            );
915            var defaultMessage = null;
916
917            Array.prototype.some.call(systemMessages, function (systemMessage) {
918                var systemContent = systemMessage.querySelector(
919                    '.listeo-ai-chat-message-content'
920                );
921                if (
922                    systemContent &&
923                    welcomeText &&
924                    normalizeText(systemContent.textContent) === welcomeText
925                ) {
926                    defaultMessage = systemMessage;
927                    return true;
928                }
929                return false;
930            });
931
932            if (!hasConversation && !hasProactiveMessage && defaultMessage) {
933                var content = defaultMessage.querySelector('.listeo-ai-chat-message-content');
934                if (content) {
935                    content.textContent = item.message;
936                    defaultMessage.id = messageId;
937                    defaultMessage.setAttribute('data-proactive-action-id', item.id);
938                    messagesChanged = true;
939                    return;
940                }
941            }
942
943            var imageWelcome = messagesContainer.querySelector('.chat-image-bg-welcome-text');
944            if (!hasConversation && !hasProactiveMessage && imageWelcome) {
945                imageWelcome.remove();
946                messagesChanged = true;
947            }
948
949            chatInstance.addMessage('system', item.message, messageId, true);
950            messagesChanged = true;
951            var addedMessage = document.getElementById(messageId);
952            if (addedMessage) {
953                addedMessage.setAttribute('data-proactive-action-id', item.id);
954            }
955        });
956
957        if (messagesChanged && typeof chatInstance.saveConversation === 'function') {
958            chatInstance.saveConversation();
959        }
960
961        this.pendingProactiveMessages = [];
962        this.proactiveMessageAttempts = 0;
963    };
964
965    ListeoFloatingChatWidget.prototype.showProactiveBubble = function (
966        actionId,
967        message,
968        priority
969    ) {
970        actionId = String(actionId || '').trim();
971        message = String(message || '').trim();
972        priority = Number.isFinite(priority) ? priority : 10;
973
974        if (!this.bubbleStack || !actionId || !message) return;
975
976        var bubbleId = 'listeo-floating-proactive-bubble-' + actionId;
977        if (document.getElementById(bubbleId)) return;
978
979        var bubble = document.createElement('div');
980        bubble.className = 'listeo-floating-welcome-bubble listeo-floating-proactive-bubble';
981        bubble.id = bubbleId;
982        bubble.setAttribute('data-proactive-action-id', actionId);
983        bubble.setAttribute('data-proactive-priority', String(priority));
984
985        var content = document.createElement('div');
986        content.className = 'listeo-floating-welcome-bubble-content';
987        content.textContent = message;
988
989        var arrow = document.createElement('div');
990        arrow.className = 'listeo-floating-welcome-bubble-arrow';
991
992        bubble.appendChild(content);
993        bubble.appendChild(arrow);
994
995        var inserted = false;
996        var existingBubbles = this.bubbleStack.querySelectorAll(
997            '.listeo-floating-proactive-bubble'
998        );
999        Array.prototype.some.call(existingBubbles, function (existingBubble) {
1000            var existingPriority = parseInt(
1001                existingBubble.getAttribute('data-proactive-priority'),
1002                10
1003            );
1004            if (!Number.isFinite(existingPriority)) existingPriority = 10;
1005
1006            if (existingPriority > priority) {
1007                existingBubble.parentNode.insertBefore(bubble, existingBubble);
1008                inserted = true;
1009                return true;
1010            }
1011            return false;
1012        });
1013
1014        if (!inserted) {
1015            this.bubbleStack.appendChild(bubble);
1016        }
1017    };
1018
1019    ListeoFloatingChatWidget.prototype.createMiniChat = function () {
1020        var self = this;
1021        var widget = document.getElementById('listeo-floating-chat-widget');
1022        if (!widget || this.miniChat) return;
1023
1024        var panel = document.createElement('div');
1025        panel.className = 'listeo-floating-mini-chat';
1026        panel.setAttribute('role', 'dialog');
1027        panel.setAttribute('aria-live', 'polite');
1028
1029        var identity = document.createElement('div');
1030        identity.className = 'listeo-floating-mini-chat-identity';
1031        identity.setAttribute('role', 'button');
1032        identity.setAttribute('tabindex', '0');
1033
1034        if (this.assistantAvatarUrl) {
1035            var avatar = document.createElement('img');
1036            avatar.className = 'listeo-floating-mini-chat-avatar';
1037            avatar.src = this.assistantAvatarUrl;
1038            avatar.alt = '';
1039            avatar.setAttribute('aria-hidden', 'true');
1040            identity.classList.add('has-avatar');
1041            identity.appendChild(avatar);
1042        }
1043
1044        var assistantName = document.createElement('span');
1045        assistantName.className = 'listeo-floating-mini-chat-assistant-name';
1046        assistantName.textContent = this.assistantName;
1047        identity.appendChild(assistantName);
1048
1049        var closeControl = document.createElement('div');
1050        closeControl.className = 'listeo-floating-mini-chat-close';
1051        closeControl.setAttribute('role', 'button');
1052        closeControl.setAttribute('tabindex', '0');
1053        closeControl.setAttribute(
1054            'aria-label',
1055            String(this.proactiveStrings.closeMiniChat || '')
1056        );
1057        closeControl.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12"></path><path d="M18 6 6 18"></path></svg>';
1058
1059        var message = document.createElement('div');
1060        message.className = 'listeo-floating-mini-chat-message';
1061
1062        var quickActions = document.createElement('div');
1063        quickActions.className = 'listeo-floating-mini-chat-quick-actions';
1064        quickActions.hidden = true;
1065
1066        var form = document.createElement('form');
1067        form.className = 'listeo-floating-mini-chat-form';
1068
1069        var input = document.createElement('input');
1070        input.type = 'text';
1071        input.className = 'listeo-floating-mini-chat-input';
1072        input.maxLength = 1000;
1073        input.placeholder = String(this.proactiveStrings.miniPlaceholder || '');
1074        input.setAttribute(
1075            'aria-label',
1076            String(this.proactiveStrings.miniPlaceholder || '')
1077        );
1078
1079        var sendButton = document.createElement('button');
1080        sendButton.type = 'submit';
1081        sendButton.className = 'listeo-floating-mini-chat-send';
1082        sendButton.setAttribute(
1083            'aria-label',
1084            String(this.proactiveStrings.sendMessage || '')
1085        );
1086        sendButton.innerHTML = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5"></path><path d="m5 12 7-7 7 7"></path></svg>';
1087
1088        form.appendChild(input);
1089        form.appendChild(sendButton);
1090        panel.appendChild(identity);
1091        panel.appendChild(closeControl);
1092        panel.appendChild(message);
1093        panel.appendChild(quickActions);
1094        panel.appendChild(form);
1095        widget.appendChild(panel);
1096
1097        closeControl.addEventListener('click', function () {
1098            self.dismissActiveMiniChat();
1099        });
1100        closeControl.addEventListener('keydown', function (event) {
1101            if (event.key !== 'Enter' && event.key !== ' ') return;
1102            event.preventDefault();
1103            self.dismissActiveMiniChat();
1104        });
1105
1106        identity.addEventListener('click', function () {
1107            self.openChat();
1108        });
1109        identity.addEventListener('keydown', function (event) {
1110            if (event.key !== 'Enter' && event.key !== ' ') return;
1111            event.preventDefault();
1112            self.openChat();
1113        });
1114
1115        quickActions.addEventListener('click', function (event) {
1116            if (!event.target || typeof event.target.closest !== 'function') return;
1117
1118            var button = event.target.closest('.listeo-floating-mini-chat-quick-action');
1119            if (!button || !quickActions.contains(button)) return;
1120
1121            self.sendMiniChatQuestion(button.getAttribute('data-message') || '');
1122        });
1123
1124        form.addEventListener('submit', function (event) {
1125            event.preventDefault();
1126            self.sendMiniChatQuestion(input.value);
1127        });
1128
1129        this.miniChat = panel;
1130        this.miniChatMessage = message;
1131        this.miniChatQuickActions = quickActions;
1132        this.miniChatInput = input;
1133    };
1134
1135    ListeoFloatingChatWidget.prototype.sendMiniChatQuestion = function (question) {
1136        question = String(question || '').trim().substring(0, 1000);
1137        if (!question) return;
1138
1139        this.pendingMagicQuestion = question;
1140        this.magicQuestionAttempts = 0;
1141        if (this.magicQuestionTimeoutId) {
1142            clearTimeout(this.magicQuestionTimeoutId);
1143            this.magicQuestionTimeoutId = null;
1144        }
1145
1146        this.hideMiniChat(true, true);
1147        this.openChat();
1148        this.sendPendingMagicQuestion();
1149    };
1150
1151    ListeoFloatingChatWidget.prototype.renderMiniChatQuickActions = function (quickActions) {
1152        var container = this.miniChatQuickActions;
1153        if (!container) return;
1154
1155        container.textContent = '';
1156
1157        if (!Array.isArray(quickActions)) {
1158            container.hidden = true;
1159            return;
1160        }
1161
1162        quickActions.forEach(function (quickAction) {
1163            if (!quickAction || typeof quickAction !== 'object') return;
1164
1165            var label = String(quickAction.label || '').trim();
1166            var message = String(quickAction.message || '').trim().substring(0, 1000);
1167            if (!label || !message) return;
1168
1169            var button = document.createElement('button');
1170            button.type = 'button';
1171            button.className = 'listeo-floating-mini-chat-quick-action';
1172            button.textContent = label;
1173            button.setAttribute('data-message', message);
1174
1175            var color = String(quickAction.color || '').trim();
1176            if (/^#[0-9a-f]{3}$/i.test(color)) {
1177                color = '#' +
1178                    color.charAt(1) + color.charAt(1) +
1179                    color.charAt(2) + color.charAt(2) +
1180                    color.charAt(3) + color.charAt(3);
1181            }
1182            if (/^#[0-9a-f]{6}$/i.test(color)) {
1183                var red = parseInt(color.substring(1, 3), 16);
1184                var green = parseInt(color.substring(3, 5), 16);
1185                var blue = parseInt(color.substring(5, 7), 16);
1186
1187                button.classList.add(
1188                    'listeo-floating-mini-chat-quick-action-custom'
1189                );
1190                button.style.setProperty('--quick-btn-color', color);
1191                button.style.setProperty(
1192                    '--quick-btn-color-light',
1193                    'rgba(' + red + ', ' + green + ', ' + blue + ', 0.1)'
1194                );
1195            }
1196
1197            container.appendChild(button);
1198        });
1199
1200        container.hidden = !container.children.length;
1201    };
1202
1203    ListeoFloatingChatWidget.prototype.showMiniChat = function (actionId, message, quickActions) {
1204        actionId = String(actionId || '').trim();
1205        message = String(message || '').trim();
1206
1207        if (!actionId || !message || this.isOpen) return false;
1208
1209        this.createMiniChat();
1210        if (!this.miniChat) return false;
1211
1212        this.miniChat.setAttribute('data-proactive-action-id', actionId);
1213        this.miniChatMessage.innerHTML = message;
1214        this.miniChat.setAttribute(
1215            'aria-label',
1216            String(this.miniChatMessage.textContent || '').trim()
1217        );
1218        Array.prototype.forEach.call(
1219            this.miniChatMessage.querySelectorAll('a[target="_blank"]'),
1220            function (link) {
1221                var rel = String(link.getAttribute('rel') || '')
1222                    .split(/\s+/)
1223                    .filter(Boolean);
1224                if (rel.indexOf('noopener') === -1) rel.push('noopener');
1225                if (rel.indexOf('noreferrer') === -1) rel.push('noreferrer');
1226                link.setAttribute('rel', rel.join(' '));
1227            }
1228        );
1229        this.miniChatInput.value = '';
1230        this.renderMiniChatQuickActions(quickActions);
1231
1232        if (this.miniChatCloseTimeoutId) {
1233            clearTimeout(this.miniChatCloseTimeoutId);
1234            this.miniChatCloseTimeoutId = null;
1235        }
1236
1237        void this.miniChat.offsetWidth;
1238        this.miniChat.classList.add('is-visible');
1239
1240        if (this.button) {
1241            this.button.style.display = 'none';
1242        }
1243        if (this.bubbleStack) {
1244            this.bubbleStack.style.display = 'none';
1245        }
1246
1247        return true;
1248    };
1249
1250    ListeoFloatingChatWidget.prototype.hideMiniChat = function (restoreLauncher, animateLauncher) {
1251        var self = this;
1252        if (!this.miniChat || !this.miniChat.classList.contains('is-visible')) return;
1253
1254        this.miniChat.classList.remove('is-visible');
1255        if (!restoreLauncher) return;
1256
1257        if (this.miniChatCloseTimeoutId) {
1258            clearTimeout(this.miniChatCloseTimeoutId);
1259        }
1260
1261        this.miniChatCloseTimeoutId = setTimeout(function () {
1262            self.miniChatCloseTimeoutId = null;
1263            if (self.miniChat.classList.contains('is-visible')) return;
1264
1265            if (self.button) {
1266                self.button.style.display = '';
1267                if (animateLauncher) {
1268                    self.button.classList.remove('listeo-mini-chat-launcher-return');
1269                    void self.button.offsetWidth;
1270                    self.button.classList.add('listeo-mini-chat-launcher-return');
1271                    setTimeout(function () {
1272                        if (self.button) {
1273                            self.button.classList.remove('listeo-mini-chat-launcher-return');
1274                        }
1275                    }, MINI_CHAT_ANIMATION_MS);
1276                }
1277            }
1278            if (self.bubbleStack) {
1279                self.bubbleStack.style.display = '';
1280            }
1281        }, MINI_CHAT_ANIMATION_MS);
1282    };
1283
1284    ListeoFloatingChatWidget.prototype.lazyLoadAndInit = function () {
1285        var self = this;
1286        var chatWrapper = document.getElementById('listeo-floating-chat-instance');
1287        var existingChatInstance = null;
1288
1289        if (chatWrapper && typeof window.jQuery !== 'undefined') {
1290            existingChatInstance = window.jQuery(chatWrapper).data('listeo-ai-chat-instance');
1291        }
1292
1293        // shortcode on same page may have already loaded core
1294        if (
1295            existingChatInstance ||
1296            document.querySelector(
1297                'script[src*="purio-ai-scripts.js"], script[src*="ai-chat-core"]'
1298            )
1299        ) {
1300            this.scriptsLoaded = true;
1301            this.initializeChat();
1302            return;
1303        }
1304
1305        if (chatWrapper) {
1306            chatWrapper.classList.add('listeo-ai-chat-lazy-state');
1307        }
1308
1309        var ver = this.scriptVersion;
1310        var urls = this.lazyScripts.map(function (url) {
1311            return url + (url.indexOf('?') === -1 ? '?' : '&') + 'ver=' + ver;
1312        });
1313
1314        this.loadScriptsSequential(urls, 0, function () {
1315            self.scriptsLoaded = true;
1316            // let chatbot-core's ready handler settle
1317            setTimeout(function () {
1318                if (chatWrapper) {
1319                    chatWrapper.classList.remove('listeo-ai-chat-lazy-state');
1320                }
1321                self.initializeChat();
1322            }, 50);
1323        });
1324    };
1325
1326    ListeoFloatingChatWidget.prototype.loadScriptsSequential = function (urls, index, callback) {
1327        var self = this;
1328        if (index >= urls.length) {
1329            callback();
1330            return;
1331        }
1332
1333        var script = document.createElement('script');
1334        script.src = urls[index];
1335        script.onload = function () {
1336            self.loadScriptsSequential(urls, index + 1, callback);
1337        };
1338        script.onerror = function () {
1339            console.error('[AI Chat] Failed to load:', urls[index]);
1340            self.loadScriptsSequential(urls, index + 1, callback);
1341        };
1342        document.body.appendChild(script);
1343    };
1344
1345    ListeoFloatingChatWidget.prototype.initializeChat = function () {
1346        setTimeout(function () {
1347            dispatchReady('listeo-floating-chat-instance');
1348        }, 100);
1349    };
1350
1351    function boot() {
1352        if (document.getElementById('listeo-floating-chat-widget')) {
1353            new ListeoFloatingChatWidget();
1354        }
1355    }
1356
1357    // works in head (lazy mode, defer) or footer
1358    if (document.readyState === 'loading') {
1359        document.addEventListener('DOMContentLoaded', boot);
1360    } else {
1361        boot();
1362    }
1363})();

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.