PageSourceSearch

https://democratas.co/wp-content/plugins/gpt3-ai-content-generator/public/js/wpaicg-chat.js

js democratas.co collected 2026-10-08 20:58:08 UTC 167,749 bytes, 3,200 lines download raw bytes

1/**
2 * Loads or initializes conversations on page load, but now
3 * simply displays the *latest* conversation instead of forcing
4 * a brand-new conversation each time.
5 */
6function loadConversations() {
7    // Attempt to retrieve existing clientId from localStorage; create if missing
8    var clientId = localStorage.getItem('wpaicg_chat_client_id');
9    if (!clientId) {
10        clientId = generateRandomString(10);
11        localStorage.setItem('wpaicg_chat_client_id', clientId);
12    }
13
14    // Load chat interfaces (shortcode + widget) on the page
15    loadChatInterface('.wpaicg-chat-shortcode', 'shortcode', clientId);
16    loadChatInterface('.wpaicg-chatbox', 'widget', clientId);
17
18    // For each container, set its active conversation to the highest
19    // existing index. If none exist, create a new conversation once.
20    var containers = document.querySelectorAll('.wpaicg-chat-shortcode, .wpaicg-chatbox');
21    containers.forEach(container => {
22        let botId = container.getAttribute('data-bot-id') || '0';
23        let chatType = container.getAttribute('data-type') || 'shortcode';
24
25        // Build base localStorage prefix
26        let conversationKeyBase = (botId !== '0')
27            ? `wpaicg_chat_history_custom_bot_${botId}_${clientId}`
28            : `wpaicg_chat_history_${chatType}_${clientId}`;
29
30        // Find existing conversation indexes
31        let highestIndex = 0;
32        for (let i = 0; i < localStorage.length; i++) {
33            let key = localStorage.key(i);
34            if (key.startsWith(conversationKeyBase)) {
35                let suffixMatch = key.match(/_(\d+)$/);
36                if (suffixMatch) {
37                    let indexNum = parseInt(suffixMatch[1], 10);
38                    if (indexNum > highestIndex) {
39                        highestIndex = indexNum;
40                    }
41                }
42            }
43        }
44
45        // If no existing conversation found, create one
46        if (highestIndex === 0) {
47            highestIndex = 1;
48            let firstConversationKey = `${conversationKeyBase}_${highestIndex}`;
49            localStorage.setItem(firstConversationKey, JSON.stringify([]));
50        }
51
52        // Mark the highest (latest) conversation as active
53        let latestConversationKey = `${conversationKeyBase}_${highestIndex}`;
54        let activeConversationKey = `wpaicg_current_conversation_${botId}_${clientId}`;
55        localStorage.setItem(activeConversationKey, latestConversationKey);
56
57        // If assistant is enabled, do the same for the "thread" logic
58        let assistantEnabled = container.getAttribute('data-assistant-enabled') === 'true';
59        if (assistantEnabled) {
60            let threadBase = (botId !== '0')
61                ? `custom_bot_${botId}_${clientId}`
62                : `${chatType}_${clientId}`;
63
64            let threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
65            let highestThreadIndex = 0;
66            for (let existingKey in threadListObj) {
67                if (existingKey.startsWith(threadBase)) {
68                    let suffixMatch = existingKey.match(/_(\d+)$/);
69                    if (suffixMatch) {
70                        let threadIndexNum = parseInt(suffixMatch[1], 10);
71                        if (threadIndexNum > highestThreadIndex) {
72                            highestThreadIndex = threadIndexNum;
73                        }
74                    }
75                }
76            }
77
78            // If no existing thread, create a new one
79            if (highestThreadIndex === 0) {
80                highestThreadIndex = 1;
81                let newThreadKey = `${threadBase}_${highestThreadIndex}`;
82                localStorage.setItem(`wpaicg_current_thread_${botId}_${clientId}`, newThreadKey);
83            } else {
84                // Otherwise, set the highest as active
85                let existingThreadKey = `${threadBase}_${highestThreadIndex}`;
86                localStorage.setItem(`wpaicg_current_thread_${botId}_${clientId}`, existingThreadKey);
87            }
88        }
89    });
90
91    // Update the conversation list in the sidebar
92    loadConversationList();
93}
94/**
95 * Initialize "New Chat" buttons. When a user clicks "New Chat," we create
96 * a fresh conversation in localStorage and load it in the chat interface.
97 * This updated version also immediately refreshes the conversation list
98 * in the sidebar so users see the new chat appear instantly.
99 */
100function initNewChatButtons() {
101    const newChatButtons = document.querySelectorAll('.wpaicg-new-chat-button');
102    newChatButtons.forEach(button => {
103        button.addEventListener('click', function () {
104            // Find the parent chat container (shortcode or widget)
105            const chatContainer = button.closest('.wpaicg-chat-shortcode, .wpaicg-chatbox');
106            if (!chatContainer) return;
107
108            // Identify chat type, e.g. 'custom_bot_4367' or 'shortcode' or 'widget'
109            const botId = chatContainer.getAttribute('data-bot-id') || '0';
110            const chatType = chatContainer.getAttribute('data-type') || 'shortcode';
111            const assistantEnabled = chatContainer.getAttribute('data-assistant-enabled') === 'true';
112            let conversationKeyBase = '';
113
114            // Distinguish custom bots vs default shortcodes or widgets
115            if (botId !== '0') {
116                // custom bot format
117                conversationKeyBase = `wpaicg_chat_history_custom_bot_${botId}`;
118            } else {
119                // fallback: default shortcodes or widgets
120                conversationKeyBase = `wpaicg_chat_history_${chatType}`;
121            }
122
123            // Grab or create the clientID
124            let clientID = localStorage.getItem('wpaicg_chat_client_id');
125            if (!clientID) {
126                clientID = generateRandomString(10);
127                localStorage.setItem('wpaicg_chat_client_id', clientID);
128            }
129
130            conversationKeyBase += `_${clientID}`;
131
132            // Count how many existing conversations use this prefix
133            let highestIndex = 0;
134            for (let i = 0; i < localStorage.length; i++) {
135                const key = localStorage.key(i);
136                if (key.startsWith(conversationKeyBase)) {
137                    const suffixMatch = key.match(/_(\d+)$/);
138                    if (suffixMatch) {
139                        const indexNum = parseInt(suffixMatch[1], 10);
140                        if (indexNum > highestIndex) highestIndex = indexNum;
141                    }
142                }
143            }
144            // The new conversation will be next integer
145            const newConversationIndex = highestIndex + 1;
146            const newConversationKey = `${conversationKeyBase}_${newConversationIndex}`;
147
148            // Initialize an empty array for that conversation
149            localStorage.setItem(newConversationKey, JSON.stringify([]));
150
151            // Also set a timestamp for sorting
152            localStorage.setItem(`${newConversationKey}_timestamp`, Date.now().toString());
153
154            // Also set this new conversation key as the "active conversation"
155            const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientID}`;
156            localStorage.setItem(activeConversationKey, newConversationKey);
157
158            // ----- Assistant-enabled logic for multi-thread handling -----
159            if (assistantEnabled) {
160                // We store the "active thread" localStorage
161                const threadBase = botId !== '0'
162                    ? `custom_bot_${botId}_${clientID}`
163                    : `${chatType}_${clientID}`;
164
165                // Count how many existing "thread" keys exist for this base
166                let highestThreadIndex = 0;
167                const threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
168                for (let existingKey in threadListObj) {
169                    if (existingKey.startsWith(threadBase)) {
170                        const suffixMatch = existingKey.match(/_(\d+)$/);
171                        if (suffixMatch) {
172                            const threadIndexNum = parseInt(suffixMatch[1], 10);
173                            if (threadIndexNum > highestThreadIndex) highestThreadIndex = threadIndexNum;
174                        }
175                    }
176                }
177                const newThreadIndex = highestThreadIndex + 1;
178                const newThreadKey = `${threadBase}_${newThreadIndex}`;
179
180                // Now set the "active thread" localStorage
181                const activeThreadKey = `wpaicg_current_thread_${botId}_${clientID}`;
182                localStorage.setItem(activeThreadKey, newThreadKey);
183            }
184
185            // Immediately load the newly created conversation so user sees empty chat
186            loadSelectedConversation(newConversationKey, chatContainer);
187
188            // Refresh the conversation list in the sidebar so user sees it
189            loadConversationList();
190        });
191    });
192}
193
194/**
195 * Utility function that the code base already uses or references for client ID generation.
196 * If you already have a similar function, you may omit or adapt this.
197 */
198function generateRandomString(length) {
199    let result = '';
200    const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
201    for (let i = 0; i < length; i++) {
202        result += characters.charAt(Math.floor(Math.random() * characters.length));
203    }
204    return result;
205}
206function showAllConversationStarters() {
207    // Target both interfaces
208    var containers = ['.wpaicg-chat-shortcode', '.wpaicg-chatbox'];
209    containers.forEach(containerSelector => {
210        var chatContainers = document.querySelectorAll(containerSelector);
211        chatContainers.forEach(chatContainer => {
212            showConversationStarters(chatContainer);
213        });
214    });
215}
216/**
217 * Appends the user/AI message to the localStorage conversation, respecting memory limits
218 * and triggers a refresh of the conversation list. Also stores a timestamp for sorting.
219 *
220 * @param {string} message           The raw message text from the user or AI.
221 * @param {string} sender            Either 'user' or 'ai'.
222 * @param {string} uniqueId          A unique identifier for the message (e.g., a random integer).
223 * @param {HTMLElement} chat         The parent chat container (.wpaicg-chat-shortcode or .wpaicg-chatbox).
224 * @param {string} chatbot_identity  Identity of the chatbot ('custom_bot_####' or 'shortcode'/'widget').
225 * @param {string} clientID          The user's local client/session ID from localStorage.
226 */
227function updateChatHistory(message, sender, uniqueId, chat, chatbot_identity, clientID) {
228    // Identify which bot is being used
229    const botId = chat.getAttribute('data-bot-id') || '0';
230    // Active conversation key in localStorage
231    const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientID}`;
232    let conversationKey = localStorage.getItem(activeConversationKey);
233
234    // If no active conversation yet, create one
235    if (!conversationKey) {
236        let defaultIndex = 1;
237        if (botId !== '0') {
238            // custom bot
239            conversationKey = `wpaicg_chat_history_custom_bot_${botId}_${clientID}_${defaultIndex}`;
240        } else {
241            // fallback for shortcodes or widgets
242            const type = chat.getAttribute('data-type') || 'shortcode';
243            conversationKey = `wpaicg_chat_history_${type}_${clientID}_${defaultIndex}`;
244        }
245        localStorage.setItem(conversationKey, JSON.stringify([]));
246        localStorage.setItem(activeConversationKey, conversationKey);
247    }
248
249    // Load existing history
250    let history = JSON.parse(localStorage.getItem(conversationKey) || '[]');
251
252    // Memory limit from data attribute or default
253    const memoryLimit = parseInt(chat.getAttribute('data-memory-limit'), 10) || 100;
254    // Format the message with "Human: " or "AI: "
255    const formattedMessage = (sender === 'user' ? "Human: " : "AI: ") + (message.trim() || '');
256    const numericId = typeof uniqueId === 'string' ? parseInt(uniqueId, 10) : uniqueId;
257
258    // Push new message to array
259    history.push({
260        id: numericId || '',
261        text: formattedMessage
262    });
263
264    // Trim the array to memoryLimit
265    if (history.length > memoryLimit) {
266        history = history.slice(-memoryLimit);
267    }
268
269    // Save updated history
270    localStorage.setItem(conversationKey, JSON.stringify(history));
271
272    // Update the 'last updated' timestamp in a separate localStorage key
273    // This will be used for sorting conversations in descending order
274    localStorage.setItem(`${conversationKey}_timestamp`, Date.now().toString());
275
276    // Refresh the conversation list so the user sees updated titles/timestamps
277    loadConversationList();
278}
279
280function loadChatInterface(containerSelector, type, clientId) {
281    var chatContainers = document.querySelectorAll(containerSelector);
282
283    chatContainers.forEach(chatContainer => {
284        // Read autoload chat conversations setting, default to '1' if not set
285        var autoloadConversations = chatContainer.getAttribute('data-autoload_chat_conversations');
286        if (autoloadConversations === null) {
287            autoloadConversations = '1';  // Default value if attribute does not exist
288        }
289
290        // Fetch the bot ID based on the type
291        var botId = chatContainer.getAttribute('data-bot-id') || '0';
292
293        if (autoloadConversations === '0') {
294            // Get the active conversation key for this chat/bot
295            var activeConversationKey = `wpaicg_current_conversation_${botId}_${clientId}`;
296            var conversationKey = localStorage.getItem(activeConversationKey);
297
298            // If no active conversation has been set, find the latest one
299            if (!conversationKey) {
300                let highestIndex = 0;
301                let conversationKeyBase = botId !== '0'
302                    ? `wpaicg_chat_history_custom_bot_${botId}_${clientId}`
303                    : `wpaicg_chat_history_${type}_${clientId}`;
304
305                // Find the highest index (latest conversation)
306                for (let i = 0; i < localStorage.length; i++) {
307                    let key = localStorage.key(i);
308                    if (key.startsWith(conversationKeyBase)) {
309                        let suffixMatch = key.match(/_(\d+)$/);
310                        if (suffixMatch) {
311                            let indexNum = parseInt(suffixMatch[1], 10);
312                            if (indexNum > highestIndex) {
313                                highestIndex = indexNum;
314                            }
315                        }
316                    }
317                }
318
319                // If we found a conversation, use it
320                if (highestIndex > 0) {
321                    conversationKey = `${conversationKeyBase}_${highestIndex}`;
322                }
323            }
324
325            // Retrieve and display the chat history if we have a valid key
326            if (conversationKey && localStorage.getItem(conversationKey)) {
327                var chatHistory = localStorage.getItem(conversationKey);
328                chatHistory = JSON.parse(chatHistory);
329
330                // Convert old format messages to new format
331                chatHistory = chatHistory.map(message => {
332                    if (typeof message === 'string') {
333                        return {
334                            id: '', // Old messages won't have an ID, so leave it empty
335                            text: message
336                        };
337                    }
338                    return message; // Leave new format messages as is
339                });
340
341                // Save the updated history back to localStorage in the new format
342                localStorage.setItem(conversationKey, JSON.stringify(chatHistory));
343
344                var chatBox = chatContainer.querySelector('.wpaicg-chatbox-messages, .wpaicg-chat-shortcode-messages'); // Generalized selector
345                if (!chatBox) {
346                    console.error(`No chat box found within the ${type} container.`);
347                    return;
348                }
349                chatBox.innerHTML = '';  // Clears the chat box
350                chatHistory.forEach(message => {
351                    reconstructMessage(chatBox, message, chatContainer);
352                });
353                chatBox.appendChild(document.createElement('br'));
354                requestAnimationFrame(() => {
355                    requestAnimationFrame(() => {
356                        chatBox.scrollTop = chatBox.scrollHeight; // Scrolls to the bottom
357                    });
358                });
359                hideConversationStarter(chatContainer);
360            } else {
361                showConversationStarters(chatContainer);
362            }
363        } else {
364            showConversationStarters(chatContainer);
365        }
366    });
367}
368
369function reconstructMessage(chatBox, message, chatContainer) {
370    var messageElement = document.createElement('li');
371
372    // Check if message is an object and has a 'text' property
373    var messageText = typeof message === 'object' && message.text ? message.text : message;
374
375    // Determine if the message is from the user or AI
376    var isUserMessage = messageText.startsWith('Human:');
377    var isWidget = chatContainer.classList.contains('wpaicg-chatbox');
378
379    // Apply the correct class based on message source and container type
380    if (isUserMessage) {
381        messageElement.className = isWidget ? 'wpaicg-chat-user-message' : 'wpaicg-user-message';
382    } else {
383        messageElement.className = isWidget ? 'wpaicg-chat-ai-message' : 'wpaicg-ai-message';
384    }
385
386    var formattedMessage = messageText.replace('Human:', '').replace('AI:', '');
387    formattedMessage = marked.parse(formattedMessage);
388    var userBgColor = chatContainer.getAttribute('data-user-bg-color');
389    var aiBgColor = chatContainer.getAttribute('data-ai-bg-color');
390    var fontSize = chatContainer.getAttribute('data-fontsize');
391    var fontColor = chatContainer.getAttribute('data-color');
392
393    messageElement.style.backgroundColor = isUserMessage ? userBgColor : aiBgColor;
394    messageElement.style.color = fontColor;
395    messageElement.style.fontSize = fontSize;
396    messageElement.innerHTML = `<span class="wpaicg-chat-message">${formattedMessage}</span>`;
397
398    chatBox.appendChild(messageElement);
399}
400function hideConversationStarter(chatContainer) {
401    var starters = chatContainer.querySelectorAll('.wpaicg-conversation-starters');
402    starters.forEach(starter => starter.style.display = 'none');
403}
404
405function showConversationStarters(chatContainer) {
406    const startersContainer = chatContainer.querySelector('.wpaicg-conversation-starters');
407    if (startersContainer) {  // Check if the container exists
408        startersContainer.style.visibility = 'visible'; // Make the container visible if it exists
409        const starters = startersContainer.querySelectorAll('.wpaicg-conversation-starter');
410        starters.forEach((starter, index) => {
411            setTimeout(() => {
412                starter.style.opacity = "1";
413                starter.style.transform = "translateY(0)";
414            }, index * 150); // Staggered appearance
415        });
416    }
417}
418/**
419 * Adjusts the size of all shortcode-based chat windows (.wpaicg-chat-shortcode),
420 * including handling fullscreen mode and mobile layout. Now adds an extra offset
421 * on mobile when there is *no* footer to prevent the text box from being cut off.
422 */
423function wpaicgChatShortcodeSize() {
424    var wpaicgWindowWidth = window.innerWidth;
425    var wpaicgWindowHeight = window.innerHeight;
426    var chatShortcodes = document.getElementsByClassName('wpaicg-chat-shortcode');
427
428    if (chatShortcodes !== null && chatShortcodes.length) {
429        for (var i = 0; i < chatShortcodes.length; i++) {
430            var chatShortcode = chatShortcodes[i];
431            var parentChat = chatShortcode.parentElement;
432            var parentWidth = parentChat.offsetWidth;
433            var chatWidth = chatShortcode.getAttribute('data-width');
434            var chatHeight = chatShortcode.getAttribute('data-height');
435            var chatFooter = chatShortcode.getAttribute('data-footer') === 'true';
436            var chatBar = chatShortcode.getAttribute('data-has-bar') === 'true';
437            var chatRounded = parseFloat(chatShortcode.getAttribute('data-chat_rounded'));
438            var textRounded = parseFloat(chatShortcode.getAttribute('data-text_rounded'));
439            var textHeight = parseFloat(chatShortcode.getAttribute('data-text_height'));
440            var textInput = chatShortcode.getElementsByClassName('wpaicg-chat-shortcode-typing')[0];
441
442            // Set text area dimensions
443            textInput.style.height = textHeight + 'px';
444            textInput.style.borderRadius = textRounded + 'px';
445
446            // Round the chat container
447            chatShortcode.style.borderRadius = chatRounded + 'px';
448            chatShortcode.style.overflow = 'hidden';
449
450            // Fallback dimension if not supplied
451            chatWidth = chatWidth !== null ? chatWidth : '350';
452            chatHeight = chatHeight !== null ? chatHeight : '400';
453
454            // FULLSCREEN MODE
455            if (chatShortcode.classList.contains('wpaicg-fullscreened')) {
456                // Occupy the full window
457                parentWidth = wpaicgWindowWidth;
458
459                // On very narrow mobile, subtract a bit more from height
460                if (wpaicgWindowWidth < 480) {
461                    chatWidth = wpaicgWindowWidth;
462                    chatHeight = wpaicgWindowHeight - 40; 
463                } else {
464                    chatWidth = resolveDimension(chatWidth, parentWidth);
465                    chatHeight = resolveDimension(chatHeight, wpaicgWindowHeight);
466                }
467
468                chatShortcode.style.width = chatWidth + 'px';
469                chatShortcode.style.maxWidth = chatWidth + 'px';
470                chatShortcode.style.height = chatHeight + 'px';
471                chatShortcode.style.maxHeight = chatHeight + 'px';
472                chatShortcode.style.marginTop = 0;
473
474                // Deduce space for header(s), action bar, etc.
475                var deduceHeight = 69; // Base
476                if (chatFooter) {
477                    deduceHeight += 60; // Footer
478                }
479                if (chatBar) {
480                    deduceHeight += 30; // Action bar
481                }
482                deduceHeight += 20;   // Additional spacing
483
484                // On mobile, add some extra
485                if (wpaicgWindowWidth < 480) {
486                    deduceHeight += 20;
487                }
488
489                // NEW: If no footer and on mobile, add offset so text box is visible
490                if (!chatFooter && wpaicgWindowWidth < 480) {
491                    deduceHeight += 60;
492                }
493
494                var chatMessages = chatShortcode.getElementsByClassName('wpaicg-chat-shortcode-messages')[0];
495                chatMessages.style.height = (chatHeight - deduceHeight - textHeight) + 'px';
496
497                // Force scroll to bottom so new messages or input are visible
498                requestAnimationFrame(() => {
499                    requestAnimationFrame(() => {
500                        chatMessages.scrollTop = chatMessages.scrollHeight;
501                    });
502                });
503            }
504            // NON-FULLSCREEN MODE
505            else {
506                // Resolve percentage or px for chatWidth
507                if (chatWidth.indexOf('%') < 0) {
508                    if (chatWidth.indexOf('px') < 0) {
509                        chatWidth = parseFloat(chatWidth);
510                    } else {
511                        chatWidth = parseFloat(chatWidth.replace(/px/g, ''));
512                    }
513                } else {
514                    chatWidth = parseFloat(chatWidth.replace(/%/g, ''));
515                    if (chatWidth < 100) {
516                        chatWidth = chatWidth * parentWidth / 100;
517                    } else {
518                        chatWidth = '';
519                    }
520                }
521
522                // Resolve percentage or px for chatHeight
523                if (chatHeight.indexOf('%') < 0) {
524                    if (chatHeight.indexOf('px') < 0) {
525                        chatHeight = parseFloat(chatHeight);
526                    } else {
527                        chatHeight = parseFloat(chatHeight.replace(/px/g, ''));
528                    }
529                } else {
530                    chatHeight = parseFloat(chatHeight.replace(/%/g, ''));
531                    chatHeight = chatHeight * wpaicgWindowHeight / 100;
532                }
533
534                // Apply final computed width & height
535                if (chatWidth !== '') {
536                    chatShortcode.style.width = chatWidth + 'px';
537                    chatShortcode.style.maxWidth = chatWidth + 'px';
538                } else {
539                    chatShortcode.style.width = '';
540                    chatShortcode.style.maxWidth = '';
541                }
542
543                if (chatShortcode.classList.contains('wpaicg-fullscreened')) {
544                    chatShortcode.style.marginTop = 0;
545                } else {
546                    chatShortcode.style.marginTop = '';
547                }
548
549                var chatMessages = chatShortcode.getElementsByClassName('wpaicg-chat-shortcode-messages')[0];
550                var deduceHeight = 69; // Base
551                if (chatFooter) {
552                    deduceHeight += 60; // Footer
553                }
554                if (chatBar) {
555                    deduceHeight += 30; // Action bar
556                }
557                deduceHeight += 20;   // Additional spacing
558
559                // On mobile, add some extra
560                if (wpaicgWindowWidth < 480) {
561                    deduceHeight += 20;
562                }
563
564                // NEW: If no footer and on mobile, add offset so text box is visible
565                if (!chatFooter && wpaicgWindowWidth < 480) {
566                    deduceHeight += 60;
567                }
568
569                // Subtract the typed text box height
570                chatMessages.style.height = (chatHeight - deduceHeight - textHeight) + 'px';
571            }
572        }
573    }
574}
575
576/**
577 * Adjusts the size of widget-based chat windows (.wpaicg_chat_widget_content
578 * containing .wpaicg-chatbox). Also adds an extra offset on mobile when there's
579 * no footer so the text box remains visible in fullscreen.
580 */
581function wpaicgChatBoxSize() {
582    var wpaicgWindowWidth = window.innerWidth;
583    var wpaicgWindowHeight = window.innerHeight;
584    var chatWidgets = document.getElementsByClassName('wpaicg_chat_widget_content');
585    var isMobile = wpaicgWindowWidth <= 767;
586
587    if (chatWidgets.length) {
588        for (var i = 0; i < chatWidgets.length; i++) {
589            var chatWidget = chatWidgets[i];
590            var chatbox = chatWidget.getElementsByClassName('wpaicg-chatbox')[0];
591            
592            if (!chatbox) continue;
593            
594            var chatWidth = chatbox.getAttribute('data-width') || '350';
595            var chatHeight = chatbox.getAttribute('data-height') || '400';
596            var chatFooter = chatbox.getAttribute('data-footer') === 'true';
597            var chatboxBar = chatbox.getElementsByClassName('wpaicg-chatbox-action-bar');
598            var textHeight = parseFloat(chatbox.getAttribute('data-text_height'));
599
600            // If the widget container is in fullscreen mode or if it's a mobile device
601            if (chatWidget.classList.contains('wpaicg-fullscreened') || isMobile) {
602                // For mobile or fullscreen, use the full window dimensions
603                chatWidth = wpaicgWindowWidth;
604                chatHeight = wpaicgWindowHeight - (isMobile ? 60 : 20); // Extra offset for mobile
605                
606                // Apply mobile-specific styling
607                if (isMobile) {
608                    chatWidget.style.position = 'fixed';
609                    chatWidget.style.top = '0';
610                    chatWidget.style.left = '0';
611                    chatWidget.style.right = '0';
612                    chatWidget.style.bottom = '0';
613                    chatWidget.style.zIndex = '999999';
614                    chatWidget.style.width = '100%';
615                    chatWidget.style.height = '100%';
616                    chatWidget.style.maxWidth = 'none';
617                    chatWidget.style.maxHeight = 'none';
618                }
619            } else {
620                // For desktop, use the user-defined dimensions
621                chatWidth = resolveDimension(chatWidth, wpaicgWindowWidth);
622                chatHeight = resolveDimension(chatHeight, wpaicgWindowHeight);
623            }
624
625            // Set dimensions for the chatbox and widget content
626            chatbox.style.width = chatWidth + 'px';
627            chatbox.style.height = chatHeight + 'px';
628            chatWidget.style.width = chatWidth + 'px';
629            chatWidget.style.height = chatHeight + 'px';
630
631            // Calculate the content height by deducting header, footer, action bar etc.
632            var actionBarHeight = chatboxBar.length ? 40 : 0;
633            var footerHeight = chatFooter ? 60 : 0;
634            var extraGap = (isMobile) ? 40 : 20;
635
636            // If no footer on mobile, still add 60px so text box isn't hidden
637            if (!chatFooter && isMobile) {
638                footerHeight = 60;
639            }
640
641            var contentHeight = chatHeight - textHeight - actionBarHeight - footerHeight - extraGap;
642            var messagesHeight = contentHeight - 20;
643
644            var chatboxContent = chatWidget.getElementsByClassName('wpaicg-chatbox-content')[0];
645            var chatboxMessages = chatWidget.getElementsByClassName('wpaicg-chatbox-messages')[0];
646
647            chatboxContent.style.height = contentHeight + 'px';
648            chatboxMessages.style.height = messagesHeight + 'px';
649
650            // Ensure we are scrolled to bottom for existing messages
651            chatboxMessages.scrollTop = chatboxMessages.scrollHeight;
652        }
653    }
654}
655function resolveDimension(value, totalSize) {
656    if (value.includes('%')) {
657        return parseFloat(value) / 100 * totalSize;
658    } else if (value.includes('px')) {
659        return parseFloat(value.replace('px', ''));
660    }
661    return parseFloat(value); // Default to parsing the value as pixels if no units are specified
662}
663
664function wpaicgChatInit() {
665    let wpaicgMicIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M176 0C123 0 80 43 80 96V256c0 53 43 96 96 96s96-43 96-96V96c0-53-43-96-96-96zM48 216c0-13.3-10.7-24-24-24s-24 10.7-24 24v40c0 89.1 66.2 162.7 152 174.4V464H104c-13.3 0-24 10.7-24 24s10.7 24 24 24h72 72c13.3 0 24-10.7 24-24s-10.7-24-24-24H200V430.4c85.8-11.7 152-85.3 152-174.4V216c0-13.3-10.7-24-24-24s-24 10.7-24 24v40c0 70.7-57.3 128-128 128s-128-57.3-128-128V216z"/></svg>';
666    let wpaicgStopIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm256-96a96 96 0 1 1 0 192 96 96 0 1 1 0-192zm0 224a128 128 0 1 0 0-256 128 128 0 1 0 0 256zm0-96a32 32 0 1 0 0-64 32 32 0 1 0 0 64z"/></svg>';
667    var wpaicgChatStream;
668    var wpaicgChatRec;
669    var wpaicgInput;
670    var wpaicgChatAudioContext = window.AudioContext || window['webkitAudioContext'];
671    var wpaicgaudioContext;
672    var wpaicgMicBtns = document.querySelectorAll('.wpaicg-mic-icon');
673    var wpaicgChatTyping = document.querySelectorAll('.wpaicg-chatbox-typing');
674    var wpaicgShortcodeTyping = document.querySelectorAll('.wpaicg-chat-shortcode-typing');
675    var wpaicgChatSend = document.querySelectorAll('.wpaicg-chatbox-send');
676    var wpaicgShortcodeSend = document.querySelectorAll('.wpaicg-chat-shortcode-send');
677    var wpaicgChatFullScreens = document.getElementsByClassName('wpaicg-chatbox-fullscreen');
678    var wpaicgChatCloseButtons = document.getElementsByClassName('wpaicg-chatbox-close-btn');
679    var wpaicgChatDownloadButtons = document.getElementsByClassName('wpaicg-chatbox-download-btn');
680    var wpaicg_chat_widget_toggles = document.getElementsByClassName('wpaicg_toggle');
681    var wpaicg_chat_widgets = document.getElementsByClassName('wpaicg_chat_widget');
682    var imageInputThumbnail = null; // Variable to store the image thumbnail
683    function setupConversationStarters() {
684        const starters = document.querySelectorAll('.wpaicg-conversation-starter');
685        starters.forEach(starter => {
686            starter.addEventListener('click', function () {
687                const messageText = starter.innerText || starter.textContent;
688                const chatContainer = starter.closest('.wpaicg-chat-shortcode') || starter.closest('.wpaicg-chatbox');
689                const type = chatContainer.classList.contains('wpaicg-chat-shortcode') ? 'shortcode' : 'widget';
690                const typingInput = type === 'shortcode' ? chatContainer.querySelector('.wpaicg-chat-shortcode-typing') : chatContainer.querySelector('.wpaicg-chatbox-typing');
691
692                typingInput.value = messageText;
693                wpaicgSendChatMessage(chatContainer, typingInput, type);
694
695                // Hide all starters
696                starters.forEach(starter => {
697                    starter.style.display = 'none';
698                });
699            });
700        });
701    }
702
703    function maybeShowLeadForm(chat, chatId) {
704        // Helper function to interpret truthy values
705        function isTruthy(value) {
706            if (value === null || value === undefined) return false;
707            return value === '1' || value.toLowerCase() === 'true';
708        }
709
710        // Get the necessary data attributes
711        let leadCollectionEnabled = isTruthy(chat.getAttribute('data-lead-collection'));
712        if (!leadCollectionEnabled) {
713            return;
714        }
715
716        // Check if form has already been shown
717        let leadFormShown = localStorage.getItem('wpaicg_lead_form_shown');
718        if (leadFormShown === '1') {
719            return;
720        }
721
722        // Get the enable fields
723        let enableLeadName = isTruthy(chat.getAttribute('data-enable-lead-name'));
724        let enableLeadEmail = isTruthy(chat.getAttribute('data-enable-lead-email'));
725        let enableLeadPhone = isTruthy(chat.getAttribute('data-enable-lead-phone'));
726
727        // Check if at least one field is enabled
728        if (!(enableLeadName || enableLeadEmail || enableLeadPhone)) {
729            return;
730        }
731
732        // Get other data attributes
733        let leadTitle = chat.getAttribute('data-lead-title') || 'Contact Information';
734        let leadNameLabel = chat.getAttribute('data-lead-name') || 'Name';
735        let leadEmailLabel = chat.getAttribute('data-lead-email') || 'Email';
736        let leadPhoneLabel = chat.getAttribute('data-lead-phone') || 'Phone';
737        let wpaicg_nonce = chat.getAttribute('data-nonce');
738        let aiBg = chat.getAttribute('data-ai-bg-color');
739        let fontSize = chat.getAttribute('data-fontsize');
740        let fontColor = chat.getAttribute('data-color');
741        let text_field_bgcolor = chat.getAttribute('data-bg_text_field');
742        let text_field_font_color = chat.getAttribute('data-bg_text_field_font_color');
743        let text_field_border_color = chat.getAttribute('data-bg_text_field_border_color');
744
745        // Now, construct the form in JavaScript
746        // Define the form HTML within a chat message
747        let formHtml = `
748        <li class="wpaicg-lead-form-message" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}">
749            <div class="wpaicg-lead-form-container">
750                <button class="wpaicg-lead-form-close" style="float:right;">&times;</button>
751                <h2>${leadTitle}</h2>
752                <form>
753        `;
754
755        if (enableLeadName) {
756            formHtml += `
757                <div class="wpaicg-lead-form-field">
758                    <label>${leadNameLabel}</label>
759                    <input type="text" name="lead_name"/>
760                </div>
761            `;
762        }
763
764        if (enableLeadEmail) {
765            formHtml += `
766                <div class="wpaicg-lead-form-field">
767                    <label>${leadEmailLabel}</label>
768                    <input type="email" name="lead_email" />
769                </div>
770            `;
771        }
772
773        if (enableLeadPhone) {
774            formHtml += `
775                <div class="wpaicg-lead-form-field">
776                    <label>${leadPhoneLabel}</label>
777                    <input type="tel" name="lead_phone"/>
778                </div>
779            `;
780        }
781
782        // Add error message container
783        formHtml += `
784                <div class="wpaicg-lead-form-error" style="color: red; display: none;"></div>
785        `;
786
787        formHtml += `
788                    <div class="svg-submit-button-container">
789                        <button type="submit" class="svg-submit-button">
790                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill=${fontColor} aria-hidden="true" width="24" height="24">
790<path d="M3.478 2.405a.75.75 0 00-.926.94l2.432 7.905H13.5a.75.75 0 010 1.5H4.984l-2.432 7.905a.75.75 0 00.926.94 60.519 60.519 0 0018.445-8.986.75.75 0 000-1.218A60.517 60.517 0 003.478 2.405z"></path></svg>
791                        </button>
792                    </div>
793                </form>
794            </div>
795        </li>
796        `;
797
798        // Append the form after the last AI message
799        let messagesList = chat.querySelector('.wpaicg-chatbox-messages') || chat.querySelector('.wpaicg-chat-shortcode-messages');
800        if (messagesList) {
801            messagesList.insertAdjacentHTML('beforeend', formHtml);
802        }
803
804        // Add CSS styles
805        let styles = `
806        .wpaicg-lead-form-message {
807            list-style: none;
808            padding: 10px;
809            margin-bottom: 10px;
810            position: relative;
811        }
812    
813        .wpaicg-lead-form-container {
814            display: block;
815        }
816    
817        .wpaicg-lead-form-container h2 {
818            margin-top: 0;
819            font-size: 1.2em;
820            margin-bottom: 10px;
821            color: ${fontColor};
822            padding-right: 40px;
823        }
824    
825        .wpaicg-lead-form-close {
826            position: absolute;
827            top: 10px;
828            right: 10px;
829            background: none;
830            border: none;
831            font-size: 18px;
832            cursor: pointer;
833            color: ${fontColor};
834        }
835    
836        .wpaicg-lead-form-field {
837            margin-bottom: 15px;
838        }
839    
840        .wpaicg-lead-form-field label {
841            display: block;
842            margin-bottom: 5px;
843        }
844    
845        .wpaicg-lead-form-field input {
846            color: ${text_field_font_color};
847            background-color: ${text_field_bgcolor};
848            width: 100%;
849            padding: 8px;
850            box-sizing: border-box;
851            border: 1px solid ${text_field_border_color};
852        }
853    
854        .svg-submit-button {
855            background-color: ${aiBg};
856            border: none;
857            cursor: pointer;
858            padding: 10px;
859            outline: none;
860        }
861
862        .svg-submit-button svg {
863            fill: ${fontColor}; /* Dynamic color */
864            transition: fill 0.3s ease;
865        }
866
867        /* Spinner inside button */
868        .wpaicg-lead-spinner {
869            display: inline-flex;
870            justify-content: center;
871            align-items: center;
872        }
873
874        .wpaicg-lead-spinner .dot {
875            font-size: 16px;
876            color: ${fontColor};
877            animation: jump 1s infinite;
878            margin: 0 2px;
879        }
880
881        /* Align submit button to the right */
882        .svg-submit-button-container {
883            text-align: right; /* This aligns the button to the right */
884        }
885
886        @keyframes jump {
887            0%, 100% {
888                transform: translateY(0);
889            }
890            50% {
891                transform: translateY(-6px);
892            }
893        }
894
895        `;
896
897        // Create a style element and append to the head
898        let styleSheet = document.createElement("style");
899        styleSheet.innerText = styles;
900        document.head.appendChild(styleSheet);
901
902        // Add event listeners for form submission and close button
903        let formMessage = messagesList.querySelector('.wpaicg-lead-form-message');
904        let closeButton = formMessage.querySelector('.wpaicg-lead-form-close');
905        let form = formMessage.querySelector('form');
906        let errorDiv = form.querySelector('.wpaicg-lead-form-error');
907
908        closeButton.addEventListener('click', function () {
909            // Hide the form
910            formMessage.remove();
911            // Set that form has been shown
912            localStorage.setItem('wpaicg_lead_form_shown', '1');
913        });
914
915        form.addEventListener('submit', function (event) {
916            event.preventDefault();
917
918            // Validate that at least one field is filled
919            let nameInput = form.querySelector('input[name="lead_name"]');
920            let emailInput = form.querySelector('input[name="lead_email"]');
921            let phoneInput = form.querySelector('input[name="lead_phone"]');
922
923            let nameValue = nameInput ? nameInput.value.trim() : '';
924            let emailValue = emailInput ? emailInput.value.trim() : '';
925            let phoneValue = phoneInput ? phoneInput.value.trim() : '';
926
927            // If all fields are empty, display an error message and stop submission
928            if (!nameValue && !emailValue && !phoneValue) {
929                errorDiv.textContent = 'Please fill in at least one field.';
930                errorDiv.style.display = 'block';
931                return; // Stop form submission
932            } else {
933                errorDiv.textContent = '';
934                errorDiv.style.display = 'none';
935            }
936
937            // Get the submit button
938            let submitButton = form.querySelector('.svg-submit-button');
939
940            // Replace the button content with the spinner (jumping dots)
941            submitButton.innerHTML = `
942                <div class="wpaicg-lead-spinner">
943                    <span class="dot">•</span>
944                    <span class="dot">•</span>
945                    <span class="dot">•</span>
946                </div>
947            `;
948            submitButton.disabled = true; // Disable the button while submitting
949
950            // Collect the data
951            let formData = new FormData(form);
952            formData.append('action', 'wpaicg_submit_lead');
953            formData.append('_wpnonce', wpaicg_nonce);
954            // Include chatId
955            formData.append('chatId', chatId);
956
957            // Send the data via AJAX
958            fetch(wpaicgParams.ajax_url, {
959                method: 'POST',
960                body: formData
961            }).then(response => response.json())
962                .then(data => {
963                    formMessage.remove();
964
965                    // Mark form as shown
966                    localStorage.setItem('wpaicg_lead_form_shown', '1');
967                }).catch(error => {
968                    console.error('Error submitting lead form:', error);
969                    formMessage.remove();
970                    localStorage.setItem('wpaicg_lead_form_shown', '1');
971                }).finally(() => {
972                    // Restore original SVG icon in the button and re-enable it
973                    submitButton.innerHTML = `
974                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="${fontColor}" class="bi bi-send" viewBox="0 0 16 16">
975                        <path d="M15.5 0.5a.5.5 0 0 0-.854-.353L.646 14.646a.5.5 0 0 0 .708.708L15.5 0.854A.5.5 0 0 0 15.5 0.5z"/>
976                        <path d="M6.646 15.646l8-8a.5.5 0 0 0-.708-.708l-8 8a.5.5 0 1 0 .708.708z"/>
977                        <path d="M4.5 3.5a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6z"/>
978                    </svg>
979                `;
980                    submitButton.disabled = false;
981                });
982        });
983    }
984
985    setupConversationStarters();
986
987    var wpaicgUserAudioEnabled = {}; // Object to track audio settings per chat instance
988
989    // Function to set up event listeners on audio buttons
990    function setupAudioButtons() {
991        var wpaicgAudioButtons = document.querySelectorAll('.wpaicg-chatbox-audio-btn');
992        wpaicgAudioButtons.forEach(button => {
993            var chatContainer = button.closest('.wpaicg-chat-shortcode, .wpaicg-chatbox');
994            var botId = chatContainer.getAttribute('data-bot-id') || '0';
995            var chatType = chatContainer.getAttribute('data-type') || 'shortcode';
996
997            // Create a unique key for each bot based on bot-id and chat-type
998            var audioKey = `audio_${chatType}_${botId}`;
999
1000            // Get muted state from data attribute (1 = muted, 0 or absent = unmuted)
1001            var voiceMutedByDefault = chatContainer.getAttribute('data-voice-muted-by-default') === '1';
1002
1003            // Initialize the audio button state based on the default mode
1004            if (voiceMutedByDefault) {
1005                // Audio is muted by default
1006                wpaicgUserAudioEnabled[audioKey] = false;
1007                button.classList.remove('wpaicg-audio-enabled');
1008                button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3zM425 167l55 55 55-55c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-55 55 55 55c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-55-55-55 55c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l55-55-55-55c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0z"/></svg>'; // Muted icon SVG
1009            } else {
1010                // Audio is not muted by default
1011                wpaicgUserAudioEnabled[audioKey] = true;
1012                button.classList.add('wpaicg-audio-enabled');
1013                button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M533.6 32.5C598.5 85.2 640 165.8 640 256s-41.5 170.7-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z"/></svg>'; // Unmuted icon SVG
1014            }
1015
1016            // Set up click listener to toggle audio state
1017            button.addEventListener('click', function () {
1018                wpaicgUserAudioEnabled[audioKey] = !wpaicgUserAudioEnabled[audioKey];
1019                if (wpaicgUserAudioEnabled[audioKey]) {
1020                    // Audio is now enabled
1021                    button.classList.add('wpaicg-audio-enabled');
1022                    button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M533.6 32.5C598.5 85.2 640 165.8 640 256s-41.5 170.7-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z"/></svg>'; // Unmuted icon SVG
1023                } else {
1024                    // Audio is now disabled
1025                    button.classList.remove('wpaicg-audio-enabled');
1026                    button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3zM425 167l55 55 55-55c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-55 55 55 55c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-55-55-55 55c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l55-55-55-55c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0z"/></svg>'; // Muted icon SVG
1027                }
1028            });
1029        });
1030    }
1031
1032    // Call the setup function
1033    setupAudioButtons();
1034
1035    var imageIcon = document.querySelector('.wpaicg-img-icon');
1036    var spinner = document.querySelector('.wpaicg-img-spinner');
1037    var thumbnailPlaceholder = document.querySelector('.wpaicg-thumbnail-placeholder');
1038
1039    if (imageIcon) {
1040        imageIcon.addEventListener('click', function () {
1041            var imageInput = document.getElementById('imageUpload');
1042            imageInput.click();
1043        });
1044    }
1045
1046    var imageInput = document.getElementById('imageUpload');
1047    if (imageInput) {
1048        imageInput.addEventListener('change', function () {
1049            if (this.files && this.files[0]) {
1050                var file = this.files[0];  // Store the file reference here
1051
1052                // Show the spinner and hide the image icon
1053                imageIcon.style.display = 'none';
1054                spinner.style.display = 'inline-block';
1055
1056                imageIcon.title = file.name; // Optional: show image name on hover
1057
1058                // Hide the spinner and show the image icon and thumbnail after a delay
1059                setTimeout(function () {
1060                    spinner.style.display = 'none';
1061                    imageIcon.style.display = 'inline-block'; // Re-display image icon
1062
1063                    // Now set the thumbnail image using the stored file reference
1064                    thumbnailPlaceholder.style.backgroundImage = `url(${URL.createObjectURL(file)})`;
1065                    thumbnailPlaceholder.style.backgroundSize = 'cover';
1066                    thumbnailPlaceholder.style.backgroundPosition = 'center';
1067                    thumbnailPlaceholder.style.backgroundRepeat = 'no-repeat';
1068                    thumbnailPlaceholder.style.display = 'inline-block'; // Display thumbnail
1069                }, 2000);
1070            }
1071        });
1072    }
1073
1074    // Function to set up event listeners on all clear chat buttons
1075    function setupClearChatButtons() {
1076        var wpaicgChatClearButtons = document.querySelectorAll('.wpaicg-chatbox-clear-btn');
1077        wpaicgChatClearButtons.forEach(button => {
1078            button.addEventListener('click', function () {
1079                var chatContainer = button.closest('[data-bot-id]'); // Finds the nearest parent with 'data-bot-id'
1080                if (chatContainer) {
1081                    var botId = chatContainer.getAttribute('data-bot-id') || '0';
1082                    var clientId = localStorage.getItem('wpaicg_chat_client_id');
1083                    clearChatHistory(botId, clientId, chatContainer);
1084                }
1085            });
1086        });
1087    }
1088
1089    // Function to clear the chat history from local storage and the display
1090    function clearChatHistory(botId, clientId, chatContainer) {
1091        var isCustomBot = botId !== '0';
1092        var type = chatContainer.classList.contains('wpaicg-chat-shortcode') ? 'shortcode' : 'widget'; // Determine the type based on class
1093        
1094        // First, get the active conversation key
1095        const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientId}`;
1096        const currentConversationKey = localStorage.getItem(activeConversationKey);
1097        
1098        if (currentConversationKey) {
1099            // Remove the active conversation from localStorage
1100            localStorage.removeItem(currentConversationKey);
1101            // Also remove its timestamp
1102            localStorage.removeItem(`${currentConversationKey}_timestamp`);
1103        } else {
1104            // Fallback if no active conversation: clear using base key
1105            var historyKey = isCustomBot
1106                ? `wpaicg_chat_history_custom_bot_${botId}_${clientId}`
1107                : `wpaicg_chat_history_${type}_${clientId}`; // Adjust history key based on type
1108            
1109            // Get all keys that start with this prefix
1110            for (let i = 0; i < localStorage.length; i++) {
1111                const key = localStorage.key(i);
1112                if (key && key.startsWith(historyKey)) {
1113                    localStorage.removeItem(key);
1114                }
1115            }
1116        }
1117    
1118        // Clear the chat display
1119        var chatBoxSelector = '.wpaicg-chatbox-messages, .wpaicg-chat-shortcode-messages'; // Generalized selector for both types
1120        var chatBox = chatContainer.querySelector(chatBoxSelector);
1121        if (chatBox) {
1122            chatBox.innerHTML = ''; // Clear the chat box visually
1123        }
1124    
1125        // delete wpaicg_lead_form_shown if exists
1126        localStorage.removeItem('wpaicg_lead_form_shown');
1127    
1128        // Check if wpaicg_thread_list exists in local storage (for assistant API)
1129        var threadList = localStorage.getItem('wpaicg_thread_list');
1130        if (threadList) {
1131            try {
1132                var threadListObj = JSON.parse(threadList);
1133    
1134                // Construct the key for the thread to be deleted
1135                var threadKey = isCustomBot
1136                    ? `custom_bot_${botId}_${clientId}`
1137                    : `${type}_${clientId}`;
1138                    
1139                // If we have a specific active thread key, clear only that one
1140                const activeThreadKey = `wpaicg_current_thread_${botId}_${clientId}`;
1141                const currentThreadKey = localStorage.getItem(activeThreadKey);
1142                
1143                if (currentThreadKey && threadListObj[currentThreadKey]) {
1144                    // Delete this specific thread
1145                    delete threadListObj[currentThreadKey];
1146                } else {
1147                    // Otherwise delete all threads matching the base prefix
1148                    for (let existingKey in threadListObj) {
1149                        if (existingKey.startsWith(threadKey)) {
1150                            delete threadListObj[existingKey];
1151                        }
1152                    }
1153                }
1154    
1155                // Update the local storage with the modified thread list
1156                localStorage.setItem('wpaicg_thread_list', JSON.stringify(threadListObj));
1157            } catch (error) {
1158                console.error('Error parsing wpaicg_thread_list:', error);
1159            }
1160        }
1161        
1162        // Show conversation starters after clearing
1163        showConversationStarters(chatContainer);
1164        
1165        // Refresh the conversation list in the sidebar
1166        loadConversationList();
1167    }
1168
1169    // Call this function once your DOM is fully loaded or at the end of your script
1170    setupClearChatButtons();
1171
1172
1173    if (wpaicg_chat_widget_toggles !== null && wpaicg_chat_widgets.length) {
1174        for (var i = 0; i < wpaicg_chat_widget_toggles.length; i++) {
1175            var wpaicg_chat_widget_toggle = wpaicg_chat_widget_toggles[i];
1176            var wpaicg_chat_widget = wpaicg_chat_widget_toggle.closest('.wpaicg_chat_widget');
1177            wpaicg_chat_widget_toggle.addEventListener('click', function (e) {
1178                e.preventDefault();
1179                wpaicg_chat_widget_toggle = e.currentTarget;
1180                
1181                if (wpaicg_chat_widget_toggle.classList.contains('wpaicg_widget_open')) {
1182                    wpaicg_chat_widget_toggle.classList.remove('wpaicg_widget_open');
1183                    wpaicg_chat_widget.classList.remove('wpaicg_widget_open');
1184                } else {
1185                    wpaicg_chat_widget.classList.add('wpaicg_widget_open');
1186                    wpaicg_chat_widget_toggle.classList.add('wpaicg_widget_open');
1187                    
1188                    // Check if it's a mobile device (width <= 767px)
1189                    if (window.innerWidth <= 767) {
1190                        // Get the widget content
1191                        var widgetContent = wpaicg_chat_widget.querySelector('.wpaicg_chat_widget_content');
1192                        // Set it to fullscreen for mobile
1193                        if (widgetContent && !widgetContent.classList.contains('wpaicg-fullscreened')) {
1194                            var fullscreenBtn = widgetContent.querySelector('.wpaicg-chatbox-fullscreen');
1195                            if (fullscreenBtn) {
1196                                // Trigger fullscreen mode
1197                                wpaicgFullScreen(fullscreenBtn);
1198                                
1199                                // Hide the fullscreen button on mobile
1200                                fullscreenBtn.style.display = 'none';
1201                            }
1202                        }
1203                    }
1204                }
1205            });
1206        }
1207    }
1208    if (wpaicgChatDownloadButtons.length) {
1209        for (var i = 0; i < wpaicgChatDownloadButtons.length; i++) {
1210            var wpaicgChatDownloadButton = wpaicgChatDownloadButtons[i];
1211            wpaicgChatDownloadButton.addEventListener('click', function (e) {
1212                wpaicgChatDownloadButton = e.currentTarget;
1213                var type = wpaicgChatDownloadButton.getAttribute('data-type');
1214                var wpaicgWidgetContent, listMessages;
1215                if (type === 'shortcode') {
1216                    wpaicgWidgetContent = wpaicgChatDownloadButton.closest('.wpaicg-chat-shortcode');
1217                    listMessages = wpaicgWidgetContent.getElementsByClassName('wpaicg-chat-shortcode-messages');
1218                }
1219                else {
1220                    wpaicgWidgetContent = wpaicgChatDownloadButton.closest('.wpaicg_chat_widget_content');
1221                    listMessages = wpaicgWidgetContent.getElementsByClassName('wpaicg-chatbox-messages');
1222                }
1223                if (listMessages.length) {
1224                    var listMessage = listMessages[0];
1225                    var messages = [];
1226                    var chatMessages = listMessage.getElementsByTagName('li');
1227                    if (chatMessages.length) {
1228                        for (var i = 0; i < chatMessages.length; i++) {
1229                            messages.push(chatMessages[i].innerText.replace("\n", ' '));
1230                        }
1231                    }
1232                    var messagesDownload = messages.join("\n");
1233                    var element = document.createElement('a');
1234                    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(messagesDownload));
1235                    element.setAttribute('download', 'chat.txt');
1236
1237                    element.style.display = 'none';
1238                    document.body.appendChild(element);
1239
1240                    element.click();
1241
1242                    document.body.removeChild(element);
1243                }
1244            })
1245        }
1246    }
1247    if (wpaicgChatCloseButtons.length) {
1248        for (var i = 0; i < wpaicgChatCloseButtons.length; i++) {
1249            var wpaicgChatCloseButton = wpaicgChatCloseButtons[i];
1250            wpaicgChatCloseButton.addEventListener('click', function (e) {
1251                wpaicgChatCloseButton = e.currentTarget;
1252                var wpaicgWidgetContent = wpaicgChatCloseButton.closest('.wpaicg_chat_widget_content');
1253                var chatbox = wpaicgWidgetContent.closest('.wpaicg_chat_widget');
1254                if (wpaicgWidgetContent.classList.contains('wpaicg-fullscreened')) {
1255                    var fullScreenBtn = wpaicgWidgetContent.getElementsByClassName('wpaicg-chatbox-fullscreen')[0];
1256                    wpaicgFullScreen(fullScreenBtn);
1257                }
1258                chatbox.getElementsByClassName('wpaicg_toggle')[0].click();
1259
1260            })
1261        }
1262    }
1263
1264    /**
1265     * Toggle fullscreen mode for either a shortcode chat or a widget chat.
1266     * Ensures that upon restore, the prior sizing/layout is fully restored,
1267     * including footer, text box, etc.
1268     *
1269     * @param {HTMLElement} btn - The clicked fullscreen button.
1270     */
1271    function wpaicgFullScreen(btn) {
1272        const type = btn.getAttribute('data-type');
1273        const isExitingFullscreen = btn.classList.contains('wpaicg-fullscreen-box');
1274        const isMobile = window.innerWidth <= 767;
1275    
1276        if (type === 'shortcode') {
1277            const wpaicgChatShortcode = btn.closest('.wpaicg-chat-shortcode');
1278            if (!wpaicgChatShortcode) return;
1279    
1280            // Exiting fullscreen
1281            if (isExitingFullscreen) {
1282                btn.classList.remove('wpaicg-fullscreen-box');
1283                wpaicgChatShortcode.classList.remove('wpaicg-fullscreened');
1284    
1285                // Restore old inline style
1286                const oldInlineStyle = wpaicgChatShortcode.getAttribute('data-old-inline-style') || '';
1287                wpaicgChatShortcode.setAttribute('style', oldInlineStyle);
1288                wpaicgChatShortcode.removeAttribute('data-old-inline-style');
1289    
1290                // Restore old data-width / data-height
1291                const oldDataWidth = wpaicgChatShortcode.getAttribute('data-old-width');
1292                const oldDataHeight = wpaicgChatShortcode.getAttribute('data-old-height');
1293                if (oldDataWidth !== null) {
1294                    wpaicgChatShortcode.setAttribute('data-width', oldDataWidth);
1295                    wpaicgChatShortcode.removeAttribute('data-old-width');
1296                }
1297                if (oldDataHeight !== null) {
1298                    wpaicgChatShortcode.setAttribute('data-height', oldDataHeight);
1299                    wpaicgChatShortcode.removeAttribute('data-old-height');
1300                }
1301    
1302                // Rerun sizing
1303                wpaicgChatShortcodeSize();
1304            }
1305            // Entering fullscreen
1306            else {
1307                btn.classList.add('wpaicg-fullscreen-box');
1308                wpaicgChatShortcode.classList.add('wpaicg-fullscreened');
1309    
1310                // Save existing style
1311                const currentInline = wpaicgChatShortcode.getAttribute('style') || '';
1312                wpaicgChatShortcode.setAttribute('data-old-inline-style', currentInline);
1313    
1314                // Save data-width / data-height
1315                const currentDataWidth = wpaicgChatShortcode.getAttribute('data-width') || '';
1316                const currentDataHeight = wpaicgChatShortcode.getAttribute('data-height') || '';
1317                wpaicgChatShortcode.setAttribute('data-old-width', currentDataWidth);
1318                wpaicgChatShortcode.setAttribute('data-old-height', currentDataHeight);
1319    
1320                // Apply fixed positioning for fullscreen
1321                wpaicgChatShortcode.style.position = 'fixed';
1322                wpaicgChatShortcode.style.top = '0';
1323                wpaicgChatShortcode.style.left = '0';
1324                wpaicgChatShortcode.style.zIndex = '999999999';
1325                wpaicgChatShortcode.style.width = '100%';
1326                // Use slight offset so the text area & footer are fully visible
1327                wpaicgChatShortcode.style.height = 'calc(100vh - 20px)';
1328                wpaicgChatShortcode.style.overflowY = 'auto';
1329    
1330                // Update data-width / data-height to reflect 100% fill
1331                wpaicgChatShortcode.setAttribute('data-width', '100%');
1332                // Use 100% but we do an inline "calc(100vh - 20px)" so that the JS dimensioning can handle subtracting
1333                wpaicgChatShortcode.setAttribute('data-height', '100%');
1334    
1335                // Re-run sizing
1336                wpaicgChatShortcodeSize();
1337            }
1338        }
1339        else {
1340            // For a widget
1341            const wpaicgWidgetContent = btn.closest('.wpaicg_chat_widget_content');
1342            if (!wpaicgWidgetContent) return;
1343            const chatbox = wpaicgWidgetContent.querySelector('.wpaicg-chatbox');
1344            if (!chatbox) return;
1345    
1346            // Exiting fullscreen
1347            if (isExitingFullscreen) {
1348                btn.classList.remove('wpaicg-fullscreen-box');
1349                wpaicgWidgetContent.classList.remove('wpaicg-fullscreened');
1350    
1351                // Don't restore original dimensions if on mobile
1352                if (!isMobile) {
1353                    // Restore old inline style for wpaicg_chat_widget_content
1354                    const oldWidgetStyle = wpaicgWidgetContent.getAttribute('data-old-inline-style') || '';
1355                    wpaicgWidgetContent.setAttribute('style', oldWidgetStyle);
1356                    wpaicgWidgetContent.removeAttribute('data-old-inline-style');
1357    
1358                    // Restore old inline style for chatbox
1359                    const oldChatboxStyle = chatbox.getAttribute('data-old-inline-style') || '';
1360                    chatbox.setAttribute('style', oldChatboxStyle);
1361                    chatbox.removeAttribute('data-old-inline-style');
1362    
1363                    // Restore data-width / data-height
1364                    const oldWidth = chatbox.getAttribute('data-old-width');
1365                    const oldHeight = chatbox.getAttribute('data-old-height');
1366                    if (oldWidth !== null) {
1367                        chatbox.setAttribute('data-width', oldWidth);
1368                        chatbox.removeAttribute('data-old-width');
1369                    }
1370                    if (oldHeight !== null) {
1371                        chatbox.setAttribute('data-height', oldHeight);
1372                        chatbox.removeAttribute('data-old-height');
1373                    }
1374                }
1375    
1376                // Re-run the widget sizing
1377                wpaicgChatBoxSize();
1378            }
1379            // Entering fullscreen
1380            else {
1381                btn.classList.add('wpaicg-fullscreen-box');
1382                wpaicgWidgetContent.classList.add('wpaicg-fullscreened');
1383    
1384                // Save wpaicg_chat_widget_content style
1385                const currentWidgetStyle = wpaicgWidgetContent.getAttribute('style') || '';
1386                wpaicgWidgetContent.setAttribute('data-old-inline-style', currentWidgetStyle);
1387    
1388                // Save chatbox style
1389                const currentChatboxStyle = chatbox.getAttribute('style') || '';
1390                chatbox.setAttribute('data-old-inline-style', currentChatboxStyle);
1391    
1392                // Also store data-width / data-height
1393                const currentDataWidth = chatbox.getAttribute('data-width') || '';
1394                const currentDataHeight = chatbox.getAttribute('data-height') || '';
1395                chatbox.setAttribute('data-old-width', currentDataWidth);
1396                chatbox.setAttribute('data-old-height', currentDataHeight);
1397    
1398                // Apply fullscreen styling with mobile-specific adjustments
1399                wpaicgWidgetContent.style.position = 'fixed';
1400                wpaicgWidgetContent.style.top = '0';
1401                wpaicgWidgetContent.style.left = '0';
1402                wpaicgWidgetContent.style.width = '100%';
1403                wpaicgWidgetContent.style.height = isMobile ? '100%' : 'calc(100vh - 20px)';
1404                wpaicgWidgetContent.style.zIndex = '999999999';
1405                wpaicgWidgetContent.style.overflowY = 'auto';
1406    
1407                // Expand chatbox to fill container
1408                chatbox.style.width = '100%';
1409                chatbox.style.height = isMobile ? '100%' : 'calc(100vh - 20px)';
1410                chatbox.style.overflowY = 'auto';
1411                
1412                // Set maximum sizes for mobile
1413                if (isMobile) {
1414                    chatbox.style.maxWidth = '100%';
1415                    chatbox.style.maxHeight = '100%';
1416                    wpaicgWidgetContent.style.maxWidth = '100%';
1417                    wpaicgWidgetContent.style.maxHeight = '100%';
1418                    
1419                    // Hide the fullscreen button on mobile devices when in fullscreen mode
1420                    btn.style.display = 'none';
1421                }
1422    
1423                // Update data attributes
1424                chatbox.setAttribute('data-width', '100%');
1425                chatbox.setAttribute('data-height', '100%');
1426    
1427                // Re-run widget sizing
1428                wpaicgChatBoxSize();
1429            }
1430        }
1431    }
1432    if (wpaicgChatFullScreens.length) {
1433        for (var i = 0; i < wpaicgChatFullScreens.length; i++) {
1434            var wpaicgChatFullScreen = wpaicgChatFullScreens[i];
1435            wpaicgChatFullScreen.addEventListener('click', function (e) {
1436                wpaicgFullScreen(e.currentTarget);
1437            })
1438        }
1439    }
1440    function resizeChatWidgets() {
1441        if (wpaicg_chat_widgets !== null && wpaicg_chat_widgets.length) {
1442            for (var i = 0; i < wpaicg_chat_widgets.length; i++) {
1443                var wpaicg_chat_widget = wpaicg_chat_widgets[i];
1444                if (window.innerWidth < 350) {
1445                    wpaicg_chat_widget.getElementsByClassName('wpaicg-chatbox')[0].style.width = window.innerWidth + 'px';
1446                    wpaicg_chat_widget.getElementsByClassName('wpaicg_chat_widget_content')[0].style.width = window.innerWidth + 'px';
1447                }
1448            }
1449        }
1450    }
1451    // Function to check if device is mobile based on width
1452    function isMobileDevice() {
1453        return window.innerWidth <= 767;
1454    }
1455
1456    // Add a resize listener to handle orientation changes
1457    window.addEventListener('resize', function () {
1458        wpaicgChatBoxSize();
1459        wpaicgChatShortcodeSize();
1460        resizeChatWidgets();
1461        
1462        // Check if device changed from mobile to desktop
1463        if (window.innerWidth > 767) {
1464            // Find all fullscreen buttons in fullscreened widgets
1465            document.querySelectorAll('.wpaicg-fullscreened .wpaicg-chatbox-fullscreen').forEach(btn => {
1466                // Make sure the button is visible on desktop
1467                btn.style.display = '';
1468            });
1469        } else {
1470            // Hide fullscreen button on mobile in fullscreen mode
1471            document.querySelectorAll('.wpaicg-fullscreened .wpaicg-chatbox-fullscreen').forEach(btn => {
1472                btn.style.display = 'none';
1473            });
1474        }
1475        
1476        // When orientation changes, ensure mobile fullscreen is maintained
1477        if (isMobileDevice()) {
1478            const openWidgets = document.querySelectorAll('.wpaicg_widget_open .wpaicg_chat_widget_content');
1479            openWidgets.forEach(widget => {
1480                if (!widget.classList.contains('wpaicg-fullscreened')) {
1481                    const fullscreenBtn = widget.querySelector('.wpaicg-chatbox-fullscreen');
1482                    if (fullscreenBtn) {
1483                        wpaicgFullScreen(fullscreenBtn);
1484                        // Hide the fullscreen button
1485                        fullscreenBtn.style.display = 'none';
1486                    }
1487                }
1488            });
1489        }
1490    });
1491    wpaicgChatShortcodeSize();
1492    wpaicgChatBoxSize();
1493    resizeChatWidgets();
1494
1495    function wpaicgescapeHtml(unsafe) {
1496        return unsafe
1497            .replace(/&/g, "&amp;")
1498            .replace(/</g, "&lt;")
1499            .replace(/>/g, "&gt;")
1500            .replace(/"/g, "&quot;")
1501            .replace(/'/g, "&#039;");
1502    }
1503
1504    function wpaicgstartChatRecording() {
1505        let constraints = { audio: true, video: false }
1506        navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
1507            wpaicgaudioContext = new wpaicgChatAudioContext();
1508            wpaicgChatStream = stream;
1509            wpaicgInput = wpaicgaudioContext.createMediaStreamSource(stream);
1510            wpaicgChatRec = new Recorder(wpaicgInput, { numChannels: 1 });
1511            wpaicgChatRec.record();
1512        })
1513    }
1514
1515    function wpaicgstopChatRecording(mic) {
1516        wpaicgChatRec.stop();
1517        wpaicgChatStream.getAudioTracks()[0].stop();
1518        wpaicgChatRec.exportWAV(function (blob) {
1519            let type = mic.getAttribute('data-type');
1520            let parentChat;
1521            let chatContent;
1522            let chatTyping;
1523            if (type === 'widget') {
1524                parentChat = mic.closest('.wpaicg-chatbox');
1525                chatContent = parentChat.querySelectorAll('.wpaicg-chatbox-content')[0];
1526                chatTyping = parentChat.querySelectorAll('.wpaicg-chatbox-typing')[0];
1527            } else {
1528                parentChat = mic.closest('.wpaicg-chat-shortcode');
1529                chatContent = parentChat.querySelectorAll('.wpaicg-chat-shortcode-content')[0];
1530                chatTyping = parentChat.querySelectorAll('.wpaicg-chat-shortcode-typing')[0];
1531            }
1532            wpaicgSendChatMessage(parentChat, chatTyping, type, blob);
1533        });
1534    }
1535
1536    // Function to generate a random string
1537    function generateRandomString(length) {
1538        let result = '';
1539        let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
1540        let charactersLength = characters.length;
1541        for (let i = 0; i < length; i++) {
1542            result += characters.charAt(Math.floor(Math.random() * charactersLength));
1543        }
1544        return result;
1545    }
1546
1547    function setupButtonListeners(isCopyEnabled, isFeedbackEnabled, class_ai_item, emptyClipboardSVG, checkedClipboardSVG, thumbsUpSVG, thumbsDownSVG, showFeedbackModal, aiBg, fontColor, usrBg, chat, wpaicg_nonce, chatbot_identity) {
1548        let hideTimeout;
1549
1550        // Show buttons on hover or touchstart of the icon container
1551        jQuery(document).on('mouseenter touchstart', `li.${class_ai_item} .wpaicg-icon-container`, function (event) {
1552            clearTimeout(hideTimeout);
1553
1554            // Prevent triggering on scroll or unintended touches
1555            if (event.type === 'touchstart') {
1556                event.stopPropagation();
1557            }
1558
1559            const buttons = jQuery(this).find('.wpaicg-copy-button, .wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button');
1560
1561            // Display copy button if enabled
1562            if (isCopyEnabled) {
1563                buttons.filter('.wpaicg-copy-button').css({
1564                    display: 'inline-block',
1565                    opacity: 1,
1566                    visibility: 'visible'
1567                });
1568            }
1569
1570            // Display feedback buttons if enabled
1571            if (isFeedbackEnabled) {
1572                buttons.filter('.wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button').css({
1573                    display: 'inline-block',
1574                    opacity: 1,
1575                    visibility: 'visible'
1576                });
1577            }
1578        });
1579
1580        // Hide buttons after leaving the icon container
1581        jQuery(document).on('mouseleave touchend', `li.${class_ai_item} .wpaicg-icon-container`, function () {
1582            const buttons = jQuery(this).find('.wpaicg-copy-button, .wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button');
1583            hideTimeout = setTimeout(() => {
1584                buttons.css({
1585                    opacity: 0,
1586                    visibility: 'hidden',
1587                    display: 'none'
1588                });
1589            }, 2000);
1590        });
1591
1592        // Copy text functionality remains unchanged
1593        jQuery(document).on('click', '.wpaicg-copy-button', function () {
1594            const chatId = jQuery(this).data('chat-id');
1595            const messageText = document.getElementById(chatId).innerText;
1596
1597            navigator.clipboard.writeText(messageText).then(() => {
1598                // Change icon to check mark
1599                jQuery(this).html(checkedClipboardSVG);
1600                setTimeout(() => {
1601                    // Reset icon to original after 2 seconds
1602                    jQuery(this).html(emptyClipboardSVG);
1603                }, 2000);
1604            }).catch(err => console.error('Failed to copy text: ', err));
1605        });
1606
1607        // Feedback functionality remains unchanged
1608        jQuery(document).on('click', '.wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button', function () {
1609            const feedbackType = jQuery(this).hasClass('wpaicg-thumbs-up-button') ? 'up' : 'down';
1610            const chatId = jQuery(this).data('chat-id').replace('wpaicg-chat-message-', '');
1611            showFeedbackModal(feedbackType, chatId, aiBg, fontColor, usrBg, chat, wpaicg_nonce, chatbot_identity);
1612        });
1613    }
1614
1615    function showFeedbackModal(feedbackType, chatId, bgColor, textColor, usrBg, chat, wpaicg_nonce, chatbot_identity) {
1616        const chatWidget = jQuery('.wpaicg_chat_widget');
1617        const feedbackTitle = chat.getAttribute('data-feedback_title') || 'Feedback';
1618        const feedbackMessage = chat.getAttribute('data-feedback_message') || 'Please provide details: (optional)';
1619        const feedbackSuccessMessage = chat.getAttribute('data-feedback_success') || 'Thank you for your feedback!';
1620
1621        const chatShortcode = jQuery(chat).closest('.wpaicg-chat-shortcode');
1622        const wasFullscreen = chatShortcode.hasClass('wpaicg-fullscreened');
1623
1624        if (wasFullscreen) {
1625            // Exit fullscreen mode before showing feedback modal
1626            const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen');
1627            wpaicgFullScreen(fullScreenBtn[0]); // Exit fullscreen
1628        }
1629
1630        if (chatWidget.hasClass('wpaicg_widget_open')) {
1631            chatWidget.data('was-open', true);
1632            chatWidget.removeClass('wpaicg_widget_open');
1633        } else {
1634            chatWidget.data('was-open', false);
1635        }
1636
1637        const modalHtml = ` 
1638            <style>
1639                @keyframes wpaicg-feedback-spin {
1640                    0% { transform: rotate(0deg); }
1641                    100% { transform: rotate(360deg); }
1642                }
1643        
1644                .wpaicg-feedback-spinner {
1645                    display: inline-block;
1646                    width: 16px;
1647                    height: 16px;
1648                    border: 2px solid ${textColor};
1649                    border-top: 2px solid ${bgColor};
1650                    border-radius: 50%;
1651                    animation: wpaicg-feedback-spin 1s linear infinite;
1652                }
1653                .wpaicg-feedback-message {
1654                    color: ${textColor};
1655                }
1656            </style>
1657            <div class="wpaicg-feedback-modal-overlay">
1658                <div class="wpaicg-feedback-modal" style="background-color:${bgColor};color:${textColor};position:relative;">
1659                    <button class="wpaicg-feedback-modal-close" style="position:absolute; top:10px; right:10px; background:none; border:none;
1659 color:${textColor}; font-size:18px; cursor:pointer;">&times;</button>
1660                    <h2 style="background-color:${bgColor};color:${textColor};">${feedbackTitle}</h2>
1661                    <p>${feedbackMessage}</p>
1662                    <textarea class="wpaicg-feedback-textarea"></textarea>
1663                    <div class="wpaicg-feedback-modal-buttons">
1664                        <div class="wpaicg-feedback-message" style="display:none;"></div>
1665                        <button class="wpaicg-feedback-modal-submit" style="background-color:${usrBg};color:${textColor};border:none;" data-feedback-type="${feedbackType}" data-chat-id="${chatId}">
1666                            Submit
1667                            <span class="wpaicg-feedback-spinner" style="display:none; margin-left:5px; border: 2px solid ${textColor}; border-top: 2px solid ${bgColor}; border-radius: 50%; width: 16px; height: 16px; animation: wpaicg-feedback-spin 1s linear infinite;"></span>
1668                        </button>
1669                    </div>
1670                </div>
1671            </div>
1672        `;
1673
1674        jQuery('body').append(modalHtml);
1675
1676        jQuery('.wpaicg-feedback-modal-close').on('click', function () {
1677            jQuery('.wpaicg-feedback-modal-overlay').fadeOut(300, function () {
1678                jQuery(this).remove();
1679                if (wasFullscreen) {
1680                    // Restore fullscreen after feedback modal is closed
1681                    const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen');
1682                    wpaicgFullScreen(fullScreenBtn[0]); // Re-enter fullscreen
1683                }
1684                if (chatWidget.data('was-open')) {
1685                    chatWidget.addClass('wpaicg_widget_open');
1686                }
1687            });
1688        });
1689
1690        jQuery('.wpaicg-feedback-modal-submit').on('click', function () {
1691            const modal = jQuery(this).closest('.wpaicg-feedback-modal');
1692            const feedbackText = modal.find('.wpaicg-feedback-textarea').val();
1693            const feedbackType = jQuery(this).data('feedback-type');
1694            const chatId = jQuery(this).data('chat-id');
1695            const nonce = wpaicg_nonce;
1696            const submitButton = jQuery(this);
1697            const spinner = submitButton.find('.wpaicg-feedback-spinner');
1698            const feedbackMessageElement = modal.find('.wpaicg-feedback-message');
1699
1700            spinner.show();
1701            submitButton.prop('disabled', true);
1702
1703            jQuery.ajax({
1704                url: wpaicgParams.ajax_url,
1705                method: 'POST',
1706                data: {
1707                    action: 'wpaicg_submit_feedback',
1708                    chatId: chatId,
1709                    feedbackType: feedbackType,
1710                    feedbackDetails: feedbackText,
1711                    _wpnonce: nonce,
1712                    chatbot_id: chatbot_identity,
1713                },
1714                success: function (response) {
1715                    feedbackMessageElement.html(`<span style="color:${textColor};">${feedbackSuccessMessage}</span>`).fadeIn(300);
1716                    setTimeout(() => {
1717                        jQuery('.wpaicg-feedback-modal-overlay').fadeOut(300, function () {
1718                            jQuery(this).remove();
1719                            if (chatWidget.data('was-open')) {
1720                                chatWidget.addClass('wpaicg_widget_open');
1721                            }
1722                            if (wasFullscreen) {
1723                                const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen');
1724                                wpaicgFullScreen(fullScreenBtn[0]); // Re-enter fullscreen
1725                            }
1726                        });
1727                    }, 2000);
1728                },
1729                error: function (error) {
1730                    feedbackMessageElement.html(`<span style="color:${textColor};">Error. Please try again later.</span>`).fadeIn(300);
1731                    setTimeout(() => {
1732                        jQuery('.wpaicg-feedback-modal-overlay').fadeOut(300, function () {
1733                            jQuery(this).remove();
1734                            if (chatWidget.data('was-open')) {
1735                                chatWidget.addClass('wpaicg_widget_open');
1736                            }
1737                            if (wasFullscreen) {
1738                                const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen');
1739                                wpaicgFullScreen(fullScreenBtn[0]); // Re-enter fullscreen
1740                            }
1741                        });
1742                    }, 2000);
1743                },
1744                complete: function () {
1745                    spinner.hide();
1746                    submitButton.prop('disabled', false);
1747                }
1748            });
1749        });
1750    }
1751
1752    /**
1753     * Sends a user message (text or audio) to the server, handles response streaming or non-streaming,
1754     * and updates the conversation history in localStorage.
1755     *
1756     * @param {HTMLElement} chat - The parent chat container (wpaicg-chat-shortcode or wpaicg-chatbox).
1757     * @param {HTMLTextAreaElement} typing - The textarea input element.
1758     * @param {string} type - Either "shortcode" or "widget", indicating chat type.
1759     * @param {Blob} [blob] - Optional audio blob if the user recorded microphone input.
1760     */
1761    function wpaicgSendChatMessage(chat, typing, type, blob) {
1762        hideConversationStarters();
1763        // Remove the lead form if it exists
1764        var leadFormMessage = chat.querySelector('.wpaicg-lead-form-message');
1765        if (leadFormMessage) {
1766            leadFormMessage.remove();
1767            // Optionally, set 'wpaicg_lead_form_shown' to '1' so it doesn't show again
1768            localStorage.setItem('wpaicg_lead_form_shown', '1');
1769        }
1770        let botIdAudio = chat.getAttribute('data-bot-id') || '0';
1771        let chatTypeAudio = chat.getAttribute('data-type') || 'shortcode';
1772        let audioKey = `audio_${chatTypeAudio}_${botIdAudio}`; // Use the new key for audio state
1773
1774        let isAudioEnabledByUser = wpaicgUserAudioEnabled[audioKey]; // Get the audio state for this bot instance
1775        let userVoiceControl = chat.getAttribute('data-user-voice-control');
1776        let wpaicg_box_typing = typing;
1777        let wpaicg_ai_thinking, wpaicg_messages_box, class_user_item, class_ai_item;
1778        let wpaicgMessage = '';
1779        let wpaicgData = new FormData();
1780        let wpaicg_nonce = chat.getAttribute('data-nonce');
1781        let wpaicg_bot_id = parseInt(chat.getAttribute('data-bot-id'));
1782        let wpaicg_user_bg = chat.getAttribute('data-user-bg-color');
1783        let wpaicg_font_size = chat.getAttribute('data-fontsize');
1784        let wpaicg_speech = chat.getAttribute('data-speech');
1785        let wpaicg_voice = chat.getAttribute('data-voice');
1786        let elevenlabs_model = chat.getAttribute('data-elevenlabs-model');
1787        if (elevenlabs_model === null || elevenlabs_model === undefined) {
1788            elevenlabs_model = chat.getAttribute('data-elevenlabs_model');
1789        }
1790        let elevenlabs_voice = chat.getAttribute('data-elevenlabs-voice');
1791        if (elevenlabs_voice === null || elevenlabs_voice === undefined) {
1792            elevenlabs_voice = chat.getAttribute('data-elevenlabs_voice');
1793        }
1794        let wpaicg_voice_error = chat.getAttribute('data-voice-error');
1795        let wpaicg_typewriter_effect = chat.getAttribute('data-typewriter-effect');
1796        let wpaicg_typewriter_speed = chat.getAttribute('data-typewriter-speed');
1797
1798        let url = chat.getAttribute('data-url');
1799        let post_id = chat.getAttribute('data-post-id');
1800        let wpaicg_ai_bg = chat.getAttribute('data-ai-bg-color');
1801        let wpaicg_font_color = chat.getAttribute('data-color');
1802        let voice_service = chat.getAttribute('data-voice_service');
1803
1804        let voice_language = chat.getAttribute('data-voice_language');
1805        let voice_name = chat.getAttribute('data-voice_name');
1806        let voice_device = chat.getAttribute('data-voice_device');
1807        let openai_model = chat.getAttribute('data-openai_model');
1808
1809        let openai_voice = chat.getAttribute('data-openai_voice');
1810
1811        let openai_output_format = chat.getAttribute('data-openai_output_format');
1812
1813        let openai_voice_speed = chat.getAttribute('data-openai_voice_speed');
1814
1815        let openai_stream_nav = chat.getAttribute('data-openai_stream_nav');
1816
1817        let voice_speed = chat.getAttribute('data-voice_speed');
1818        let voice_pitch = chat.getAttribute('data-voice_pitch');
1819        var chat_pdf = chat.getAttribute('data-pdf');
1820
1821        // Handle image upload
1822        var imageInput = document.getElementById('imageUpload');
1823        var imageUrl = ''; // Variable to store the URL of the uploaded image for preview
1824        if (imageInput) {
1825            if (imageInput.files && imageInput.files[0]) {
1826                var validImageTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp', 'image/gif'];
1827                if (!validImageTypes.includes(imageInput.files[0].type)) {
1828                    alert('Invalid file type. Only PNG, JPEG, WEBP, and non-animated GIF images are allowed.');
1829                    return;
1830                }
1831                // Append image file to FormData object
1832                wpaicgData.append('image', imageInput.files[0], imageInput.files[0].name);
1833                // Create a URL for the uploaded image file for preview
1834                imageUrl = URL.createObjectURL(imageInput.files[0]);
1835            }
1836        }
1837
1838        if (type === 'widget') {
1839            wpaicg_ai_thinking = chat.getElementsByClassName('wpaicg-bot-thinking')[0];
1840            wpaicg_messages_box = chat.getElementsByClassName('wpaicg-chatbox-messages')[0];
1841            class_user_item = 'wpaicg-chat-user-message';
1842            class_ai_item = 'wpaicg-chat-ai-message';
1843            wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
1844            // Retrieve all message elements
1845            const messages = wpaicg_messages_box.querySelectorAll('li');
1846            // Ensure messages exist and scroll to the last message
1847            if (messages.length > 0) {
1848                messages[messages.length - 1].scrollIntoView();
1849            }
1850
1851        } else {
1852            wpaicg_ai_thinking = chat.getElementsByClassName('wpaicg-bot-thinking')[0];
1853            wpaicg_messages_box = chat.getElementsByClassName('wpaicg-chat-shortcode-messages')[0];
1854            class_user_item = 'wpaicg-user-message';
1855            class_ai_item = 'wpaicg-ai-message';
1856        }
1857        wpaicg_ai_thinking.style.display = 'block';
1858        let wpaicg_question = wpaicgescapeHtml(wpaicg_box_typing.value);
1859        if (!wpaicg_question.trim() && blob === undefined) {
1860            wpaicg_ai_thinking.style.display = 'none';
1861            return; // Exit the function if no message or blob is provided
1862        }
1863        wpaicgMessage += '<li class="' + class_user_item + '" style="background-color:' + wpaicg_user_bg + ';font-size: ' + wpaicg_font_size + 'px;color: ' + wpaicg_font_color + '">';
1864        wpaicgData.append('_wpnonce', wpaicg_nonce);
1865        wpaicgData.append('post_id', post_id);
1866        if (chat_pdf && chat_pdf !== null) {
1867            wpaicgData.append('namespace', chat_pdf);
1868        }
1869        wpaicgData.append('url', url);
1870        if (type === 'widget') {
1871            wpaicgData.append('action', 'wpaicg_chatbox_message');
1872        } else {
1873            wpaicgData.append('action', 'wpaicg_chat_shortcode_message');
1874        }
1875        if (blob !== undefined) {
1876            let url = URL.createObjectURL(blob);
1877            wpaicgMessage += '<audio src="' + url + '" controls="true"></audio>';
1878            wpaicgData.append('audio', blob, 'wpaicg-chat-recording.wav');
1879        } else if (wpaicg_question !== '') {
1880            wpaicgData.append('message', wpaicg_question);
1881            wpaicgMessage += wpaicg_question.replace(/\n/g, '<br>');
1882
1883        }
1884
1885        wpaicgData.append('bot_id', wpaicg_bot_id);
1886        wpaicgMessage += '</li>';
1887        // If an image URL is available, add an <img> tag to display the image
1888        if (imageUrl !== '') {
1889            wpaicgMessage += '<li class="' + class_user_item + '" style="background-color:' + wpaicg_user_bg + ';font-size: ' + wpaicg_font_size + 'px;color: ' + wpaicg_font_color + '">';
1890            wpaicgMessage += '<div style="max-width: 300px; height: auto; display: flex;">';
1891            wpaicgMessage += '<img src="' + imageUrl + '" style="max-width: 100%; height: auto;" onload="this.parentElement.parentElement.parentElement.scrollTop = this.parentElement.parentElement.parentElement.scrollHeight;">';
1892            wpaicgMessage += '</div>'; // Closing the div tag
1893            wpaicgMessage += '</li>';
1894        }
1895        wpaicg_messages_box.innerHTML += wpaicgMessage;
1896        wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
1897
1898        // Hide the thumbnail placeholder
1899        var thumbnailPlaceholder = document.querySelector('.wpaicg-thumbnail-placeholder');
1900        if (thumbnailPlaceholder) {
1901            thumbnailPlaceholder.style.display = 'none'; // Hide the thumbnail after message is sent
1902        }
1903
1904        // Reset the image input after sending the message if imageInput exists first
1905        if (imageInput) {
1906            imageInput.value = '';
1907        }
1908
1909        let chat_type = chat.getAttribute('data-type');
1910
1911        let stream_nav;
1912        let chatbot_identity;
1913
1914        // Check if it's a bot with dynamic ID
1915        if (wpaicg_bot_id && wpaicg_bot_id !== "0") {
1916            stream_nav = openai_stream_nav;
1917            chatbot_identity = 'custom_bot_' + wpaicg_bot_id;
1918        } else {
1919            // Check if it's a shortcode or widget based on chat_type
1920            if (chat_type === "shortcode") {
1921                stream_nav = chat.getAttribute('data-openai_stream_nav');
1922                chatbot_identity = 'shortcode';
1923            } else if (chat_type === "widget") {
1924                stream_nav = chat.getAttribute('data-openai_stream_nav');
1925                chatbot_identity = 'widget';
1926            }
1927        }
1928        wpaicgData.append('chatbot_identity', chatbot_identity);
1929
1930        // Check for existing client_id in localStorage
1931        let clientID = localStorage.getItem('wpaicg_chat_client_id');
1932        if (!clientID) {
1933            // Generate and store a new client ID if not found
1934            clientID = generateRandomString(10); // Generate a 10 character string
1935            localStorage.setItem('wpaicg_chat_client_id', clientID);
1936        }
1937
1938        let botId = chat.getAttribute('data-bot-id') || '0';
1939        let assistantEnabled = chat.getAttribute('data-assistant-enabled') === 'true';
1940
1941        // Include local conversation if any
1942        let botIdAttr = botId || '0';
1943        let activeConversationKey = `wpaicg_current_conversation_${botIdAttr}_${clientID}`;
1944        let conversationKey = localStorage.getItem(activeConversationKey);
1945        // If there's no active conversation yet, fallback to a default
1946        if (!conversationKey) {
1947            // e.g. wpaicg_chat_history_custom_bot_4367_{clientID}_1
1948            let basePrefix = botIdAttr !== '0'
1949                ? `wpaicg_chat_history_custom_bot_${botIdAttr}_${clientID}`
1950                : `wpaicg_chat_history_${type}_${clientID}`;
1951            // Create the default index = 1 if not set
1952            let newKey = `${basePrefix}_1`;
1953            if (!localStorage.getItem(newKey)) {
1954                localStorage.setItem(newKey, JSON.stringify([]));
1955            }
1956            localStorage.setItem(activeConversationKey, newKey);
1957            conversationKey = newKey;
1958        }
1959        // Add the conversation history to form data
1960        let storedHistory = localStorage.getItem(conversationKey) || '[]';
1961        wpaicgData.append('wpaicg_chat_history', storedHistory);
1962
1963        //append client_id to wpaicgData
1964        wpaicgData.append('wpaicg_chat_client_id', clientID);
1965
1966        // --------------- Assistant-Enabled logic for thread ---------------
1967        let activeThreadKey = `wpaicg_current_thread_${botIdAttr}_${clientID}`;
1968        let currentThreadStorageKey = null;
1969        if (assistantEnabled) {
1970            // We store or retrieve the "active thread" from localStorage
1971            currentThreadStorageKey = localStorage.getItem(activeThreadKey);
1972            if (!currentThreadStorageKey) {
1973                // If user never clicked "New Chat," fallback to default index=1
1974                let baseThreadPrefix = botIdAttr !== '0'
1975                    ? `custom_bot_${botIdAttr}_${clientID}`
1976                    : `${type}_${clientID}`;
1977                let foundMaxThreadIndex = 0;
1978                const threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
1979                for (let existingKey in threadListObj) {
1980                    if (existingKey.startsWith(baseThreadPrefix)) {
1981                        let suffixMatch = existingKey.match(/_(\d+)$/);
1982                        if (suffixMatch) {
1983                            let indexNum = parseInt(suffixMatch[1], 10);
1984                            if (indexNum > foundMaxThreadIndex) foundMaxThreadIndex = indexNum;
1985                        }
1986                    }
1987                }
1988                let defaultThreadIndex = foundMaxThreadIndex + 1;
1989                currentThreadStorageKey = `${baseThreadPrefix}_${defaultThreadIndex}`;
1990                localStorage.setItem(activeThreadKey, currentThreadStorageKey);
1991            }
1992            // If there's a real thread ID in localStorage for that key, we pass it
1993            let threadListData = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
1994            let existingThreadId = threadListData[currentThreadStorageKey];
1995            if (existingThreadId) {
1996                wpaicgData.append('thread_id', existingThreadId);
1997            }
1998        }
1999
2000        // Hide the image thumbnail after sending the message
2001        if (imageInputThumbnail) {
2002            imageInputThumbnail.style.display = 'none';
2003        }
2004
2005        if (stream_nav === "1") {
2006            // Update local storage
2007            updateChatHistory(wpaicg_question, 'user', wpaicg_randomnum, chat, chatbot_identity, clientID);
2008
2009            if (assistantEnabled) {
2010                // Call the new assistant streaming function
2011                handleAssistantStreaming(
2012                    wpaicgData,
2013                    wpaicg_messages_box,
2014                    wpaicg_box_typing,
2015                    wpaicg_ai_thinking,
2016                    class_ai_item,
2017                    chat,
2018                    chatbot_identity,
2019                    clientID,
2020                    wpaicg_nonce
2021                );
2022            } else {
2023                handleStreaming(wpaicgData, wpaicg_messages_box, wpaicg_box_typing, wpaicg_ai_thinking, class_ai_item, chat, chatbot_identity, clientID, wpaicg_nonce);
2024            }
2025        }
2026        else {
2027            updateChatHistory(wpaicg_question, 'user', wpaicg_randomnum, chat, chatbot_identity, clientID);
2028
2029            // We also pass a fresh random ID for chat message
2030            var wpaicg_randomnum = Math.floor((Math.random() * 100000) + 1);
2031            const chatId = `wpaicg-chat-message-${wpaicg_randomnum}`;
2032            // Extract copy and feedback settings from the chat element
2033            const copyEnabled = chat.getAttribute('data-copy_btn') === "1";
2034            const feedbackEnabled = chat.getAttribute('data-feedback_btn') === "1";
2035            const fontColor = chat.getAttribute('data-color');
2036            const usrBg = chat.getAttribute('data-user-bg-color');
2037            const emptyClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-copy" viewBox="0 0 16 16">
2038            <path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/>
2039            </svg>`;
2040            const checkedClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-check2" viewBox="0 0 16 16">
2041            <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0"/>
2042            </svg>`;
2043
2044            const thumbsUpSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">
2045            <path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2 2 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a10 10 0 0 0-.443.05 9.4 9.4 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a9 9 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.2 2.2 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.9.9 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>
2046            </svg>`;
2047            const thumbsDownSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}
2047" class="bi bi-hand-thumbs-down" viewBox="0 0 16 16">
2048            <path d="M8.864 15.674c-.956.24-1.843-.484-1.908-1.42-.072-1.05-.23-2.015-.428-2.59-.125-.36-.479-1.012-1.04-1.638-.557-.624-1.282-1.179-2.131-1.41C2.685 8.432 2 7.85 2 7V3c0-.845.682-1.464 1.448-1.546 1.07-.113 1.564-.415 2.068-.723l.048-.029c.272-.166.578-.349.97-.484C6.931.08 7.395 0 8 0h3.5c.937 0 1.599.478 1.934 1.064.164.287.254.607.254.913 0 .152-.023.312-.077.464.201.262.38.577.488.9.11.33.172.762.004 1.15.069.13.12.268.159.403.077.27.113.567.113.856s-.036.586-.113.856c-.035.12-.08.244-.138.363.394.571.418 1.2.234 1.733-.206.592-.682 1.1-1.2 1.272-.847.283-1.803.276-2.516.211a10 10 0 0 1-.443-.05 9.36 9.36 0 0 1-.062 4.51c-.138.508-.55.848-1.012.964zM11.5 1H8c-.51 0-.863.068-1.14.163-.281.097-.506.229-.776.393l-.04.025c-.555.338-1.198.73-2.49.868-.333.035-.554.29-.554.55V7c0 .255.226.543.62.65 1.095.3 1.977.997 2.614 1.709.635.71 1.064 1.475 1.238 1.977.243.7.407 1.768.482 2.85.025.362.36.595.667.518l.262-.065c.16-.04.258-.144.288-.255a8.34 8.34 0 0 0-.145-4.726.5.5 0 0 1 .595-.643h.003l.014.004.058.013a9 9 0 0 0 1.036.157c.663.06 1.457.054 2.11-.163.175-.059.45-.301.57-.651.107-.308.087-.67-.266-1.021L12.793 7l.353-.354c.043-.042.105-.14.154-.315.048-.167.075-.37.075-.581s-.027-.414-.075-.581c-.05-.174-.111-.273-.154-.315l-.353-.354.353-.354c.047-.047.109-.176.005-.488a2.2 2.2 0 0 0-.505-.804l-.353-.354.353-.354c.006-.005.041-.05.041-.17a.9.9 0 0 0-.121-.415C12.4 1.272 12.063 1 11.5 1"/>
2049            </svg>`;
2050            const xhttp = new XMLHttpRequest();
2051            wpaicg_box_typing.value = '';
2052            xhttp.open('POST', wpaicgParams.ajax_url, true);
2053            xhttp.send(wpaicgData);
2054            xhttp.onreadystatechange = function (oEvent) {
2055                if (xhttp.readyState === 4) {
2056                    var wpaicg_message = '';
2057                    var wpaicg_response_text = '';
2058                    if (xhttp.status === 200) {
2059                        var wpaicg_response = this.responseText;
2060                        if (wpaicg_response !== '') {
2061                            wpaicg_response = JSON.parse(wpaicg_response);
2062                            wpaicg_ai_thinking.style.display = 'none'
2063                            if (wpaicg_response.status === 'success') {
2064                                // If assistantEnabled and the response contains thread_id, store it:
2065                                if (assistantEnabled && wpaicg_response.thread_id) {
2066                                    let threadList = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
2067                                    threadList[currentThreadStorageKey] = wpaicg_response.thread_id;
2068                                    localStorage.setItem('wpaicg_thread_list', JSON.stringify(threadList));
2069                                }
2070                                wpaicg_response_text = wpaicg_response.data;
2071                                wpaicg_message = `
2072                                    <li class="${class_ai_item} wpaicg-icon-container" style="background-color:${wpaicg_ai_bg};font-size:${wpaicg_font_size}px;color:${wpaicg_font_color}">
2073                                        <p style="width:100%">
2074                                            <span class="wpaicg-chat-message" id="${chatId}">${wpaicg_response_text}</span>
2075                                            ${copyEnabled ? `<button class="wpaicg-copy-button" data-chat-id="${chatId}">${emptyClipboardSVG}</button>` : ''}
2076                                            ${feedbackEnabled ? `
2077                                                <button class="wpaicg-thumbs-up-button" data-chat-id="${chatId}">${thumbsUpSVG}</button>
2078                                                <button class="wpaicg-thumbs-down-button" data-chat-id="${chatId}">${thumbsDownSVG}</button>` : ''}
2079                                        </p>
2080                                    </li>
2081                                `;
2082                            } else {
2083                                wpaicg_response_text = wpaicg_response.msg;
2084                                wpaicg_message = '<li class="' + class_ai_item + '" style="background-color:' + wpaicg_ai_bg + ';font-size: ' + wpaicg_font_size + 'px;color: ' + wpaicg_font_color + '"><p style="width:100%"><span class="wpaicg-chat-message wpaicg-chat-message-error" id="wpaicg-chat-message-' + wpaicg_randomnum + '"></span>';
2085                            }
2086                        }
2087                    } else {
2088                        wpaicg_message = '<li class="' + class_ai_item + '" style="background-color:' + wpaicg_ai_bg + ';font-size: ' + wpaicg_font_size + 'px;color: ' + wpaicg_font_color + '"><p style="width:100%"><span class="wpaicg-chat-message wpaicg-chat-message-error" id="wpaicg-chat-message-' + wpaicg_randomnum + '"></span>';
2089                        wpaicg_response_text = 'Something went wrong. Please clear your cache and try again.';
2090                        wpaicg_ai_thinking.style.display = 'none';
2091                    }
2092                    if (wpaicg_response_text === 'null' || wpaicg_response_text === null) {
2093                        wpaicg_response_text = 'Empty response from api. Check your server logs for more details.';
2094                    }
2095                    setupButtonListeners(copyEnabled, feedbackEnabled, class_ai_item, emptyClipboardSVG, checkedClipboardSVG, thumbsUpSVG, thumbsDownSVG, showFeedbackModal, wpaicg_ai_bg, wpaicg_font_color, usrBg, chat, wpaicg_nonce, chatbot_identity);
2096                    const simpleChatId = chatId.replace('wpaicg-chat-message-', '');
2097                    updateChatHistory(wpaicg_response_text, 'ai', simpleChatId, chat, chatbot_identity, clientID);
2098                    if (wpaicg_response_text !== '' && wpaicg_message !== '') {
2099                        if (parseInt(wpaicg_speech) == 1 && (userVoiceControl == "1" ? isAudioEnabledByUser : true)) {
2100                            if (voice_service === 'google') {
2101                                wpaicg_ai_thinking.style.display = 'block';
2102                                let speechData = new FormData();
2103                                speechData.append('nonce', wpaicg_nonce);
2104                                speechData.append('action', 'wpaicg_google_speech');
2105                                speechData.append('language', voice_language);
2106                                speechData.append('name', voice_name);
2107                                speechData.append('device', voice_device);
2108                                speechData.append('speed', voice_speed);
2109                                speechData.append('pitch', voice_pitch);
2110                                speechData.append('text', wpaicg_response_text);
2111                                var speechRequest = new XMLHttpRequest();
2112                                speechRequest.open("POST", wpaicgParams.ajax_url);
2113                                speechRequest.onload = function () {
2114                                    var result = speechRequest.responseText;
2115                                    try {
2116                                        result = JSON.parse(result);
2117                                        if (result.status === 'success') {
2118                                            var byteCharacters = atob(result.audio);
2119                                            const byteNumbers = new Array(byteCharacters.length);
2120                                            for (let i = 0; i < byteCharacters.length; i++) {
2121                                                byteNumbers[i] = byteCharacters.charCo
2121deAt(i);
2122                                            }
2123                                            const byteArray = new Uint8Array(byteNumbers);
2124                                            const blob = new Blob([byteArray], { type: 'audio/mp3' });
2125                                            const blobUrl = URL.createObjectURL(blob);
2126                                            wpaicg_message += '<audio style="margin-top:6px;" controls="controls"><source type="audio/mpeg" src="' + blobUrl + '"></audio>';
2127                                            wpaicg_message += '</p></li>';
2128                                            wpaicg_ai_thinking.style.display = 'none';
2129                                            // scroll to the bottom of the chatbox
2130                                            wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
2131                                            wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
2132                                        }
2133                                        else {
2134                                            var errorMessageDetail = 'Google: ' + result.msg;
2135                                            wpaicg_ai_thinking.style.display = 'none';
2136                                            if (parseInt(wpaicg_voice_error) !== 1) {
2137                                                wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">' + errorMessageDetail + '</span>';
2138                                            }
2139                                            else if (typeof wpaicg_response !== 'undefined' && typeof wpaicg_response.log !== 'undefined' && wpaicg_response.log !== '') {
2140                                                var speechLogMessage = new FormData();
2141                                                speechLogMessage.append('nonce', wpaicg_nonce);
2142                                                speechLogMessage.append('log_id', wpaicg_response.log);
2143                                                speechLogMessage.append('message', errorMessageDetail);
2144                                                speechLogMessage.append('action', 'wpaicg_speech_error_log');
2145                                                var speechErrorRequest = new XMLHttpRequest();
2146                                                speechErrorRequest.open("POST", wpaicgParams.ajax_url);
2147                                                speechErrorRequest.send(speechLogMessage);
2148                                            }
2149                                            wpaicg_message += '</p></li>';
2150                                            wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
2151                                        }
2152                                    }
2153                                    catch (errorSpeech) {
2154
2155                                    }
2156                                }
2157                                speechRequest.send(speechData);
2158                            }
2159                            else if (voice_service === 'openai') {
2160                                // OpenAI TTS code
2161                                let speechData = new FormData();
2162                                speechData.append('action', 'wpaicg_openai_speech');
2163                                speechData.append('nonce', wpaicg_nonce);
2164                                speechData.append('text', wpaicg_response_text);
2165
2166
2167                                speechData.append('model', openai_model);
2168                                speechData.append('voice', openai_voice);
2169                                speechData.append('output_format', openai_output_format);
2170                                speechData.append('speed', openai_voice_speed);
2171
2172                                // Display some sort of loading indicator
2173                                wpaicg_ai_thinking.style.display = 'block';
2174
2175                                var speechRequest = new XMLHttpRequest();
2176                                speechRequest.open("POST", wpaicgParams.ajax_url);
2177                                speechRequest.responseType = "arraybuffer"; // Expecting raw audio data
2178
2179                                speechRequest.onload = function () {
2180                                    if (speechRequest.status === 200) {
2181                                        wpaicg_ai_thinking.style.display = 'none';
2182
2183                                        const audioData = speechRequest.response;
2184                                        const blobMimeType = getBlobMimeType(openai_output_format); // Get the MIME type based on the format
2185                                        const blob = new Blob([audioData], { type: blobMimeType });
2186                                        const blobUrl = URL.createObjectURL(blob);
2187                                        // Update your message UI here
2188                                        wpaicg_message += '<audio style="margin-top:6px;" controls="controls"><source type="audio/mpeg" src="' + blobUrl + '"></audio>';
2189                                        // scroll to the bottom of the chatbox
2190                                        wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
2191                                        wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
2192                                    } else {
2193                                        // Handle HTTP errors
2194                                        wpaicg_ai_thinking.style.display = 'none';
2195                                        console.error('Error generating speech with OpenAI:', speechRequest.statusText);
2196                                        // Update your message UI to show the error
2197                                        wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">Error generating speech with OpenAI</span>';
2198                                        wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
2199                                    }
2200                                };
2201
2202                                speechRequest.onerror = function () {
2203                                    // Handle network errors
2204                                    wpaicg_ai_thinking.style.display = 'none';
2205                                    console.error('Network error during speech generation with OpenAI');
2206                                    // Update your message UI to show the network error
2207                                    wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">Network error during speech generation</span>';
2208                                    wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
2209                                };
2210
2211                                speechRequest.send(speechData);
2212                                // Utility function to get the correct MIME type
2213                                function getBlobMimeType(format) {
2214                                    switch (format) {
2215                                        case 'opus':
2216                                            return 'audio/opus';
2217                                        case 'aac':
2218                                            return 'audio/aac';
2219                                        case 'flac':
2220                                            return 'audio/flac';
2221                                        default:
2222                                            return 'audio/mpeg'; // Default to MP3
2223                                    }
2224                                }
2225                            }
2226
2227                            else {
2228                                let speechData = new FormData();
2229                                speechData.append('nonce', wpaicg_nonce);
2230                                speechData.append('message', wpaicg_response_text);
2231                                speechData.append('voice', wpaicg_voice);
2232                                speechData.append('elevenlabs_model', elevenlabs_model);
2233                                speechData.append('action', 'wpaicg_text_to_speech');
2234                                wpaicg_ai_thinking.style.display = 'block';
2235                                var speechRequest = new XMLHttpRequest();
2236                                speechRequest.open("POST", wpaicgParams.ajax_url);
2237                                speechRequest.responseType = "arraybuffer";
2238                                speechRequest.onload = function () {
2239                                    wpaicg_ai_thinking.style.display = 'none';
2240                                    var blob = new Blob([speechRequest.response], { type: "audio/mpeg" });
2241                                    var fr = new FileReader();
2242                                    fr.onload = function () {
2243                                        var fileText = this.result;
2244                                        try {
2245                                            var errorMessage = JSON.parse(fileText);
2246                                            var errorMessageDetail = 'ElevenLabs: ' + errorMessage.detail.message;
2247                                            if (parseInt(wpaicg_voice_error) !== 1) {
2248                                                wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">' + errorMessageDetail + '</span>';
2249                                            } else if (typeof wpaicg_response !== 'undefined' && typeof wpaicg_response.log !== 'undefined' && wpaicg_response.log !== '') {
2250                                                var speechLogMessage = new FormData();
2251                                                speechLogMessage.append('nonce', wpaicg_nonce);
2252                                                speechLogMessage.append('log_id', wpaicg_response.log);
2253                                                speechLogMessage.append('message', errorMessageDetail);
2254                                                speechLogMessage.append('action', 'wpaicg_speech_error_log');
2255                                                var speechErrorRequest = new XMLHttpRequest();
2256                                                speechErrorRequest.open("POST", wpaicgParams.ajax_url);
2257                                                speechErrorRequest.send(s
2257peechLogMessage);
2258                                            }
2259                                            wpaicg_message += '</p></li>';
2260                                            wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
2261                                        } catch (errorBlob) {
2262                                            var blobUrl = URL.createObjectURL(blob);
2263                                            wpaicg_message += '<audio style="margin-top:6px;" controls="controls"><source type="audio/mpeg" src="' + blobUrl + '"></audio>';
2264                                            wpaicg_message += '</p></li>';
2265                                            wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
2266                                        }
2267                                    }
2268                                    fr.readAsText(blob);
2269                                }
2270                                speechRequest.send(speechData);
2271                            }
2272                        }
2273                        else {
2274                            wpaicg_message += '</p></li>';
2275                            wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
2276                        }
2277                    }
2278                }
2279            }
2280        }
2281    }
2282
2283    // Function to hide all conversation starters
2284    function hideConversationStarters() {
2285        const starters = document.querySelectorAll('.wpaicg-conversation-starters');
2286        starters.forEach(starter => {
2287            starter.style.display = 'none';
2288        });
2289    }
2290
2291    function handleStreaming(wpaicgData, wpaicg_messages_box, wpaicg_box_typing, wpaicg_ai_thinking, class_ai_item, chat, chatbot_identity, clientID, wpaicg_nonce) {
2292        // Remove the lead form if it exists
2293        var leadFormMessage = chat.querySelector('.wpaicg-lead-form-message');
2294        if (leadFormMessage) {
2295            leadFormMessage.remove();
2296            // Optionally, set 'wpaicg_lead_form_shown' to '1' so it doesn't show again
2297            localStorage.setItem('wpaicg_lead_form_shown', '1');
2298        }
2299        const fontSize = chat.getAttribute('data-fontsize');
2300        const aiBg = chat.getAttribute('data-ai-bg-color');
2301        const fontColor = chat.getAttribute('data-color');
2302        const usrBg = chat.getAttribute('data-user-bg-color');
2303        const copyEnabled = chat.getAttribute('data-copy_btn') === "1";
2304        const feedbackEnabled = chat.getAttribute('data-feedback_btn') === "1";
2305
2306        wpaicg_box_typing.value = '';
2307        // add chatID to the query string
2308        const chatId = `wpaicg-chat-message-${Math.floor(Math.random() * 100000) + 1}`;
2309        const cleanedChatId = chatId.replace('wpaicg-chat-message-', ''); // Clean the chatId by removing the prefix
2310        wpaicgData.append('chat_id', cleanedChatId);
2311
2312        const emptyClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-copy" viewBox="0 0 16 16">
2313        <path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/>
2314        </svg>`;
2315        const checkedClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-check2" viewBox="0 0 16 16">
2316        <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0"/>
2317        </svg>`;
2318
2319        const thumbsUpSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">
2320        <path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2 2 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a10 10 0 0 0-.443.05 9.4 9.4 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a9 9 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.2 2.2 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.9.9 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>
2321        </svg>`;
2322        const thumbsDownSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}
2322" class="bi bi-hand-thumbs-down" viewBox="0 0 16 16">
2323        <path d="M8.864 15.674c-.956.24-1.843-.484-1.908-1.42-.072-1.05-.23-2.015-.428-2.59-.125-.36-.479-1.012-1.04-1.638-.557-.624-1.282-1.179-2.131-1.41C2.685 8.432 2 7.85 2 7V3c0-.845.682-1.464 1.448-1.546 1.07-.113 1.564-.415 2.068-.723l.048-.029c.272-.166.578-.349.97-.484C6.931.08 7.395 0 8 0h3.5c.937 0 1.599.478 1.934 1.064.164.287.254.607.254.913 0 .152-.023.312-.077.464.201.262.38.577.488.9.11.33.172.762.004 1.15.069.13.12.268.159.403.077.27.113.567.113.856s-.036.586-.113.856c-.035.12-.08.244-.138.363.394.571.418 1.2.234 1.733-.206.592-.682 1.1-1.2 1.272-.847.283-1.803.276-2.516.211a10 10 0 0 1-.443-.05 9.36 9.36 0 0 1-.062 4.51c-.138.508-.55.848-1.012.964zM11.5 1H8c-.51 0-.863.068-1.14.163-.281.097-.506.229-.776.393l-.04.025c-.555.338-1.198.73-2.49.868-.333.035-.554.29-.554.55V7c0 .255.226.543.62.65 1.095.3 1.977.997 2.614 1.709.635.71 1.064 1.475 1.238 1.977.243.7.407 1.768.482 2.85.025.362.36.595.667.518l.262-.065c.16-.04.258-.144.288-.255a8.34 8.34 0 0 0-.145-4.726.5.5 0 0 1 .595-.643h.003l.014.004.058.013a9 9 0 0 0 1.036.157c.663.06 1.457.054 2.11-.163.175-.059.45-.301.57-.651.107-.308.087-.67-.266-1.021L12.793 7l.353-.354c.043-.042.105-.14.154-.315.048-.167.075-.37.075-.581s-.027-.414-.075-.581c-.05-.174-.111-.273-.154-.315l-.353-.354.353-.354c.047-.047.109-.176.005-.488a2.2 2.2 0 0 0-.505-.804l-.353-.354.353-.354c.006-.005.041-.05.041-.17a.9.9 0 0 0-.121-.415C12.4 1.272 12.063 1 11.5 1"/>
2324        </svg>`;
2325
2326        const messageHtml = `
2327            <li class="${class_ai_item} wpaicg-icon-container" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}">
2328                <p style="width:100%">
2329                    <span class="wpaicg-chat-message" id="${chatId}"></span>
2330                    ${copyEnabled ? `<button class="wpaicg-copy-button" data-chat-id="${chatId}">${emptyClipboardSVG}</button>` : ''}
2331                    ${feedbackEnabled ? `<button class="wpaicg-thumbs-up-button" data-chat-id="${chatId}">${thumbsUpSVG}</button>
2332                    <button class="wpaicg-thumbs-down-button" data-chat-id="${chatId}">${thumbsDownSVG}</button>` : ''}
2333                </p>
2334            </li>
2335        `;
2336
2337        // Buffer to accumulate chunks
2338        let buffer = '';
2339        let completeAIResponse = '';
2340        let dataQueue = [];
2341        let isProcessing = false;
2342
2343        function processBuffer() {
2344            processMarkdown(buffer, true, chatId);
2345        }
2346
2347        function typeWriter(text, i, elementId, callback) {
2348            toggleBlinkingCursor(false);
2349            if (i < text.length) {
2350                const charToAdd = text.charAt(i);
2351                if (charToAdd === '<') {
2352                    const tag = text.slice(i, i + 4);
2353                    if (tag === '<br>') {
2354                        jQuery(`#${elementId}`).append(tag);
2355                        i += 4;
2356                    } else {
2357                        jQuery(`#${elementId}`).append(charToAdd);
2358                        i++;
2359                    }
2360                } else {
2361                    jQuery(`#${elementId}`).append(charToAdd);
2362                    i++;
2363                }
2364                setTimeout(() => typeWriter(text, i, elementId, callback), 1);
2365            } else if (callback) {
2366                callback();
2367                scrollToBottom();
2368            }
2369        }
2370
2371        function scrollToBottom() {
2372            wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
2373        }
2374
2375        function processQueue() {
2376            if (dataQueue.length && !isProcessing) {
2377                isProcessing = true;
2378                const nextChunk = dataQueue.shift();
2379                typeWriter(nextChunk, 0, chatId, () => {
2380                    isProcessing = false;
2381                    processQueue();
2382                });
2383            } else {
2384                toggleBlinkingCursor(false);
2385            }
2386        }
2387
2388        function toggleBlinkingCursor(isVisible) {
2389            const cursorElement = jQuery(`#${chatId} .blinking-cursor`);
2390            if (isVisible) {
2391                if (!cursorElement.length) {
2392                    jQuery(`#${chatId}`).append('<span class="blinking-cursor">|</span>');
2393                }
2394            } else {
2395                cursorElement.remove();
2396            }
2397        }
2398
2399        // Fetch POST request for streaming
2400        fetch(wpaicgParams.ajax_url, {
2401            method: 'POST',
2402            headers: {
2403                'Content-Type': 'application/x-www-form-urlencoded',
2404            },
2405            body: new URLSearchParams(wpaicgData).toString(),
2406        })
2407            .then(response => response.body)
2408            .then(async (reader) => {
2409                const decoder = new TextDecoder();
2410                const stream = reader.getReader();
2411
2412                toggleBlinkingCursor(true);
2413                wpaicg_ai_thinking.style.display = 'none';
2414                wpaicg_messages_box.innerHTML += messageHtml;
2415
2416                let partial = '';
2417
2418                while (true) {
2419                    const { done, value } = await stream.read();
2420                    if (done) {
2421                        toggleBlinkingCursor(false);
2422                        wpaicg_ai_thinking.style.display = 'none';
2423                        const simpleChatId = chatId.replace('wpaicg-chat-message-', '');
2424                        updateChatHistory(completeAIResponse, 'ai', simpleChatId, chat, chatbot_identity, clientID);
2425
2426                        break;
2427                    }
2428
2429                    partial += decoder.decode(value); // Append chunk
2430
2431                    // Split the partial data by lines
2432                    const lines = partial.split('\n');
2433
2434                    for (let i = 0; i < lines.length - 1; i++) {
2435                        let line = lines[i];
2436                        if (line.trim() === '' || !line.startsWith('data: ')) {
2437                            continue;
2438                        }
2439
2440                        // Remove 'data: ' prefix
2441                        line = line.slice(6);
2442
2443                        // Handle [DONE] signal
2444                        if (line === "[DONE]") {
2445                            toggleBlinkingCursor(false);
2446                            wpaicg_ai_thinking.style.display = 'none';
2447                            scrollToBottom();
2448                            const simpleChatId = chatId.replace('wpaicg-chat-message-', '');
2449                            updateChatHistory(completeAIResponse, 'ai', simpleChatId, chat, chatbot_identity, clientID);
2450
2451                            if (!localStorage.getItem('wpaicg_lead_form_shown')) {
2452                                maybeShowLeadForm(chat, chatId);
2453                                // scroll to the bottom of the chatbox
2454                                scrollToBottom();
2455                            }
2456                            return;
2457                        }
2458
2459                        // Try to parse the remaining JSON data
2460                        try {
2461                            const resultData = JSON.parse(line);
2462
2463                            if (resultData.tokenLimitReached || resultData.messageFlagged || resultData.pineconeError || resultData.ipBanned || resultData.modflag) {
2464                                document.getElementById(chatId).innerHTML = `<span class="wpaicg-chat-message">${resultData.msg}</span>`;
2465                                wpaicg_ai_thinking.style.display = 'none';
2466                                toggleBlinkingCursor(false);
2467                                scrollToBottom();
2468                                return;
2469                            }
2470
2471                            if (resultData.error) {
2472                                dataQueue.push(resultData.error.message);
2473                            } else {
2474                                const content = resultData.choices?.[0]?.delta?.content || resultData.choices?.[0]?.text || '';
2475                                buffer += content;
2476                                processBuffer();
2477                                completeAIResponse += content;
2478                            }
2479
2480                            processQueue();
2481                            scrollToBottom();
2482                        } catch (err) {
2483                            console.error('Error parsing JSON:', err, line);
2484                        }
2485                    }
2486
2487                    partial = lines[lines.length - 1]; // Keep last partial line
2488                }
2489
2490                // Process any remaining partial data after the loop ends
2491                if (partial.trim() !== '') {
2492                    const lines = partial.split('\n');
2493                    for (let line of lines) {
2494                        if (line.trim() === '' || !line.startsWith('data: ')) {
2495                            continue;
2496                        }
2497
2498                        // Remove 'data: ' prefix
2499                        line = line.slice(6);
2500
2501                        // Handle [DONE] signal
2502                        if (line === "[DONE]") {
2503                            toggleBlinkingCursor(false);
2504                            return;
2505                        }
2506
2507                        // Try to parse the remaining JSON data
2508                        try {
2509                            const resultData = JSON.parse(line);
2510
2511                            if (resultData.tokenLimitReached || resultData.messageFlagged || resultData.pineconeError || resultData.ipBanned || resultData.modflag) {
2512                                document.getElementById(chatId).innerHTML = `<span class="wpaicg-chat-message">${resultData.msg}</span>`;
2513                                wpaicg_ai_thinking.style.display = 'none';
2514                                toggleBlinkingCursor(false);
2515                                scrollToBottom();
2516                                return;
2517                            }
2518
2519                            if (resultData.error) {
2520                                dataQueue.push(resultData.error.message);
2521                            } else {
2522                                const content = resultData.choices?.[0]?.delta?.content || resultData.choices?.[0]?.text || '';
2523                                buffer += content;
2524                                processBuffer();
2525                                completeAIResponse += content;
2526                            }
2527
2528                            processQueue();
2529                            scrollToBottom();
2530                        } catch (err) {
2531                            console.error('Error parsing JSON after stream end:', err, line);
2532                        }
2533                    }
2534                }
2535            })
2536            .catch(error => {
2537                console.log("Fetch failed:", error);
2538                toggleBlinkingCursor(false);
2539                wpaicg_ai_thinking.style.display = 'none';
2540            });
2541
2542        // Setup button listeners for the copy and feedback buttons
2543        setupButtonListeners(copyEnabled, feedbackEnabled, class_ai_item, emptyClipboardSVG, checkedClipboardSVG, thumbsUpSVG, thumbsDownSVG, showFeedbackModal, aiBg, fontColor, usrBg, chat, wpaicg_nonce, chatbot_identity);
2544
2545    }
2546
2547    function handleAssistantStreaming(
2548        wpaicgData,
2549        wpaicg_messages_box,
2550        wpaicg_box_typing,
2551        wpaicg_ai_thinking,
2552        class_ai_item,
2553        chat,
2554        chatbot_identity,
2555        clientID,
2556        wpaicg_nonce
2557    ) {
2558        // Remove the lead form if it exists
2559        const leadFormMessage = chat.querySelector('.wpaicg-lead-form-message');
2560        if (leadFormMessage) {
2561            leadFormMessage.remove();
2562            // Optionally, set 'wpaicg_lead_form_shown' to '1' so it doesn't show again
2563            localStorage.setItem('wpaicg_lead_form_shown', '1');
2564        }
2565
2566        // Retrieve UI configurations
2567        const fontSize = chat.getAttribute('data-fontsize');
2568        const aiBg = chat.getAttribute('data-ai-bg-color');
2569        const fontColor = chat.getAttribute('data-color');
2570        const usrBg = chat.getAttribute('data-user-bg-color');
2571        const copyEnabled = chat.getAttribute('data-copy_btn') === "1";
2572        const feedbackEnabled = chat.getAttribute('data-feedback_btn') === "1";
2573
2574        // Clear the typing box
2575        wpaicg_box_typing.value = '';
2576
2577        // Generate a unique chat ID
2578        const chatId = `wpaicg-chat-message-${Math.floor(Math.random() * 100000) + 1}`;
2579        const cleanedChatId = chatId.replace('wpaicg-chat-message-', ''); // Clean the chatId by removing the prefix
2580        wpaicgData.append('chat_id', cleanedChatId);
2581
2582        const emptyClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}
2582" class="bi bi-copy" viewBox="0 0 16 16">
2583        <path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/>
2584        </svg>`;
2585        const checkedClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-check2" viewBox="0 0 16 16">
2586        <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0"/>
2587        </svg>`;
2588
2589        const thumbsUpSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">
2590        <path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2 2 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a10 10 0 0 0-.443.05 9.4 9.4 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a9 9 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.2 2.2 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.9.9 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>
2591        </svg>`;
2592        const thumbsDownSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-hand-thumbs-down" viewBox="0 0 16 16">
2593        <path d="M8.864 15.674c-.956.24-1.843-.484-1.908-1.42-.072-1.05-.23-2.015-.428-2.59-.125-.36-.479-1.012-1.04-1.638-.557-.624-1.282-1.179-2.131-1.41C2.685 8.432 2 7.85 2 7V3c0-.845.682-1.464 1.448-1.546 1.07-.113 1.564-.415 2.068-.723l.048-.029c.272-.166.578-.349.97-.484C6.931.08 7.395 0 8 0h3.5c.937 0 1.599.478 1.934 1.064.164.287.254.607.254.913 0 .152-.023.312-.077.464.201.262.38.577.488.9.11.33.172.762.004 1.15.069.13.12.268.159.403.077.27.113.567.113.856s-.036.586-.113.856c-.035.12-.08.244-.138.363.394.571.418 1.2.234 1.733-.206.592-.682 1.1-1.2 1.272-.847.283-1.803.276-2.516.211a10 10 0 0 1-.443-.05 9.36 9.36 0 0 1-.062 4.51c-.138.508-.55.848-1.012.964zM11.5 1H8c-.51 0-.863.068-1.14.163-.281.097-.506.229-.776.393l-.04.025c-.555.338-1.198.73-2.49.868-.333.035-.554.29-.554.55V7c0 .255.226.543.62.65 1.095.3 1.977.997 2.614 1.709.635.71 1.064 1.475 1.238 1.977.243.7.407 1.768.482 2.85.025.362.36.595.667.518l.262-.065c.16-.04.258-.144.288-.255a8.34 8.34 0 0 0-.145-4.726.5.5 0 0 1 .595-.643h.003l.014.004.058.013a9 9 0 0 0 1.036.157c.663.06 1.457.054 2.11-.163.175-.059.45-.301.57-.651.107-.308.087-.67-.266-1.021L12.793 7l.353-.354c.043-.042.105-.14.154-.315.048-.167.075-.37.075-.581s-.027-.414-.075-.581c-.05-.174-.111-.273-.154-.315l-.353-.354.353-.354c.047-.047.109-.176.005-.488a2.2 2.2 0 0 0-.505-.804l-.353-.354.353-.354c.006-.005.041-.05.041-.17a.9.9 0 0 0-.121-.415C12.4 1.272 12.063 1 11.5 1"/>
2594        </svg>`;
2595
2596        const messageHtml = `
2597            <li class="${class_ai_item} wpaicg-icon-container" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}">
2598                <span class="wpaicg-chat-message" id="${chatId}"></span>
2599                ${copyEnabled ? `<button class="wpaicg-copy-button" data-chat-id="${chatId}">${emptyClipboardSVG}</button>` : ''}
2600                ${feedbackEnabled ? `<button class="wpaicg-thumbs-up-button" data-chat-id="${chatId}">${thumbsUpSVG}</button>
2601                <button class="wpaicg-thumbs-down-button" data-chat-id="${chatId}">${thumbsDownSVG}</button>` : ''}
2602            </li>
2603        `;
2604
2605        let completeAIResponse = '';
2606        let aiMessageAdded = false;
2607        let accumulatedBuffer = ''; // Buffer to accumulate chunks
2608        let doneCalled = false;
2609
2610        // To store the thread ID logic (multiple threads)
2611        const botId = chat.getAttribute('data-bot-id') || '0';
2612        const activeThreadKey = `wpaicg_current_thread_${botId}_${clientID}`;
2613        const existingThreadKey = localStorage.getItem(activeThreadKey);
2614        // If we appended a real thread_id in wpaicgData, handle that in the server streaming
2615
2616        function scrollToBottom() {
2617            wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
2618        }
2619
2620        function toggleBlinkingCursor(isVisible) {
2621            const cursorElement = jQuery(`#${chatId} .blinking-cursor`);
2622            if (isVisible) {
2623                if (!cursorElement.length) {
2624                    jQuery(`#${chatId}`).append('<span class="blinking-cursor">|</span>');
2625                }
2626            } else {
2627                cursorElement.remove();
2628            }
2629        }
2630
2631
2632        // Fetch POST request for Assistant API streaming
2633        fetch(wpaicgParams.ajax_url, {
2634            method: 'POST',
2635            headers: {
2636                'Content-Type': 'application/x-www-form-urlencoded',
2637            },
2638            body: new URLSearchParams(wpaicgData).toString(),
2639        })
2640            .then(response => {
2641                const reader = response.body.getReader();
2642                const decoder = new TextDecoder();
2643                let partial = '';
2644                let currentEvent = null;
2645
2646                function read() {
2647                    return reader.read().then(({ done, value }) => {
2648                        if (done) {
2649                            handleDoneEvent();
2650                            return;
2651                        }
2652
2653                        partial += decoder.decode(value, { stream: true });
2654                        // attempt to parse the partial response as JSON
2655                        try {
2656                            const json = JSON.parse(partial);
2657                            if (json.status === 'error' && json.msg) {
2658                                handleErrorEvent(json.msg); // use the existing error handler
2659                                return; // stop further processing
2660                            }
2661                        } catch (e) {
2662                            // ignore JSON parse errors here, continue streaming
2663                        }
2664                        const lines = partial.split('\n');
2665
2666                        for (let i = 0; i < lines.length - 1; i++) {
2667                            const line = lines[i].trim();
2668                            if (line === '') continue;
2669
2670                            if (line.startsWith('event: ')) {
2671                                currentEvent = line.slice(7).trim();
2672                            } else if (line.startsWith('data: ')) {
2673                                const data = line.slice(6).trim();
2674                                handleAssistantStreamEvent(currentEvent, data);
2675                            }
2676                        }
2677
2678                        partial = lines[lines.length - 1];
2679                        return read();
2680                    });
2681                }
2682
2683                return read();
2684            })
2685            .catch(error => {
2686                console.log("Fetch failed:", error);
2687                toggleBlinkingCursor(false);
2688                wpaicg_ai_thinking.style.display = 'none';
2689            });
2690
2691        // Function to handle Assistant API streaming events
2692        function handleAssistantStreamEvent(event, data) {
2693            switch (event) {
2694                case 'thread_id':
2695                    handleThreadIdEvent(data);
2696                    break;
2697                case 'thread.message.delta':
2698                    handleMessageDeltaEvent(data);
2699                    break;
2700                case 'thread.message.completed':
2701                    handleMessageCompletedEvent(data);
2702                    break;
2703                case 'assistant_error':
2704                    handleErrorEvent(data);
2705                    break;
2706                case 'done':
2707                    handleDoneEvent();
2708                    break;
2709                case null:
2710                    handleErrorEvent(data);
2711                    break;
2712            }
2713        }
2714
2715        function handleMessageDeltaEvent(data) {
2716            const deltaData = JSON.parse(data);
2717            if (deltaData && deltaData.delta && deltaData.delta.content) {
2718                const contentArray = deltaData.delta.content;
2719                for (const contentItem of contentArray) {
2720                    if (contentItem.type === 'text' && contentItem.text && contentItem.text.value) {
2721                        const messageChunk = contentItem.text.value;
2722                        if (!aiMessageAdded) {
2723                            wpaicg_messages_box.innerHTML += messageHtml;
2724                            aiMessageAdded = true;
2725                            wpaicg_ai_thinking.style.display = 'none';
2726                        }
2727
2728                        // Accumulate chunks
2729                        accumulatedBuffer += messageChunk;
2730                        completeAIResponse += messageChunk;
2731
2732                        // Convert Markdown to HTML
2733                        const htmlContent = marked.parse(accumulatedBuffer);
2734
2735                        // Update the chat message content
2736                        document.getElementById(chatId).innerHTML = htmlContent;
2737
2738                        scrollToBottom();
2739                    }
2740                }
2741            }
2742        }
2743
2744        function handleMessageCompletedEvent(data) {
2745            toggleBlinkingCursor(false);
2746            wpaicg_ai_thinking.style.display = 'none';
2747            scrollToBottom();
2748        }
2749
2750        function handleThreadIdEvent(threadId) {
2751            // store thread id for the active thread localStorage
2752            if (existingThreadKey) {
2753                let threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
2754                threadListObj[existingThreadKey] = threadId;
2755                localStorage.setItem('wpaicg_thread_list', JSON.stringify(threadListObj));
2756            }
2757        }
2758
2759        // Handler for 'assistant_error' events
2760        function handleErrorEvent(errorData) {
2761            console.log('Assistant Error:', errorData);
2762
2763            let errorMessage = 'An unknown error occurred.';
2764
2765            try {
2766                // check if errorData is a string and try to parse it as JSON
2767                if (typeof errorData === 'string') {
2768                    try {
2769                        const parsedError = JSON.parse(errorData);
2770                        errorMessage = parsedError.msg || errorMessage; // use parsed message if available
2771                    } catch (jsonParseError) {
2772                        // if parsing fails, fall back to using the raw string
2773                        errorMessage = errorData;
2774                    }
2775                } else if (errorData && errorData.msg) {
2776                    errorMessage = errorData.msg; // directly use the msg field if it's an object
2777                }
2778            } catch (e) {
2779                console.error('Error handling error data:', e); // log any unexpected issues
2780            }
2781
2782            // parse the error message with marked for markdown or URL formatting
2783            const parsedHtml = marked.parse(errorMessage);
2784
2785            const chatElement = document.getElementById(chatId);
2786
2787            if (chatElement) {
2788                chatElement.innerHTML = `<span class="wpaicg-chat-message">${parsedHtml}</span>`;
2789            } else {
2790                // fallback if chatId doesn't exist yet
2791                wpaicg_messages_box.innerHTML += `
2792                    <li class="${class_ai_item}" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}">
2793                        <span class="wpaicg-chat-message">${parsedHtml}</span>
2794                    </li>`;
2795            }
2796
2797            wpaicg_ai_thinking.style.display = 'none';
2798            toggleBlinkingCursor(false);
2799            scrollToBottom();
2800        }
2801
2802
2803        // Handler for 'done' events
2804        function handleDoneEvent() {
2805            if (doneCalled) return;
2806            doneCalled = true;
2807            toggleBlinkingCursor(false);
2808            wpaicg_ai_thinking.style.display = 'none';
2809            if (!localStorage.getItem('wpaicg_lead_form_shown')) {
2810                maybeShowLeadForm(chat, chatId);
2811                scrollToBottom();
2812            }
2813            // update chat history
2814            const simpleChatId = chatId.replace('wpaicg-chat-message-', '');
2815            updateChatHistory(completeAIResponse, 'ai', simpleChatId, chat, chatbot_identity, clientID);
2816        }
2817
2818        // Setup button listeners for the copy and feedback buttons
2819        setupButtonListeners(
2820            copyEnabled,
2821            feedbackEnabled,
2822            class_ai_item,
2823            emptyClipboardSVG,
2824            checkedClipboardSVG,
2825            thumbsUpSVG,
2826            thumbsDownSVG,
2827            showFeedbackModal,
2828            aiBg,
2829            fontColor,
2830            usrBg,
2831            chat,
2832            wpaicg_nonce,
2833            chatbot_identity
2834        );
2835    }
2836
2837    function processMarkdown(inputText, isStream = false, chatId = null) {
2838        inputText = inputText !== '' ? inputText.trim() : '';
2839
2840        // parse the markdown using the marked library
2841        const formattedText = marked.parse(inputText);
2842
2843        // if in stream mode and chatId exists, update the DOM
2844        if (isStream && chatId) {
2845            const element = document.getElementById(chatId);
2846            if (element) {
2847                element.innerHTML = formattedText;
2848            }
2849        }
2850
2851        return formattedText;
2852    }
2853
2854    // Scroll function to adjust.
2855    function scrollToAdjust(wpaicg_messages_box) {
2856        requestAnimationFrame(() => {
2857            wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
2858        });
2859    }
2860
2861    function wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed) {
2862        var chatContainerforLead = wpaicg_messages_box.closest('.wpaicg-chat-shortcode') || wpaicg_messages_box.closest('.wpaicg-chatbox');
2863        wpaicg_messages_box.insertAdjacentHTML('beforeend', wpaicg_message);
2864        var wpaicg_current_message = document.getElementById('wpaicg-chat-message-' + wpaicg_randomnum);
2865
2866        // Ensure the current message is found
2867        if (wpaicg_current_message) {
2868            // Get the next sibling element, which should be the audio element
2869            var nextElement = wpaicg_current_message.closest('li').nextElementSibling;
2870
2871            // Check if the next element is an audio tag and play it if found
2872            if (nextElement && nextElement.tagName.toLowerCase() === 'audio') {
2873                nextElement.play();
2874            } else {
2875                console.log('No audio found next to the current message.');
2876            }
2877        } else {
2878            console.log('Current message not found.');
2879        }
2880
2881        // Apply formatting to the entire response text first
2882        var formattedText = marked.parse(wpaicg_response_text);
2883
2884        if (wpaicg_typewriter_effect) {
2885            let index = 0; // Starting index of the substring
2886            function typeWriter() {
2887                if (index < formattedText.length) {
2888                    wpaicg_current_message.innerHTML = formattedText.slice(0, index + 1);
2889                    index++;
2890                    setTimeout(typeWriter, wpaicg_typewriter_speed);
2891                    //scroll to the latest message if needed
2892                    scrollToAdjust(wpaicg_messages_box);
2893                } else {
2894                    // Once complete, ensure scrolling if needed
2895                    scrollToAdjust(wpaicg_messages_box);
2896                }
2897            }
2898            typeWriter(); // Start the typewriter effect
2899
2900        } else {
2901            wpaicg_current_message.innerHTML = formattedText;
2902            // Scroll to the latest message if needed
2903            scrollToAdjust(wpaicg_messages_box);
2904        }
2905        if (!localStorage.getItem('wpaicg_lead_form_shown')) {
2906            maybeShowLeadForm(chatContainerforLead, 'wpaicg-chat-message-' + wpaicg_randomnum);
2907            // scroll to the bottom of the chatbox
2908            scrollToAdjust(wpaicg_messages_box);
2909        }
2910    }
2911
2912    function wpaicgMicEvent(mic) {
2913        if (mic.classList.contains('wpaicg-recording')) {
2914            mic.innerHTML = '';
2915            mic.innerHTML = wpaicgMicIcon;
2916            mic.classList.remove('wpaicg-recording');
2917            wpaicgstopChatRecording(mic)
2918        } else {
2919            let checkRecording = document.querySelectorAll('.wpaicg-recording');
2920            if (checkRecording && checkRecording.length) {
2921                alert('Please finish previous recording');
2922            } else {
2923                mic.innerHTML = '';
2924                mic.innerHTML = wpaicgStopIcon;
2925                mic.classList.add('wpaicg-recording');
2926                wpaicgstartChatRecording();
2927            }
2928        }
2929    }
2930    if (wpaicgChatTyping && wpaicgChatTyping.length) {
2931        for (let i = 0; i < wpaicgChatTyping.length; i++) {
2932            wpaicgChatTyping[i].addEventListener('keyup', function (event) {
2933                if ((event.which === 13 || event.keyCode === 13) && !event.shiftKey) {
2934                    let parentChat = wpaicgChatTyping[i].closest('.wpaicg-chatbox');
2935                    let chatTyping = parentChat.querySelectorAll('.wpaicg-chatbox-typing')[0];
2936                    wpaicgSendChatMessage(parentChat, chatTyping, 'widget');
2937                }
2938            })
2939        }
2940    }
2941    if (wpaicgShortcodeTyping && wpaicgShortcodeTyping.length) {
2942        for (let i = 0; i < wpaicgShortcodeTyping.length; i++) {
2943            wpaicgShortcodeTyping[i].addEventListener('keyup', function (event) {
2944                if ((event.which === 13 || event.keyCode === 13) && !event.shiftKey) {
2945                    let parentChat = wpaicgShortcodeTyping[i].closest('.wpaicg-chat-shortcode');
2946                    let chatTyping = parentChat.querySelectorAll('.wpaicg-chat-shortcode-typing')[0];
2947                    wpaicgSendChatMessage(parentChat, chatTyping, 'shortcode');
2948                }
2949            })
2950        }
2951    }
2952    if (wpaicgChatSend && wpaicgChatSend.length) {
2953        for (let i = 0; i < wpaicgChatSend.length; i++) {
2954            wpaicgChatSend[i].addEventListener('click', function (event) {
2955                let parentChat = wpaicgChatSend[i].closest('.wpaicg-chatbox');
2956                let chatTyping = parentChat.querySelectorAll('.wpaicg-chatbox-typing')[0];
2957                wpaicgSendChatMessage(parentChat, chatTyping, 'widget');
2958            })
2959        }
2960    }
2961    if (wpaicgShortcodeSend && wpaicgShortcodeSend.length) {
2962        for (let i = 0; i < wpaicgShortcodeSend.length; i++) {
2963            wpaicgShortcodeSend[i].addEventListener('click', function (event) {
2964                let parentChat = wpaicgShortcodeSend[i].closest('.wpaicg-chat-shortcode');
2965                let chatTyping = parentChat.querySelectorAll('.wpaicg-chat-shortcode-typing')[0];
2966                wpaicgSendChatMessage(parentChat, chatTyping, 'shortcode');
2967            })
2968        }
2969    }
2970
2971    if (wpaicgMicBtns && wpaicgMicBtns.length) {
2972        for (let i = 0; i < wpaicgMicBtns.length; i++) {
2973            wpaicgMicBtns[i].addEventListener('click', function () {
2974                wpaicgMicEvent(wpaicgMicBtns[i]);
2975            });
2976        }
2977    }
2978}
2979
2980// Initialize Sidebar Toggle
2981function initSidebarToggle() {
2982    const toggleButtons = document.querySelectorAll('.wpaicg-sidebar-toggle');
2983    const savedSidebarState = localStorage.getItem('wpaicg_sidebar_state') || 'closed';
2984
2985    // Set initial state for all sidebars based on the saved preference.
2986    document.querySelectorAll('.wpaicg-sidebar').forEach(sidebar => {
2987        if (savedSidebarState === 'open') {
2988            sidebar.classList.add('open');
2989        } else {
2990            sidebar.classList.remove('open');
2991        }
2992    });
2993
2994    toggleButtons.forEach(toggle => {
2995        toggle.addEventListener('click', function () {
2996            const chatContainer = this.closest('.wpaicg-chat-shortcode, .wpaicg-chatbox');
2997            const sidebar = chatContainer.querySelector('.wpaicg-sidebar');
2998            sidebar.classList.toggle('open');
2999            const newState = sidebar.classList.contains('open') ? 'open' : 'closed';
3000            localStorage.setItem('wpaicg_sidebar_state', newState);
3001        });
3002
3003        // Allow toggle via keyboard (Enter key)
3004        toggle.addEventListener('keypress', function (e) {
3005            if (e.key === 'Enter' || e.key === ' ') {
3006                e.preventDefault();
3007                this.click();
3008            }
3009        });
3010    });
3011}
3012
3013
3014/**
3015 * Builds the list of all conversations in the sidebar for each chat container,
3016 * sorted by their "last updated" timestamp in descending order.
3017 * We look for the first user message (prefixed "Human:") to name the conversation.
3018 */
3019function loadConversationList() {
3020    const containers = ['.wpaicg-chat-shortcode', '.wpaicg-chatbox'];
3021    containers.forEach(containerSelector => {
3022        const chatContainers = document.querySelectorAll(containerSelector);
3023        chatContainers.forEach(chatContainer => {
3024            // Find the sidebar within this container
3025            const sidebar = chatContainer.querySelector('.wpaicg-sidebar');
3026            if (!sidebar) return;
3027
3028            // Conversation list UL inside the sidebar
3029            const conversationList = sidebar.querySelector('.wpaicg-conversation-list');
3030            if (!conversationList) return;
3031
3032            // Clear out any old items
3033            conversationList.innerHTML = '';
3034
3035            // Identify which bot
3036            const botId = chatContainer.getAttribute('data-bot-id') || '0';
3037            const chatType = chatContainer.getAttribute('data-type') || 'shortcode';
3038
3039            // Need the clientID from localStorage
3040            let clientID = localStorage.getItem('wpaicg_chat_client_id');
3041            if (!clientID) return;
3042
3043            // Base prefix for conversation keys
3044            let conversationKeyBase = '';
3045            if (botId !== '0') {
3046                // custom bot
3047                conversationKeyBase = `wpaicg_chat_history_custom_bot_${botId}_${clientID}`;
3048            } else {
3049                // default shortcodes or widgets
3050                conversationKeyBase = `wpaicg_chat_history_${chatType}_${clientID}`;
3051            }
3052
3053            // We'll collect all conversations, then sort
3054            let conversationData = [];
3055
3056            // Loop through localStorage to find all conversation keys
3057            for (let i = 0; i < localStorage.length; i++) {
3058                const key = localStorage.key(i);
3059                // Must match our base prefix plus the underscore for the index
3060                if (key.startsWith(conversationKeyBase + '_')) {
3061                    // Ensure we're looking specifically at the conversation array, not the _timestamp
3062                    if (key.endsWith('_timestamp')) continue;
3063
3064                    const rawData = localStorage.getItem(key);
3065                    if (!rawData) continue;
3066
3067                    let chatHistory;
3068                    try {
3069                        chatHistory = JSON.parse(rawData);
3070                    } catch (e) {
3071                        chatHistory = [];
3072                    }
3073
3074                    // Default name
3075                    let title = "Untitled Conversation";
3076
3077                    // We look specifically for the FIRST user message ("Human:")
3078                    if (Array.isArray(chatHistory) && chatHistory.length > 0) {
3079                        for (let j = 0; j < chatHistory.length; j++) {
3080                            let msg = chatHistory[j].text;
3081                            if (typeof msg === 'string' && msg.startsWith('Human:')) {
3082                                // Remove "Human:"
3083                                const userMsg = msg.replace(/^Human:\s*/, '').trim();
3084                                title = userMsg.substring(0, 20) + (userMsg.length > 20 ? '…' : '');
3085                                break; // we found our user message
3086                            }
3087                        }
3088                    }
3089
3090                    // Check if there's a timestamp
3091                    const timestampKey = key + '_timestamp';
3092                    const rawTimestamp = localStorage.getItem(timestampKey);
3093                    const lastUpdated = rawTimestamp ? parseInt(rawTimestamp, 10) : 0;
3094
3095                    // Collect
3096                    conversationData.push({ key, lastUpdated, title });
3097                }
3098            }
3099
3100            // Sort conversationData by lastUpdated descending
3101            conversationData.sort((a, b) => b.lastUpdated - a.lastUpdated);
3102
3103            // Now build <li> for each
3104            conversationData.forEach(item => {
3105                const li = document.createElement('li');
3106                li.textContent = item.title;
3107                li.dataset.conversationKey = item.key;
3108
3109                // --- NEW CODE: Add a "trash" icon on hover for deleti
3109on ---
3110                const trashIcon = document.createElement('span');
3111                // Dashicons classes for a trash icon + our custom class
3112                trashIcon.classList.add('dashicons', 'dashicons-trash', 'wpaicg-delete-icon');
3113                // On click, remove from local storage & UI
3114                trashIcon.addEventListener('click', e => {
3115                    e.stopPropagation(); // Prevent loading the conversation
3116                    localStorage.removeItem(item.key);
3117                    localStorage.removeItem(item.key + '_timestamp');
3118                    li.remove();
3119                });
3120                li.appendChild(trashIcon);
3121
3122                // Clicking the li itself loads the conversation
3123                li.addEventListener('click', function () {
3124                    loadSelectedConversation(item.key, chatContainer);
3125                });
3126
3127                conversationList.appendChild(li);
3128            });
3129        });
3130    });
3131}
3132
3133/**
3134 * Loads the selected conversation key from localStorage 
3135 * into the chat UI, and sets it as active for subsequent messages.
3136 * Also handles assistant thread alignment if assistant is enabled.
3137 *
3138 * @param {string} conversationKey - The exact localStorage key for this conversation.
3139 * @param {HTMLElement} chatContainer - The .wpaicg-chat-shortcode or .wpaicg-chatbox container.
3140 */
3141function loadSelectedConversation(conversationKey, chatContainer) {
3142    // Clear the display
3143    var chatBox = chatContainer.querySelector('.wpaicg-chatbox-messages, .wpaicg-chat-shortcode-messages');
3144    if (!chatBox) return;
3145    chatBox.innerHTML = '';
3146
3147    // Retrieve the conversation
3148    var rawHistory = localStorage.getItem(conversationKey);
3149    if (!rawHistory) return;
3150
3151    var chatHistory = JSON.parse(rawHistory);
3152    if (!Array.isArray(chatHistory)) chatHistory = [];
3153
3154    // Reconstruct the conversation in the UI
3155    chatHistory.forEach(message => {
3156        reconstructMessage(chatBox, message, chatContainer);
3157    });
3158    // Scroll to bottom
3159    chatBox.scrollTop = chatBox.scrollHeight;
3160
3161    // Mark this conversationKey as the current "active" conversation
3162    const botId = chatContainer.getAttribute('data-bot-id') || '0';
3163    const clientID = localStorage.getItem('wpaicg_chat_client_id') || '';
3164    const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientID}`;
3165    localStorage.setItem(activeConversationKey, conversationKey);
3166
3167    // If assistant is enabled, we want to align the active thread similarly
3168    let assistantEnabled = chatContainer.getAttribute('data-assistant-enabled') === 'true';
3169    if (assistantEnabled) {
3170        // Example conversationKey:
3171        // wpaicg_chat_history_custom_bot_4377_dc8XxpH90T_2
3172        // we want to map the portion after "custom_bot_4377_dc8XxpH90T_" => "2"
3173        // so the thread key is "custom_bot_4377_dc8XxpH90T_2"
3174        // or if it's the default short/widget fallback
3175        let suffixParts = conversationKey.split('_');
3176        if (suffixParts.length >= 2) {
3177            // For custom bot conversation keys => wpaicg_chat_history_custom_bot_${botId}_${clientID}_${index}
3178            // suffixParts might be: [ 'wpaicg','chat','history','custom','bot','4377','dc8XxpH90T','2' ]
3179            // We'll find the last item => index
3180            let lastIndex = suffixParts[suffixParts.length - 1];
3181            // Rebuild the base to "custom_bot_{botId}_{clientID}_{lastIndex}" or for short/widget => "shortcode_{clientID}_{lastIndex}"
3182            // Actually we can slice from the third piece onward
3183            // e.g. conversationKeyBase = "custom_bot_4377_dc8XxpH90T_2"
3184            let conversationKeyBase = suffixParts.slice(3).join('_');
3185            // e.g. "custom_bot_4377_dc8XxpH90T_2"
3186
3187            // Create the "activeThreadKey"
3188            let activeThreadKeyName = `wpaicg_current_thread_${botId}_${clientID}`;
3189            localStorage.setItem(activeThreadKeyName, conversationKeyBase);
3190        }
3191    }
3192}
3193// Call the init function when the document is ready
3194document.addEventListener('DOMContentLoaded', function () {
3195    wpaicgChatInit();
3196    loadConversations();
3197    initSidebarToggle();
3198    loadConversationList();
3199    initNewChatButtons();
3200});

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.