PageSourceSearch

https://www.museodipietrarubbia.it/wp-content/plugins/chatbot-ai-free-models/assets/js/chatbot.js?ver=2.0.7

js museodipietrarubbia.it collected 2026-10-07 08:23:02 UTC 28,307 bytes, 704 lines download raw bytes

1document.addEventListener('DOMContentLoaded', function() {
2    (function($) {
3
4        var chatbox = $('#chatbox');
5        var chatboxButton = $('#chatbox-button');
6        var chatboxButtonImg = chatboxButton.find('img');
7        var chatboxContent = $('#chatbox-content');
8        var chatboxInput = $('#chatbox-input');
9        var chatboxSend = $('#chatbox-send');
10        var chatboxStop = $('#chatbox-stop');
11        var chatboxNew = $('#chatbox-new');
12        var unreadBadge = $('#chatbox-unread-badge');
13        var proactiveBubble = $('#chatbot-proactive-bubble');
14
15        var conversationHistory = [];
16        var currentConversationId = null;
17        var userInfo = null;
18        var isChatboxOpen = false;
19        var isAnimating = false;
20        var isStreaming = false;
21        var currentAbortController = null;
22        var lastUserMessage = '';
23
24        const historyKey = chatbot_params.site_id + '_chatbotHistory';
25        const convIdKey = chatbot_params.site_id + '_chatbotConvId';
26        const unreadKey = chatbot_params.site_id + '_chatbotUnread';
27        const userInfoKey = chatbot_params.site_id + '_chatbotUserInfo';
28
29        var unreadCount = 0;
30
31        function loadUserInfo() {
32            try {
33                var stored = sessionStorage.getItem(userInfoKey);
34                if (stored) {
35                    var parsed = JSON.parse(stored);
36                    if (parsed && !parsed.sent) {
37                        userInfo = parsed;
38                    }
39                }
40            } catch (e) {}
41        }
42
43        function saveUserInfo() {
44            if (userInfo) {
45                try { sessionStorage.setItem(userInfoKey, JSON.stringify(userInfo)); } catch (e) {}
46            }
47        }
48
49        function loadUnreadCount() {
50            try { unreadCount = parseInt(sessionStorage.getItem(unreadKey), 10) || 0; } catch (e) {}
51        }
52
53        function saveUnreadCount() {
54            try { sessionStorage.setItem(unreadKey, String(unreadCount)); } catch (e) {}
55        }
56
57        function updateUnreadBadge() {
58            if (unreadCount > 0 && !isChatboxOpen) {
59                unreadBadge.text(unreadCount > 99 ? '99+' : unreadCount).show();
60            } else {
61                unreadBadge.hide();
62            }
63        }
64
65        function incrementUnread() {
66            if (!isChatboxOpen) {
67                unreadCount++;
68                saveUnreadCount();
69                updateUnreadBadge();
70            }
71        }
72
73        function clearUnread() {
74            unreadCount = 0;
75            saveUnreadCount();
76            updateUnreadBadge();
77        }
78
79        function saveHistoryToSessionStorage() {
80            try {
81                sessionStorage.setItem(historyKey, JSON.stringify(conversationHistory));
82            } catch (e) {}
83        }
84
85        function loadHistoryFromSessionStorage() {
86            try {
87                var storedHistory = sessionStorage.getItem(historyKey);
88                if (storedHistory) {
89                    conversationHistory = JSON.parse(storedHistory);
90                    rebuildChatFromHistory();
91                }
92            } catch (e) {
93                conversationHistory = [];
94            }
95        }
96
97        function rebuildChatFromHistory() {
98            chatboxContent.empty();
99            conversationHistory.forEach(function(message) {
100                if (message.role === 'user') {
101                    appendUserMessage(message.content);
102                } else if (message.role === 'assistant') {
103                    appendBotMessage(message.content);
104                }
105            });
106        }
107
108        function getConversationId() {
109            if (currentConversationId) return currentConversationId;
110            try {
111                currentConversationId = sessionStorage.getItem(convIdKey);
112            } catch (e) {}
113            return currentConversationId;
114        }
115
116        function autoResizeTextarea() {
117            chatboxInput.css('height', 'auto');
118            var scrollHeight = chatboxInput[0].scrollHeight;
119            chatboxInput.css('height', Math.min(scrollHeight, 120) + 'px');
120        }
121
122        function sendMessage() {
123            if (isStreaming) return;
124
125            var message = chatboxInput.val().trim();
126            if (message === '') return;
127
128            lastUserMessage = message;
129
130            appendUserMessage(message);
131            chatboxInput.val('');
132            autoResizeTextarea();
133
134            conversationHistory.push({ role: 'user', content: message });
135            saveHistoryToSessionStorage();
136
137            streamMessageToServer(message);
138        }
139
140        function showStreamingUI(streaming) {
141            if (streaming) {
142                chatboxStop.show();
143                chatboxSend.hide();
144            } else {
145                chatboxStop.hide();
146                chatboxSend.show();
147            }
148        }
149
150        function startNewConversation() {
151            if (isStreaming && currentAbortController) {
152                currentAbortController.abort();
153            }
154
155            conversationHistory = [];
156            currentConversationId = null;
157            lastUserMessage = '';
158
159            try {
160                sessionStorage.removeItem(historyKey);
161                sessionStorage.removeItem(convIdKey);
162            } catch (e) {}
163
164            chatboxContent.empty();
165            clearUnread();
166            sendWelcomeMessage();
167        }
168
169        function appendErrorWithRetry(messageText) {
170            var avatarImg = $('<img>').attr('src', chatbot_params.chatbot_bot_avatar_url).attr('alt', 'bot');
171            var bubble = $('<div>').addClass('chat-bubble error');
172            bubble.append($('<div>').addClass('error-text').text(messageText));
173            var retryBtn = $('<button>').addClass('chat-retry-button').text(chatbot_i18n.retry || 'Retry');
174            bubble.append(retryBtn);
175
176            var newMessage = $('<div>').addClass('chat-message bot').append(avatarImg).append(bubble);
177            chatboxContent.append(newMessage);
178            chatboxContent.stop().animate({ scrollTop: chatboxContent[0].scrollHeight }, 200);
179
180            retryBtn.on('click', function() {
181                newMessage.remove();
182                if (lastUserMessage) {
183                    streamMessageToServer(lastUserMessage);
184                }
185            });
186        }
187
188        async function streamMessageToServer(message) {
189            var conversationId = getConversationId();
190            var formData = new FormData();
191
192            formData.append('action', 'newcodebyte_chatbot_stream_message');
193            formData.append('message', message);
194            formData.append('conversation_id', conversationId || '0');
195            formData.append('history', JSON.stringify(conversationHistory));
196            formData.append('newcodebyte_chatbot_message_nonce', chatbot_ajax.newcodebyte_chatbot_message_nonce);
197
198            if (userInfo) {
199                formData.append('name', userInfo.name);
200                formData.append('email', userInfo.email);
201                formData.append('is_first_message', '1');
202                userInfo.sent = true;
203                saveUserInfo();
204                userInfo = null;
205            }
206
207            var botMessageEl = null;
208            var botContent = '';
209            var doneReceived = false;
210            var isAborted = false;
211            var watchdogFired = false;
212            var watchdogTimer = null;
213
214            function startWatchdog() {
215                stopWatchdog();
216                watchdogTimer = setTimeout(function() {
217                    watchdogFired = true;
218                    if (currentAbortController) {
219                        currentAbortController.abort();
220                    }
221                }, 60000);
222            }
223
224            function resetWatchdog() {
225                if (watchdogTimer) {
226                    clearTimeout(watchdogTimer);
227                    watchdogTimer = setTimeout(function() {
228                        watchdogFired = true;
229                        if (currentAbortController) {
230                            currentAbortController.abort();
231                        }
232                    }, 60000);
233                }
234            }
235
236            function stopWatchdog() {
237                if (watchdogTimer) {
238                    clearTimeout(watchdogTimer);
239                    watchdogTimer = null;
240                }
241            }
242
243            isStreaming = true;
244            showStreamingUI(true);
245
246            try {
247                startWatchdog();
248
249                if (!window.fetch || !window.ReadableStream || !window.AbortController) {
250                    throw new Error('no-stream-support');
251                }
252
253                currentAbortController = new AbortController();
254
255                var response = await fetch(chatbot_ajax.ajaxurl, {
256                    method: 'POST',
257                    body: formData,
258                    signal: currentAbortController.signal
259                });
260
261                if (!response.ok) throw new Error('http-error');
262                if (!response.body) throw new Error('no-stream-support');
263
264                $('.chat-message.loading').remove();
265
266                botMessageEl = createBotMessagePlaceholder();
267
268                var reader = response.body.getReader();
269                var decoder = new TextDecoder();
270                var buffer = '';
271
272                while (true) {
273                    var result;
274                    try {
275                        result = await reader.read();
276                    } catch (readError) {
277                        if (readError && readError.name === 'AbortError') {
278                            isAborted = true;
279                            break;
280                        }
281                        throw readError;
282                    }
283                    if (result.done) break;
284
285                    buffer += decoder.decode(result.value, { stream: true });
286
287                    var parts = buffer.split('\n\n');
288                    buffer = parts.pop();
289                    var hadToken = false;
290
291                    for (var p = 0; p < parts.length; p++) {
292                        var lines = parts[p].split('\n');
293                        var eventType = '';
294                        var eventData = '';
295
296                        for (var l = 0; l < lines.length; l++) {
297                            var line = lines[l];
298                            if (line.indexOf('event: ') === 0) eventType = line.substring(7);
299                            else if (line.indexOf('data: ') === 0) eventData = line.substring(6);
300                        }
301
302                        if (eventType === 'token' && eventData) {
303                            try {
304                                var parsed = JSON.parse(eventData);
305                                if (parsed.token) {
306                                    botContent += parsed.token;
307                                    updateBotMessageText(botMessageEl, botContent);
308                                    hadToken = true;
309                                }
310                            } catch (e) {}
311                        } else if (eventType === 'done' && eventData) {
312                            doneReceived = true;
313                            try {
314                                var parsed = JSON.parse(eventData);
315                                if (parsed.conversation_id && !currentConversationId) {
316                                    currentConversationId = parsed.conversation_id;
317                                    try { sessionStorage.setItem(convIdKey, currentConversationId); } catch (e) {}
318                                }
319                            } catch (e) {}
320                        } else if (eventType === 'error' && eventData) {
321                            try {
322                                var parsed = JSON.parse(eventData);
323                                $('.chat-message.loading').remove();
324                                if (botMessageEl && botMessageEl.parent().length) {
325                                    botMessageEl.closest('.chat-message').remove();
326                                }
327                                appendErrorWithRetry(parsed.message || chatbot_i18n.there_was_an_error);
328                                doneReceived = true;
329                            } catch (e) {}
330                        }
331                    }
332
333                    if (hadToken) {
334                        await new Promise(function(resolve) { setTimeout(resolve, 0); });
335                    }
336
337                    resetWatchdog();
338                }
339
340                stopWatchdog();
341
342                if (botContent && botMessageEl && botMessageEl.parent().length) {
343                    botContent = cleanBotContent(botContent);
344                    finalizeBotMessage(botMessageEl, botContent);
345                    conversationHistory.push({ role: 'assistant', content: botContent });
346                    saveHistoryToSessionStorage();
347                    if (!isAborted && !watchdogFired) {
348                        incrementUnread();
349                    }
350                } else if (!doneReceived) {
351                    $('.chat-message.loading').remove();
352                    if (botMessageEl && botMessageEl.parent().length) {
353                        botMessageEl.closest('.chat-message').remove();
354                    }
355                    appendErrorWithRetry(chatbot_i18n.there_was_an_error);
356                }
357
358            } catch (error) {
359                $('.chat-message.loading').remove();
360                stopWatchdog();
361
362                if (error && error.name === 'AbortError') {
363                    if (botMessageEl && botMessageEl.parent().length && botContent) {
364                        botContent = cleanBotContent(botContent);
365                        finalizeBotMessage(botMessageEl, botContent);
366                        conversationHistory.push({ role: 'assistant', content: botContent });
367                        saveHistoryToSessionStorage();
368                    } else {
369                        if (botMessageEl && botMessageEl.parent().length) {
370                            botMessageEl.closest('.chat-message').remove();
371                        }
372                        appendErrorWithRetry(watchdogFired ? chatbot_i18n.timeout_error : chatbot_i18n.there_was_an_error);
373                    }
374                } else {
375                    fallbackSendMessage(message);
376                }
377            } finally {
378                stopWatchdog();
379                currentAbortController = null;
380                isStreaming = false;
381                showStreamingUI(false);
382            }
383        }
384
385        function fallbackSendMessage(message) {
386            var conversationId = getConversationId();
387            var ajaxData = {
388                action: 'newcodebyte_chatbot_send_message',
389                message: message,
390                conversation_id: conversationId,
391                history: JSON.stringify(conversationHistory),
392                newcodebyte_chatbot_message_nonce: chatbot_ajax.newcodebyte_chatbot_message_nonce
393            };
394
395            if (userInfo) {
396                ajaxData.name = userInfo.name;
397                ajaxData.email = userInfo.email;
398                ajaxData.is_first_message = '1';
399                userInfo.sent = true;
400                saveUserInfo();
401                userInfo = null;
402            }
403
404            isStreaming = true;
405            showStreamingUI(true);
406
407            var loadingMessage = null;
408            if (!$('.chat-message.loading').length) {
409                var avatarImg = $('<img>').attr('src', chatbot_params.chatbot_bot_avatar_url).attr('alt', 'bot');
410                var typingIndicator = $('<span>').addClass('typing-indicator').text(chatbot_i18n.typing_indicator || 'Typing…');
411                var bubble = $('<div>').addClass('chat-bubble').append(typingIndicator);
412                loadingMessage = $('<div>').addClass('chat-message loading bot').append(avatarImg).append(bubble);
413                chatboxContent.append(loadingMessage);
414                chatboxContent.stop().animate({ scrollTop: chatboxContent[0].scrollHeight }, 200);
415            }
416
417            $.ajax({
418                url: chatbot_ajax.ajaxurl,
419                type: 'POST',
420                data: ajaxData,
421                timeout: 60000,
422                success: handleServerResponse,
423                error: handleServerError,
424                complete: function() {
425                    isStreaming = false;
426                    showStreamingUI(false);
427                }
428            });
429        }
430
431        function handleServerResponse(response) {
432            $('.chat-message.loading').remove();
433
434            if (response.success) {
435                var botMessageRaw = cleanBotContent(response.data.message);
436
437                conversationHistory.push({ role: 'assistant', content: botMessageRaw });
438                saveHistoryToSessionStorage();
439
440                appendBotMessage(botMessageRaw);
441
442                if (response.data.conversation_id && !currentConversationId) {
443                    currentConversationId = response.data.conversation_id;
444                    try { sessionStorage.setItem(convIdKey, currentConversationId); } catch (e) {}
445                }
446
447                incrementUnread();
448            } else {
449                appendErrorWithRetry(response.data.message || chatbot_i18n.there_was_an_error);
450            }
451        }
452
453        function handleServerError(xhr) {
454            $('.chat-message.loading').remove();
455            var errorMessage = chatbot_i18n.there_was_an_error;
456            if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
457                errorMessage = xhr.responseJSON.data.message;
458            }
459            appendErrorWithRetry(errorMessage);
460        }
461
462        function createBotMessagePlaceholder() {
463            var avatarImg = $('<img>').attr('src', chatbot_params.chatbot_bot_avatar_url).attr('alt', 'bot');
464            var typingIndicator = $('<span>').addClass('typing-indicator').text(chatbot_i18n.typing_indicator || 'Typing…');
465            var messageBubble = $('<div>').addClass('chat-bubble streaming').append(typingIndicator);
466            var newMessage = $('<div>').addClass('chat-message bot streaming').append(avatarImg).append(messageBubble);
467            chatboxContent.append(newMessage);
468            chatboxContent.stop().animate({ scrollTop: chatboxContent[0].scrollHeight }, 200);
469            return messageBubble;
470        }
471
472        function removeTypingIndicator($bubble) {
473            $bubble.find('.typing-indicator').remove();
474        }
475
476        function updateBotMessageText($bubble, text) {
477            removeTypingIndicator($bubble);
478            if (typeof marked !== 'undefined' && typeof DOMPurify !== 'undefined') {
479                $bubble.html(DOMPurify.sanitize(marked.parse(text).trim()));
480            } else {
481                $bubble.text(cleanBotContent(text));
482            }
483            chatboxContent.stop().animate({ scrollTop: chatboxContent[0].scrollHeight }, 50);
484        }
485
486        function cleanBotContent(text) {
487            return String(text || '').replace(/\s+$/, '');
488        }
489
490        function finalizeBotMessage($bubble, text) {
491            if (!$bubble || !$bubble.parent().length) return;
492
493            $bubble.removeClass('streaming');
494            $bubble.closest('.chat-message').removeClass('streaming');
495            removeTypingIndicator($bubble);
496
497            if (typeof marked !== 'undefined' && typeof DOMPurify !== 'undefined') {
498                $bubble.html(DOMPurify.sanitize(marked.parse(text).trim()));
499            } else {
500                $bubble.text(cleanBotContent(text));
501            }
502            chatboxContent.stop().animate({ scrollTop: chatboxContent[0].scrollHeight }, 200);
503        }
504
505        function sendWelcomeMessage() {
506            var welcomeMsg = cleanBotContent(chatbot_i18n.welcome_message || 'Hello!');
507            if (conversationHistory.length === 0) {
508                conversationHistory.push({ role: 'assistant', content: welcomeMsg });
509                saveHistoryToSessionStorage();
510                appendBotMessage(welcomeMsg);
511            }
512        }
513
514        function showPreChatForm() {
515            if (chatboxContent.find('#prechat-form').length) return;
516            chatboxContent.empty();
517            $('#chatbox-controls').hide();
518
519            var formHtml = '<div id="prechat-form">' +
520                '<h3>' + (chatbot_i18n.prechat_title || 'Before we start chatting') + '</h3>' +
521                '<p>' + (chatbot_i18n.prechat_description || 'Please provide the following information:') + '</p>' +
522                '<div class="form-field">' +
523                '<label for="prechat-name">' + (chatbot_i18n.prechat_name_label || 'Name') + ' *</label>' +
524                '<input type="text" id="prechat-name" required />' +
525                '</div>' +
526                '<div class="form-field">' +
527                '<label for="prechat-email">' + (chatbot_i18n.prechat_email_label || 'Email') + '</label>' +
528                '<input type="email" id="prechat-email" />' +
529                '</div>' +
530                '<button id="prechat-start-btn">' + (chatbot_i18n.prechat_button_text || 'Start Chat') + '</button>' +
531                '<div id="prechat-error" style="color:#c00;margin-top:8px;display:none;"></div>' +
532                '</div>';
533
534            chatboxContent.append(formHtml);
535
536            $('#prechat-start-btn').on('click', function() {
537                var name = $('#prechat-name').val().trim();
538                if (!name) {
539                    $('#prechat-error').text(chatbot_i18n.prechat_error_message || 'Please fill in all required fields.').show();
540                    return;
541                }
542                $('#prechat-error').hide();
543                userInfo = { name: name, email: $('#prechat-email').val().trim() };
544                saveUserInfo();
545                $('#prechat-form').remove();
546                $('#chatbox-controls').show();
547                // Riparte da zero: utente nuovo, cronologia fresca.
548                conversationHistory = [];
549                currentConversationId = null;
550                try {
551                    sessionStorage.removeItem(historyKey);
552                    sessionStorage.removeItem(convIdKey);
553                } catch (e) {}
554                sendWelcomeMessage();
555            });
556        }
557
558        function appendChatMessage(type, avatar, messageContent) {
559            var avatarImg = $('<img>').attr('src', avatar).attr('alt', type);
560            var messageBubble = $('<div>').addClass('chat-bubble');
561
562            if (type === 'user') {
563                messageBubble.text(messageContent);
564            } else {
565                if (typeof marked !== 'undefined' && typeof DOMPurify !== 'undefined') {
566                    messageBubble.html(DOMPurify.sanitize(marked.parse(messageContent).trim()));
567                } else {
568                    messageBubble.text(cleanBotContent(messageContent));
569                }
570            }
571
572            var newMessage = $('<div>').addClass('chat-message ' + type).append(avatarImg).append(messageBubble);
573            chatboxContent.append(newMessage);
574            chatboxContent.stop().animate({ scrollTop: chatboxContent[0].scrollHeight }, 200);
575        }
576
577        function appendUserMessage(message) { appendChatMessage('user', chatbot_params.chatbot_user_avatar_url, message); }
578        function appendBotMessage(message) { appendChatMessage('bot', chatbot_params.chatbot_bot_avatar_url, message); }
579
580        function toggleChatbox() {
581            if (isAnimating) return;
582            isAnimating = true;
583
584            if (proactiveBubble.hasClass('visible')) {
585                proactiveBubble.removeClass('visible').addClass('hidden');
586                try { sessionStorage.setItem('newcodebyte_proactive_bubble_closed', 'true'); } catch(e) {}
587            }
588
589            var isMobile = window.innerWidth <= 768;
590            var newPosition = isChatboxOpen ? (isMobile ? '-100vh' : '-800px') : (isMobile ? '60px' : '85px');
591            var newIcon = isChatboxOpen ? chatbot_params.chatbot_icon_closed_url : chatbot_params.chatbot_icon_open_url;
592
593            chatbox.animate({ bottom: newPosition }, 300, function() {
594                isAnimating = false;
595                isChatboxOpen = !isChatboxOpen;
596                if (isChatboxOpen) {
597                    clearUnread();
598                }
599                updateUnreadBadge();
600            });
601
602            chatboxButtonImg.attr('src', newIcon);
603
604            if (!isChatboxOpen) {
605                playNotificationSound();
606                if (conversationHistory.length === 0) {
607                    if (chatbot_params.enable_prechat_form === '1' && !userInfo) {
608                        showPreChatForm();
609                    } else {
610                        sendWelcomeMessage();
611                    }
612                }
613            }
614        }
615
616        function playNotificationSound() {
617            var audioPath = chatbot_params.notification_sound_url;
618            if (audioPath) {
619                try {
620                    var audio = new Audio(audioPath);
621                    audio.play().catch(function() {});
622                } catch (e) {}
623            }
624        }
625
626        loadHistoryFromSessionStorage();
627        getConversationId();
628        loadUnreadCount();
629        loadUserInfo();
630        updateUnreadBadge();
631        autoResizeTextarea();
632
633        if ($('.chatbot-shortcode-wrapper').length === 0) {
634            chatboxButton.on('click', toggleChatbox);
635            $('#chatbox-close').on('click', toggleChatbox);
636        }
637
638        chatboxSend.on('click', sendMessage);
639
640        chatboxStop.on('click', function() {
641            if (currentAbortController) {
642                currentAbortController.abort();
643            }
644        });
645
646        chatboxNew.on('click', function() {
647            if (!confirm(chatbot_i18n.confirm_new_conversation || 'Start a new conversation?')) return;
648            startNewConversation();
649        });
650
651        // Supporto tastiera per lo span #chatbox-new (role="button"):: Enter/Spazio
652        chatboxNew.on('keydown', function(e) {
653            if (e.key === 'Enter' || e.key === ' ') {
654                e.preventDefault();
655                chatboxNew.trigger('click');
656            }
657        });
658
659        chatboxInput.on('input', autoResizeTextarea);
660        chatboxInput.on('keydown', function(e) {
661            if (e.key === 'Enter' && !e.shiftKey) {
662                e.preventDefault();
663                sendMessage();
664            }
665        });
666
667        $(document).on('chatbot:ready', function() {
668            if (conversationHistory.length === 0) {
669                if (chatbot_params.enable_prechat_form === '1' && !userInfo) {
670                    showPreChatForm();
671                } else {
672                    sendWelcomeMessage();
673                }
674            }
675        });
676
677        if ($('.chatbot-shortcode-wrapper').length === 0 && chatbot_params.enable_proactive === '1' && proactiveBubble.length > 0) {
678            proactiveBubble.find('.chatbot-proactive-text').text(chatbot_params.proactive_text || '');
679
680            proactiveBubble.find('.chatbot-proactive-close').on('click', function(e) {
681                e.stopPropagation();
682                proactiveBubble.removeClass('visible').addClass('hidden');
683                try { sessionStorage.setItem('newcodebyte_proactive_bubble_closed', 'true'); } catch (e) {}
684            });
685
686            proactiveBubble.on('click', function(e) {
687                if ($(e.target).closest('.chatbot-proactive-close').length) return;
688                if (!isChatboxOpen) toggleChatbox();
689            });
690
691            var proactiveDelay = parseInt(chatbot_params.proactive_delay, 10) || 5;
692            setTimeout(function() {
693                if (!isChatboxOpen && !proactiveBubble.hasClass('visible')) {
694                    var closed = false;
695                    try { closed = sessionStorage.getItem('newcodebyte_proactive_bubble_closed') === 'true'; } catch (e) {}
696                    if (!closed) {
697                        proactiveBubble.removeClass('hidden').addClass('visible');
698                    }
699                }
700            }, proactiveDelay * 1000);
701        }
702
703    })(jQuery);
704});

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.