Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
natchbone
/
comment-content
/
plugins
/
gpt3-ai-content-generator
/
public
/
js
:
wpaicg-chat.js
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
/** * Loads or initializes conversations on page load, but now * simply displays the *latest* conversation instead of forcing * a brand-new conversation each time. */ function loadConversations() { // Attempt to retrieve existing clientId from localStorage; create if missing var clientId = localStorage.getItem('wpaicg_chat_client_id'); if (!clientId) { clientId = generateRandomString(10); localStorage.setItem('wpaicg_chat_client_id', clientId); } // Load chat interfaces (shortcode + widget) on the page loadChatInterface('.wpaicg-chat-shortcode', 'shortcode', clientId); loadChatInterface('.wpaicg-chatbox', 'widget', clientId); // For each container, set its active conversation to the highest // existing index. If none exist, create a new conversation once. var containers = document.querySelectorAll('.wpaicg-chat-shortcode, .wpaicg-chatbox'); containers.forEach(container => { let botId = container.getAttribute('data-bot-id') || '0'; let chatType = container.getAttribute('data-type') || 'shortcode'; // Build base localStorage prefix let conversationKeyBase = (botId !== '0') ? `wpaicg_chat_history_custom_bot_${botId}_${clientId}` : `wpaicg_chat_history_${chatType}_${clientId}`; // Find existing conversation indexes let highestIndex = 0; for (let i = 0; i < localStorage.length; i++) { let key = localStorage.key(i); if (key.startsWith(conversationKeyBase)) { let suffixMatch = key.match(/_(\d+)$/); if (suffixMatch) { let indexNum = parseInt(suffixMatch[1], 10); if (indexNum > highestIndex) { highestIndex = indexNum; } } } } // If no existing conversation found, create one if (highestIndex === 0) { highestIndex = 1; let firstConversationKey = `${conversationKeyBase}_${highestIndex}`; localStorage.setItem(firstConversationKey, JSON.stringify([])); } // Mark the highest (latest) conversation as active let latestConversationKey = `${conversationKeyBase}_${highestIndex}`; let activeConversationKey = `wpaicg_current_conversation_${botId}_${clientId}`; localStorage.setItem(activeConversationKey, latestConversationKey); // If assistant is enabled, do the same for the "thread" logic let assistantEnabled = container.getAttribute('data-assistant-enabled') === 'true'; if (assistantEnabled) { let threadBase = (botId !== '0') ? `custom_bot_${botId}_${clientId}` : `${chatType}_${clientId}`; let threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {}; let highestThreadIndex = 0; for (let existingKey in threadListObj) { if (existingKey.startsWith(threadBase)) { let suffixMatch = existingKey.match(/_(\d+)$/); if (suffixMatch) { let threadIndexNum = parseInt(suffixMatch[1], 10); if (threadIndexNum > highestThreadIndex) { highestThreadIndex = threadIndexNum; } } } } // If no existing thread, create a new one if (highestThreadIndex === 0) { highestThreadIndex = 1; let newThreadKey = `${threadBase}_${highestThreadIndex}`; localStorage.setItem(`wpaicg_current_thread_${botId}_${clientId}`, newThreadKey); } else { // Otherwise, set the highest as active let existingThreadKey = `${threadBase}_${highestThreadIndex}`; localStorage.setItem(`wpaicg_current_thread_${botId}_${clientId}`, existingThreadKey); } } }); // Update the conversation list in the sidebar loadConversationList(); } /** * Initialize "New Chat" buttons. When a user clicks "New Chat," we create * a fresh conversation in localStorage and load it in the chat interface. * This updated version also immediately refreshes the conversation list * in the sidebar so users see the new chat appear instantly. */ function initNewChatButtons() { const newChatButtons = document.querySelectorAll('.wpaicg-new-chat-button'); newChatButtons.forEach(button => { button.addEventListener('click', function () { // Find the parent chat container (shortcode or widget) const chatContainer = button.closest('.wpaicg-chat-shortcode, .wpaicg-chatbox'); if (!chatContainer) return; // Identify chat type, e.g. 'custom_bot_4367' or 'shortcode' or 'widget' const botId = chatContainer.getAttribute('data-bot-id') || '0'; const chatType = chatContainer.getAttribute('data-type') || 'shortcode'; const assistantEnabled = chatContainer.getAttribute('data-assistant-enabled') === 'true'; let conversationKeyBase = ''; // Distinguish custom bots vs default shortcodes or widgets if (botId !== '0') { // custom bot format conversationKeyBase = `wpaicg_chat_history_custom_bot_${botId}`; } else { // fallback: default shortcodes or widgets conversationKeyBase = `wpaicg_chat_history_${chatType}`; } // Grab or create the clientID let clientID = localStorage.getItem('wpaicg_chat_client_id'); if (!clientID) { clientID = generateRandomString(10); localStorage.setItem('wpaicg_chat_client_id', clientID); } conversationKeyBase += `_${clientID}`; // Count how many existing conversations use this prefix let highestIndex = 0; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key.startsWith(conversationKeyBase)) { const suffixMatch = key.match(/_(\d+)$/); if (suffixMatch) { const indexNum = parseInt(suffixMatch[1], 10); if (indexNum > highestIndex) highestIndex = indexNum; } } } // The new conversation will be next integer const newConversationIndex = highestIndex + 1; const newConversationKey = `${conversationKeyBase}_${newConversationIndex}`; // Initialize an empty array for that conversation localStorage.setItem(newConversationKey, JSON.stringify([])); // Also set a timestamp for sorting localStorage.setItem(`${newConversationKey}_timestamp`, Date.now().toString()); // Also set this new conversation key as the "active conversation" const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientID}`; localStorage.setItem(activeConversationKey, newConversationKey); // ----- Assistant-enabled logic for multi-thread handling ----- if (assistantEnabled) { // We store the "active thread" localStorage const threadBase = botId !== '0' ? `custom_bot_${botId}_${clientID}` : `${chatType}_${clientID}`; // Count how many existing "thread" keys exist for this base let highestThreadIndex = 0; const threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {}; for (let existingKey in threadListObj) { if (existingKey.startsWith(threadBase)) { const suffixMatch = existingKey.match(/_(\d+)$/); if (suffixMatch) { const threadIndexNum = parseInt(suffixMatch[1], 10); if (threadIndexNum > highestThreadIndex) highestThreadIndex = threadIndexNum; } } } const newThreadIndex = highestThreadIndex + 1; const newThreadKey = `${threadBase}_${newThreadIndex}`; // Now set the "active thread" localStorage const activeThreadKey = `wpaicg_current_thread_${botId}_${clientID}`; localStorage.setItem(activeThreadKey, newThreadKey); } // Immediately load the newly created conversation so user sees empty chat loadSelectedConversation(newConversationKey, chatContainer); // Refresh the conversation list in the sidebar so user sees it loadConversationList(); }); }); } /** * Utility function that the code base already uses or references for client ID generation. * If you already have a similar function, you may omit or adapt this. */ function generateRandomString(length) { let result = ''; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * characters.length)); } return result; } function showAllConversationStarters() { // Target both interfaces var containers = ['.wpaicg-chat-shortcode', '.wpaicg-chatbox']; containers.forEach(containerSelector => { var chatContainers = document.querySelectorAll(containerSelector); chatContainers.forEach(chatContainer => { showConversationStarters(chatContainer); }); }); } /** * Appends the user/AI message to the localStorage conversation, respecting memory limits * and triggers a refresh of the conversation list. Also stores a timestamp for sorting. * * @param {string} message The raw message text from the user or AI. * @param {string} sender Either 'user' or 'ai'. * @param {string} uniqueId A unique identifier for the message (e.g., a random integer). * @param {HTMLElement} chat The parent chat container (.wpaicg-chat-shortcode or .wpaicg-chatbox). * @param {string} chatbot_identity Identity of the chatbot ('custom_bot_####' or 'shortcode'/'widget'). * @param {string} clientID The user's local client/session ID from localStorage. */ function updateChatHistory(message, sender, uniqueId, chat, chatbot_identity, clientID) { // Identify which bot is being used const botId = chat.getAttribute('data-bot-id') || '0'; // Active conversation key in localStorage const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientID}`; let conversationKey = localStorage.getItem(activeConversationKey); // If no active conversation yet, create one if (!conversationKey) { let defaultIndex = 1; if (botId !== '0') { // custom bot conversationKey = `wpaicg_chat_history_custom_bot_${botId}_${clientID}_${defaultIndex}`; } else { // fallback for shortcodes or widgets const type = chat.getAttribute('data-type') || 'shortcode'; conversationKey = `wpaicg_chat_history_${type}_${clientID}_${defaultIndex}`; } localStorage.setItem(conversationKey, JSON.stringify([])); localStorage.setItem(activeConversationKey, conversationKey); } // Load existing history let history = JSON.parse(localStorage.getItem(conversationKey) || '[]'); // Memory limit from data attribute or default const memoryLimit = parseInt(chat.getAttribute('data-memory-limit'), 10) || 100; // Format the message with "Human: " or "AI: " const formattedMessage = (sender === 'user' ? "Human: " : "AI: ") + (message.trim() || ''); const numericId = typeof uniqueId === 'string' ? parseInt(uniqueId, 10) : uniqueId; // Push new message to array history.push({ id: numericId || '', text: formattedMessage }); // Trim the array to memoryLimit if (history.length > memoryLimit) { history = history.slice(-memoryLimit); } // Save updated history localStorage.setItem(conversationKey, JSON.stringify(history)); // Update the 'last updated' timestamp in a separate localStorage key // This will be used for sorting conversations in descending order localStorage.setItem(`${conversationKey}_timestamp`, Date.now().toString()); // Refresh the conversation list so the user sees updated titles/timestamps loadConversationList(); } function loadChatInterface(containerSelector, type, clientId) { var chatContainers = document.querySelectorAll(containerSelector); chatContainers.forEach(chatContainer => { // Read autoload chat conversations setting, default to '1' if not set var autoloadConversations = chatContainer.getAttribute('data-autoload_chat_conversations'); if (autoloadConversations === null) { autoloadConversations = '1'; // Default value if attribute does not exist } // Fetch the bot ID based on the type var botId = chatContainer.getAttribute('data-bot-id') || '0'; if (autoloadConversations === '0') { // Get the active conversation key for this chat/bot var activeConversationKey = `wpaicg_current_conversation_${botId}_${clientId}`; var conversationKey = localStorage.getItem(activeConversationKey); // If no active conversation has been set, find the latest one if (!conversationKey) { let highestIndex = 0; let conversationKeyBase = botId !== '0' ? `wpaicg_chat_history_custom_bot_${botId}_${clientId}` : `wpaicg_chat_history_${type}_${clientId}`; // Find the highest index (latest conversation) for (let i = 0; i < localStorage.length; i++) { let key = localStorage.key(i); if (key.startsWith(conversationKeyBase)) { let suffixMatch = key.match(/_(\d+)$/); if (suffixMatch) { let indexNum = parseInt(suffixMatch[1], 10); if (indexNum > highestIndex) { highestIndex = indexNum; } } } } // If we found a conversation, use it if (highestIndex > 0) { conversationKey = `${conversationKeyBase}_${highestIndex}`; } } // Retrieve and display the chat history if we have a valid key if (conversationKey && localStorage.getItem(conversationKey)) { var chatHistory = localStorage.getItem(conversationKey); chatHistory = JSON.parse(chatHistory); // Convert old format messages to new format chatHistory = chatHistory.map(message => { if (typeof message === 'string') { return { id: '', // Old messages won't have an ID, so leave it empty text: message }; } return message; // Leave new format messages as is }); // Save the updated history back to localStorage in the new format localStorage.setItem(conversationKey, JSON.stringify(chatHistory)); var chatBox = chatContainer.querySelector('.wpaicg-chatbox-messages, .wpaicg-chat-shortcode-messages'); // Generalized selector if (!chatBox) { console.error(`No chat box found within the ${type} container.`); return; } chatBox.innerHTML = ''; // Clears the chat box chatHistory.forEach(message => { reconstructMessage(chatBox, message, chatContainer); }); chatBox.appendChild(document.createElement('br')); requestAnimationFrame(() => { requestAnimationFrame(() => { chatBox.scrollTop = chatBox.scrollHeight; // Scrolls to the bottom }); }); hideConversationStarter(chatContainer); } else { showConversationStarters(chatContainer); } } else { showConversationStarters(chatContainer); } }); } function reconstructMessage(chatBox, message, chatContainer) { var messageElement = document.createElement('li'); // Check if message is an object and has a 'text' property var messageText = typeof message === 'object' && message.text ? message.text : message; // Determine if the message is from the user or AI var isUserMessage = messageText.startsWith('Human:'); var isWidget = chatContainer.classList.contains('wpaicg-chatbox'); // Apply the correct class based on message source and container type if (isUserMessage) { messageElement.className = isWidget ? 'wpaicg-chat-user-message' : 'wpaicg-user-message'; } else { messageElement.className = isWidget ? 'wpaicg-chat-ai-message' : 'wpaicg-ai-message'; } var formattedMessage = messageText.replace('Human:', '').replace('AI:', ''); formattedMessage = marked.parse(formattedMessage); var userBgColor = chatContainer.getAttribute('data-user-bg-color'); var aiBgColor = chatContainer.getAttribute('data-ai-bg-color'); var fontSize = chatContainer.getAttribute('data-fontsize'); var fontColor = chatContainer.getAttribute('data-color'); messageElement.style.backgroundColor = isUserMessage ? userBgColor : aiBgColor; messageElement.style.color = fontColor; messageElement.style.fontSize = fontSize; messageElement.innerHTML = `<span class="wpaicg-chat-message">${formattedMessage}</span>`; chatBox.appendChild(messageElement); } function hideConversationStarter(chatContainer) { var starters = chatContainer.querySelectorAll('.wpaicg-conversation-starters'); starters.forEach(starter => starter.style.display = 'none'); } function showConversationStarters(chatContainer) { const startersContainer = chatContainer.querySelector('.wpaicg-conversation-starters'); if (startersContainer) { // Check if the container exists startersContainer.style.visibility = 'visible'; // Make the container visible if it exists const starters = startersContainer.querySelectorAll('.wpaicg-conversation-starter'); starters.forEach((starter, index) => { setTimeout(() => { starter.style.opacity = "1"; starter.style.transform = "translateY(0)"; }, index * 150); // Staggered appearance }); } } /** * Adjusts the size of all shortcode-based chat windows (.wpaicg-chat-shortcode), * including handling fullscreen mode and mobile layout. Now adds an extra offset * on mobile when there is *no* footer to prevent the text box from being cut off. */ function wpaicgChatShortcodeSize() { var wpaicgWindowWidth = window.innerWidth; var wpaicgWindowHeight = window.innerHeight; var chatShortcodes = document.getElementsByClassName('wpaicg-chat-shortcode'); if (chatShortcodes !== null && chatShortcodes.length) { for (var i = 0; i < chatShortcodes.length; i++) { var chatShortcode = chatShortcodes[i]; var parentChat = chatShortcode.parentElement; var parentWidth = parentChat.offsetWidth; var chatWidth = chatShortcode.getAttribute('data-width'); var chatHeight = chatShortcode.getAttribute('data-height'); var chatFooter = chatShortcode.getAttribute('data-footer') === 'true'; var chatBar = chatShortcode.getAttribute('data-has-bar') === 'true'; var chatRounded = parseFloat(chatShortcode.getAttribute('data-chat_rounded')); var textRounded = parseFloat(chatShortcode.getAttribute('data-text_rounded')); var textHeight = parseFloat(chatShortcode.getAttribute('data-text_height')); var textInput = chatShortcode.getElementsByClassName('wpaicg-chat-shortcode-typing')[0]; // Set text area dimensions textInput.style.height = textHeight + 'px'; textInput.style.borderRadius = textRounded + 'px'; // Round the chat container chatShortcode.style.borderRadius = chatRounded + 'px'; chatShortcode.style.overflow = 'hidden'; // Fallback dimension if not supplied chatWidth = chatWidth !== null ? chatWidth : '350'; chatHeight = chatHeight !== null ? chatHeight : '400'; // FULLSCREEN MODE if (chatShortcode.classList.contains('wpaicg-fullscreened')) { // Occupy the full window parentWidth = wpaicgWindowWidth; // On very narrow mobile, subtract a bit more from height if (wpaicgWindowWidth < 480) { chatWidth = wpaicgWindowWidth; chatHeight = wpaicgWindowHeight - 40; } else { chatWidth = resolveDimension(chatWidth, parentWidth); chatHeight = resolveDimension(chatHeight, wpaicgWindowHeight); } chatShortcode.style.width = chatWidth + 'px'; chatShortcode.style.maxWidth = chatWidth + 'px'; chatShortcode.style.height = chatHeight + 'px'; chatShortcode.style.maxHeight = chatHeight + 'px'; chatShortcode.style.marginTop = 0; // Deduce space for header(s), action bar, etc. var deduceHeight = 69; // Base if (chatFooter) { deduceHeight += 60; // Footer } if (chatBar) { deduceHeight += 30; // Action bar } deduceHeight += 20; // Additional spacing // On mobile, add some extra if (wpaicgWindowWidth < 480) { deduceHeight += 20; } // NEW: If no footer and on mobile, add offset so text box is visible if (!chatFooter && wpaicgWindowWidth < 480) { deduceHeight += 60; } var chatMessages = chatShortcode.getElementsByClassName('wpaicg-chat-shortcode-messages')[0]; chatMessages.style.height = (chatHeight - deduceHeight - textHeight) + 'px'; // Force scroll to bottom so new messages or input are visible requestAnimationFrame(() => { requestAnimationFrame(() => { chatMessages.scrollTop = chatMessages.scrollHeight; }); }); } // NON-FULLSCREEN MODE else { // Resolve percentage or px for chatWidth if (chatWidth.indexOf('%') < 0) { if (chatWidth.indexOf('px') < 0) { chatWidth = parseFloat(chatWidth); } else { chatWidth = parseFloat(chatWidth.replace(/px/g, '')); } } else { chatWidth = parseFloat(chatWidth.replace(/%/g, '')); if (chatWidth < 100) { chatWidth = chatWidth * parentWidth / 100; } else { chatWidth = ''; } } // Resolve percentage or px for chatHeight if (chatHeight.indexOf('%') < 0) { if (chatHeight.indexOf('px') < 0) { chatHeight = parseFloat(chatHeight); } else { chatHeight = parseFloat(chatHeight.replace(/px/g, '')); } } else { chatHeight = parseFloat(chatHeight.replace(/%/g, '')); chatHeight = chatHeight * wpaicgWindowHeight / 100; } // Apply final computed width & height if (chatWidth !== '') { chatShortcode.style.width = chatWidth + 'px'; chatShortcode.style.maxWidth = chatWidth + 'px'; } else { chatShortcode.style.width = ''; chatShortcode.style.maxWidth = ''; } if (chatShortcode.classList.contains('wpaicg-fullscreened')) { chatShortcode.style.marginTop = 0; } else { chatShortcode.style.marginTop = ''; } var chatMessages = chatShortcode.getElementsByClassName('wpaicg-chat-shortcode-messages')[0]; var deduceHeight = 69; // Base if (chatFooter) { deduceHeight += 60; // Footer } if (chatBar) { deduceHeight += 30; // Action bar } deduceHeight += 20; // Additional spacing // On mobile, add some extra if (wpaicgWindowWidth < 480) { deduceHeight += 20; } // NEW: If no footer and on mobile, add offset so text box is visible if (!chatFooter && wpaicgWindowWidth < 480) { deduceHeight += 60; } // Subtract the typed text box height chatMessages.style.height = (chatHeight - deduceHeight - textHeight) + 'px'; } } } } /** * Adjusts the size of widget-based chat windows (.wpaicg_chat_widget_content * containing .wpaicg-chatbox). Also adds an extra offset on mobile when there's * no footer so the text box remains visible in fullscreen. */ function wpaicgChatBoxSize() { var wpaicgWindowWidth = window.innerWidth; var wpaicgWindowHeight = window.innerHeight; var chatWidgets = document.getElementsByClassName('wpaicg_chat_widget_content'); var isMobile = wpaicgWindowWidth <= 767; if (chatWidgets.length) { for (var i = 0; i < chatWidgets.length; i++) { var chatWidget = chatWidgets[i]; var chatbox = chatWidget.getElementsByClassName('wpaicg-chatbox')[0]; if (!chatbox) continue; var chatWidth = chatbox.getAttribute('data-width') || '350'; var chatHeight = chatbox.getAttribute('data-height') || '400'; var chatFooter = chatbox.getAttribute('data-footer') === 'true'; var chatboxBar = chatbox.getElementsByClassName('wpaicg-chatbox-action-bar'); var textHeight = parseFloat(chatbox.getAttribute('data-text_height')); // If the widget container is in fullscreen mode or if it's a mobile device if (chatWidget.classList.contains('wpaicg-fullscreened') || isMobile) { // For mobile or fullscreen, use the full window dimensions chatWidth = wpaicgWindowWidth; chatHeight = wpaicgWindowHeight - (isMobile ? 60 : 20); // Extra offset for mobile // Apply mobile-specific styling if (isMobile) { chatWidget.style.position = 'fixed'; chatWidget.style.top = '0'; chatWidget.style.left = '0'; chatWidget.style.right = '0'; chatWidget.style.bottom = '0'; chatWidget.style.zIndex = '999999'; chatWidget.style.width = '100%'; chatWidget.style.height = '100%'; chatWidget.style.maxWidth = 'none'; chatWidget.style.maxHeight = 'none'; } } else { // For desktop, use the user-defined dimensions chatWidth = resolveDimension(chatWidth, wpaicgWindowWidth); chatHeight = resolveDimension(chatHeight, wpaicgWindowHeight); } // Set dimensions for the chatbox and widget content chatbox.style.width = chatWidth + 'px'; chatbox.style.height = chatHeight + 'px'; chatWidget.style.width = chatWidth + 'px'; chatWidget.style.height = chatHeight + 'px'; // Calculate the content height by deducting header, footer, action bar etc. var actionBarHeight = chatboxBar.length ? 40 : 0; var footerHeight = chatFooter ? 60 : 0; var extraGap = (isMobile) ? 40 : 20; // If no footer on mobile, still add 60px so text box isn't hidden if (!chatFooter && isMobile) { footerHeight = 60; } var contentHeight = chatHeight - textHeight - actionBarHeight - footerHeight - extraGap; var messagesHeight = contentHeight - 20; var chatboxContent = chatWidget.getElementsByClassName('wpaicg-chatbox-content')[0]; var chatboxMessages = chatWidget.getElementsByClassName('wpaicg-chatbox-messages')[0]; chatboxContent.style.height = contentHeight + 'px'; chatboxMessages.style.height = messagesHeight + 'px'; // Ensure we are scrolled to bottom for existing messages chatboxMessages.scrollTop = chatboxMessages.scrollHeight; } } } function resolveDimension(value, totalSize) { if (value.includes('%')) { return parseFloat(value) / 100 * totalSize; } else if (value.includes('px')) { return parseFloat(value.replace('px', '')); } return parseFloat(value); // Default to parsing the value as pixels if no units are specified } function wpaicgChatInit() { 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>'; 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>'; var wpaicgChatStream; var wpaicgChatRec; var wpaicgInput; var wpaicgChatAudioContext = window.AudioContext || window['webkitAudioContext']; var wpaicgaudioContext; var wpaicgMicBtns = document.querySelectorAll('.wpaicg-mic-icon'); var wpaicgChatTyping = document.querySelectorAll('.wpaicg-chatbox-typing'); var wpaicgShortcodeTyping = document.querySelectorAll('.wpaicg-chat-shortcode-typing'); var wpaicgChatSend = document.querySelectorAll('.wpaicg-chatbox-send'); var wpaicgShortcodeSend = document.querySelectorAll('.wpaicg-chat-shortcode-send'); var wpaicgChatFullScreens = document.getElementsByClassName('wpaicg-chatbox-fullscreen'); var wpaicgChatCloseButtons = document.getElementsByClassName('wpaicg-chatbox-close-btn'); var wpaicgChatDownloadButtons = document.getElementsByClassName('wpaicg-chatbox-download-btn'); var wpaicg_chat_widget_toggles = document.getElementsByClassName('wpaicg_toggle'); var wpaicg_chat_widgets = document.getElementsByClassName('wpaicg_chat_widget'); var imageInputThumbnail = null; // Variable to store the image thumbnail function setupConversationStarters() { const starters = document.querySelectorAll('.wpaicg-conversation-starter'); starters.forEach(starter => { starter.addEventListener('click', function () { const messageText = starter.innerText || starter.textContent; const chatContainer = starter.closest('.wpaicg-chat-shortcode') || starter.closest('.wpaicg-chatbox'); const type = chatContainer.classList.contains('wpaicg-chat-shortcode') ? 'shortcode' : 'widget'; const typingInput = type === 'shortcode' ? chatContainer.querySelector('.wpaicg-chat-shortcode-typing') : chatContainer.querySelector('.wpaicg-chatbox-typing'); typingInput.value = messageText; wpaicgSendChatMessage(chatContainer, typingInput, type); // Hide all starters starters.forEach(starter => { starter.style.display = 'none'; }); }); }); } function maybeShowLeadForm(chat, chatId) { // Helper function to interpret truthy values function isTruthy(value) { if (value === null || value === undefined) return false; return value === '1' || value.toLowerCase() === 'true'; } // Get the necessary data attributes let leadCollectionEnabled = isTruthy(chat.getAttribute('data-lead-collection')); if (!leadCollectionEnabled) { return; } // Check if form has already been shown let leadFormShown = localStorage.getItem('wpaicg_lead_form_shown'); if (leadFormShown === '1') { return; } // Get the enable fields let enableLeadName = isTruthy(chat.getAttribute('data-enable-lead-name')); let enableLeadEmail = isTruthy(chat.getAttribute('data-enable-lead-email')); let enableLeadPhone = isTruthy(chat.getAttribute('data-enable-lead-phone')); // Check if at least one field is enabled if (!(enableLeadName || enableLeadEmail || enableLeadPhone)) { return; } // Get other data attributes let leadTitle = chat.getAttribute('data-lead-title') || 'Contact Information'; let leadNameLabel = chat.getAttribute('data-lead-name') || 'Name'; let leadEmailLabel = chat.getAttribute('data-lead-email') || 'Email'; let leadPhoneLabel = chat.getAttribute('data-lead-phone') || 'Phone'; let wpaicg_nonce = chat.getAttribute('data-nonce'); let aiBg = chat.getAttribute('data-ai-bg-color'); let fontSize = chat.getAttribute('data-fontsize'); let fontColor = chat.getAttribute('data-color'); let text_field_bgcolor = chat.getAttribute('data-bg_text_field'); let text_field_font_color = chat.getAttribute('data-bg_text_field_font_color'); let text_field_border_color = chat.getAttribute('data-bg_text_field_border_color'); // Now, construct the form in JavaScript // Define the form HTML within a chat message let formHtml = ` <li class="wpaicg-lead-form-message" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}"> <div class="wpaicg-lead-form-container"> <button class="wpaicg-lead-form-close" style="float:right;">×</button> <h2>${leadTitle}</h2> <form> `; if (enableLeadName) { formHtml += ` <div class="wpaicg-lead-form-field"> <label>${leadNameLabel}</label> <input type="text" name="lead_name"/> </div> `; } if (enableLeadEmail) { formHtml += ` <div class="wpaicg-lead-form-field"> <label>${leadEmailLabel}</label> <input type="email" name="lead_email" /> </div> `; } if (enableLeadPhone) { formHtml += ` <div class="wpaicg-lead-form-field"> <label>${leadPhoneLabel}</label> <input type="tel" name="lead_phone"/> </div> `; } // Add error message container formHtml += ` <div class="wpaicg-lead-form-error" style="color: red; display: none;"></div> `; formHtml += ` <div class="svg-submit-button-container"> <button type="submit" class="svg-submit-button"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill=${fontColor} aria-hidden="true" width="24" height="24"><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> </button> </div> </form> </div> </li> `; // Append the form after the last AI message let messagesList = chat.querySelector('.wpaicg-chatbox-messages') || chat.querySelector('.wpaicg-chat-shortcode-messages'); if (messagesList) { messagesList.insertAdjacentHTML('beforeend', formHtml); } // Add CSS styles let styles = ` .wpaicg-lead-form-message { list-style: none; padding: 10px; margin-bottom: 10px; position: relative; } .wpaicg-lead-form-container { display: block; } .wpaicg-lead-form-container h2 { margin-top: 0; font-size: 1.2em; margin-bottom: 10px; color: ${fontColor}; padding-right: 40px; } .wpaicg-lead-form-close { position: absolute; top: 10px; right: 10px; background: none; border: none; font-size: 18px; cursor: pointer; color: ${fontColor}; } .wpaicg-lead-form-field { margin-bottom: 15px; } .wpaicg-lead-form-field label { display: block; margin-bottom: 5px; } .wpaicg-lead-form-field input { color: ${text_field_font_color}; background-color: ${text_field_bgcolor}; width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid ${text_field_border_color}; } .svg-submit-button { background-color: ${aiBg}; border: none; cursor: pointer; padding: 10px; outline: none; } .svg-submit-button svg { fill: ${fontColor}; /* Dynamic color */ transition: fill 0.3s ease; } /* Spinner inside button */ .wpaicg-lead-spinner { display: inline-flex; justify-content: center; align-items: center; } .wpaicg-lead-spinner .dot { font-size: 16px; color: ${fontColor}; animation: jump 1s infinite; margin: 0 2px; } /* Align submit button to the right */ .svg-submit-button-container { text-align: right; /* This aligns the button to the right */ } @keyframes jump { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } } `; // Create a style element and append to the head let styleSheet = document.createElement("style"); styleSheet.innerText = styles; document.head.appendChild(styleSheet); // Add event listeners for form submission and close button let formMessage = messagesList.querySelector('.wpaicg-lead-form-message'); let closeButton = formMessage.querySelector('.wpaicg-lead-form-close'); let form = formMessage.querySelector('form'); let errorDiv = form.querySelector('.wpaicg-lead-form-error'); closeButton.addEventListener('click', function () { // Hide the form formMessage.remove(); // Set that form has been shown localStorage.setItem('wpaicg_lead_form_shown', '1'); }); form.addEventListener('submit', function (event) { event.preventDefault(); // Validate that at least one field is filled let nameInput = form.querySelector('input[name="lead_name"]'); let emailInput = form.querySelector('input[name="lead_email"]'); let phoneInput = form.querySelector('input[name="lead_phone"]'); let nameValue = nameInput ? nameInput.value.trim() : ''; let emailValue = emailInput ? emailInput.value.trim() : ''; let phoneValue = phoneInput ? phoneInput.value.trim() : ''; // If all fields are empty, display an error message and stop submission if (!nameValue && !emailValue && !phoneValue) { errorDiv.textContent = 'Please fill in at least one field.'; errorDiv.style.display = 'block'; return; // Stop form submission } else { errorDiv.textContent = ''; errorDiv.style.display = 'none'; } // Get the submit button let submitButton = form.querySelector('.svg-submit-button'); // Replace the button content with the spinner (jumping dots) submitButton.innerHTML = ` <div class="wpaicg-lead-spinner"> <span class="dot">•</span> <span class="dot">•</span> <span class="dot">•</span> </div> `; submitButton.disabled = true; // Disable the button while submitting // Collect the data let formData = new FormData(form); formData.append('action', 'wpaicg_submit_lead'); formData.append('_wpnonce', wpaicg_nonce); // Include chatId formData.append('chatId', chatId); // Send the data via AJAX fetch(wpaicgParams.ajax_url, { method: 'POST', body: formData }).then(response => response.json()) .then(data => { formMessage.remove(); // Mark form as shown localStorage.setItem('wpaicg_lead_form_shown', '1'); }).catch(error => { console.error('Error submitting lead form:', error); formMessage.remove(); localStorage.setItem('wpaicg_lead_form_shown', '1'); }).finally(() => { // Restore original SVG icon in the button and re-enable it submitButton.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="${fontColor}" class="bi bi-send" viewBox="0 0 16 16"> <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"/> <path d="M6.646 15.646l8-8a.5.5 0 0 0-.708-.708l-8 8a.5.5 0 1 0 .708.708z"/> <path d="M4.5 3.5a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6z"/> </svg> `; submitButton.disabled = false; }); }); } setupConversationStarters(); var wpaicgUserAudioEnabled = {}; // Object to track audio settings per chat instance // Function to set up event listeners on audio buttons function setupAudioButtons() { var wpaicgAudioButtons = document.querySelectorAll('.wpaicg-chatbox-audio-btn'); wpaicgAudioButtons.forEach(button => { var chatContainer = button.closest('.wpaicg-chat-shortcode, .wpaicg-chatbox'); var botId = chatContainer.getAttribute('data-bot-id') || '0'; var chatType = chatContainer.getAttribute('data-type') || 'shortcode'; // Create a unique key for each bot based on bot-id and chat-type var audioKey = `audio_${chatType}_${botId}`; // Get muted state from data attribute (1 = muted, 0 or absent = unmuted) var voiceMutedByDefault = chatContainer.getAttribute('data-voice-muted-by-default') === '1'; // Initialize the audio button state based on the default mode if (voiceMutedByDefault) { // Audio is muted by default wpaicgUserAudioEnabled[audioKey] = false; button.classList.remove('wpaicg-audio-enabled'); 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 } else { // Audio is not muted by default wpaicgUserAudioEnabled[audioKey] = true; button.classList.add('wpaicg-audio-enabled'); 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 } // Set up click listener to toggle audio state button.addEventListener('click', function () { wpaicgUserAudioEnabled[audioKey] = !wpaicgUserAudioEnabled[audioKey]; if (wpaicgUserAudioEnabled[audioKey]) { // Audio is now enabled button.classList.add('wpaicg-audio-enabled'); 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 } else { // Audio is now disabled button.classList.remove('wpaicg-audio-enabled'); 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 } }); }); } // Call the setup function setupAudioButtons(); var imageIcon = document.querySelector('.wpaicg-img-icon'); var spinner = document.querySelector('.wpaicg-img-spinner'); var thumbnailPlaceholder = document.querySelector('.wpaicg-thumbnail-placeholder'); if (imageIcon) { imageIcon.addEventListener('click', function () { var imageInput = document.getElementById('imageUpload'); imageInput.click(); }); } var imageInput = document.getElementById('imageUpload'); if (imageInput) { imageInput.addEventListener('change', function () { if (this.files && this.files[0]) { var file = this.files[0]; // Store the file reference here // Show the spinner and hide the image icon imageIcon.style.display = 'none'; spinner.style.display = 'inline-block'; imageIcon.title = file.name; // Optional: show image name on hover // Hide the spinner and show the image icon and thumbnail after a delay setTimeout(function () { spinner.style.display = 'none'; imageIcon.style.display = 'inline-block'; // Re-display image icon // Now set the thumbnail image using the stored file reference thumbnailPlaceholder.style.backgroundImage = `url(${URL.createObjectURL(file)})`; thumbnailPlaceholder.style.backgroundSize = 'cover'; thumbnailPlaceholder.style.backgroundPosition = 'center'; thumbnailPlaceholder.style.backgroundRepeat = 'no-repeat'; thumbnailPlaceholder.style.display = 'inline-block'; // Display thumbnail }, 2000); } }); } // Function to set up event listeners on all clear chat buttons function setupClearChatButtons() { var wpaicgChatClearButtons = document.querySelectorAll('.wpaicg-chatbox-clear-btn'); wpaicgChatClearButtons.forEach(button => { button.addEventListener('click', function () { var chatContainer = button.closest('[data-bot-id]'); // Finds the nearest parent with 'data-bot-id' if (chatContainer) { var botId = chatContainer.getAttribute('data-bot-id') || '0'; var clientId = localStorage.getItem('wpaicg_chat_client_id'); clearChatHistory(botId, clientId, chatContainer); } }); }); } // Function to clear the chat history from local storage and the display function clearChatHistory(botId, clientId, chatContainer) { var isCustomBot = botId !== '0'; var type = chatContainer.classList.contains('wpaicg-chat-shortcode') ? 'shortcode' : 'widget'; // Determine the type based on class // First, get the active conversation key const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientId}`; const currentConversationKey = localStorage.getItem(activeConversationKey); if (currentConversationKey) { // Remove the active conversation from localStorage localStorage.removeItem(currentConversationKey); // Also remove its timestamp localStorage.removeItem(`${currentConversationKey}_timestamp`); } else { // Fallback if no active conversation: clear using base key var historyKey = isCustomBot ? `wpaicg_chat_history_custom_bot_${botId}_${clientId}` : `wpaicg_chat_history_${type}_${clientId}`; // Adjust history key based on type // Get all keys that start with this prefix for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && key.startsWith(historyKey)) { localStorage.removeItem(key); } } } // Clear the chat display var chatBoxSelector = '.wpaicg-chatbox-messages, .wpaicg-chat-shortcode-messages'; // Generalized selector for both types var chatBox = chatContainer.querySelector(chatBoxSelector); if (chatBox) { chatBox.innerHTML = ''; // Clear the chat box visually } // delete wpaicg_lead_form_shown if exists localStorage.removeItem('wpaicg_lead_form_shown'); // Check if wpaicg_thread_list exists in local storage (for assistant API) var threadList = localStorage.getItem('wpaicg_thread_list'); if (threadList) { try { var threadListObj = JSON.parse(threadList); // Construct the key for the thread to be deleted var threadKey = isCustomBot ? `custom_bot_${botId}_${clientId}` : `${type}_${clientId}`; // If we have a specific active thread key, clear only that one const activeThreadKey = `wpaicg_current_thread_${botId}_${clientId}`; const currentThreadKey = localStorage.getItem(activeThreadKey); if (currentThreadKey && threadListObj[currentThreadKey]) { // Delete this specific thread delete threadListObj[currentThreadKey]; } else { // Otherwise delete all threads matching the base prefix for (let existingKey in threadListObj) { if (existingKey.startsWith(threadKey)) { delete threadListObj[existingKey]; } } } // Update the local storage with the modified thread list localStorage.setItem('wpaicg_thread_list', JSON.stringify(threadListObj)); } catch (error) { console.error('Error parsing wpaicg_thread_list:', error); } } // Show conversation starters after clearing showConversationStarters(chatContainer); // Refresh the conversation list in the sidebar loadConversationList(); } // Call this function once your DOM is fully loaded or at the end of your script setupClearChatButtons(); if (wpaicg_chat_widget_toggles !== null && wpaicg_chat_widgets.length) { for (var i = 0; i < wpaicg_chat_widget_toggles.length; i++) { var wpaicg_chat_widget_toggle = wpaicg_chat_widget_toggles[i]; var wpaicg_chat_widget = wpaicg_chat_widget_toggle.closest('.wpaicg_chat_widget'); wpaicg_chat_widget_toggle.addEventListener('click', function (e) { e.preventDefault(); wpaicg_chat_widget_toggle = e.currentTarget; if (wpaicg_chat_widget_toggle.classList.contains('wpaicg_widget_open')) { wpaicg_chat_widget_toggle.classList.remove('wpaicg_widget_open'); wpaicg_chat_widget.classList.remove('wpaicg_widget_open'); } else { wpaicg_chat_widget.classList.add('wpaicg_widget_open'); wpaicg_chat_widget_toggle.classList.add('wpaicg_widget_open'); // Check if it's a mobile device (width <= 767px) if (window.innerWidth <= 767) { // Get the widget content var widgetContent = wpaicg_chat_widget.querySelector('.wpaicg_chat_widget_content'); // Set it to fullscreen for mobile if (widgetContent && !widgetContent.classList.contains('wpaicg-fullscreened')) { var fullscreenBtn = widgetContent.querySelector('.wpaicg-chatbox-fullscreen'); if (fullscreenBtn) { // Trigger fullscreen mode wpaicgFullScreen(fullscreenBtn); // Hide the fullscreen button on mobile fullscreenBtn.style.display = 'none'; } } } } }); } } if (wpaicgChatDownloadButtons.length) { for (var i = 0; i < wpaicgChatDownloadButtons.length; i++) { var wpaicgChatDownloadButton = wpaicgChatDownloadButtons[i]; wpaicgChatDownloadButton.addEventListener('click', function (e) { wpaicgChatDownloadButton = e.currentTarget; var type = wpaicgChatDownloadButton.getAttribute('data-type'); var wpaicgWidgetContent, listMessages; if (type === 'shortcode') { wpaicgWidgetContent = wpaicgChatDownloadButton.closest('.wpaicg-chat-shortcode'); listMessages = wpaicgWidgetContent.getElementsByClassName('wpaicg-chat-shortcode-messages'); } else { wpaicgWidgetContent = wpaicgChatDownloadButton.closest('.wpaicg_chat_widget_content'); listMessages = wpaicgWidgetContent.getElementsByClassName('wpaicg-chatbox-messages'); } if (listMessages.length) { var listMessage = listMessages[0]; var messages = []; var chatMessages = listMessage.getElementsByTagName('li'); if (chatMessages.length) { for (var i = 0; i < chatMessages.length; i++) { messages.push(chatMessages[i].innerText.replace("\n", ' ')); } } var messagesDownload = messages.join("\n"); var element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(messagesDownload)); element.setAttribute('download', 'chat.txt'); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } }) } } if (wpaicgChatCloseButtons.length) { for (var i = 0; i < wpaicgChatCloseButtons.length; i++) { var wpaicgChatCloseButton = wpaicgChatCloseButtons[i]; wpaicgChatCloseButton.addEventListener('click', function (e) { wpaicgChatCloseButton = e.currentTarget; var wpaicgWidgetContent = wpaicgChatCloseButton.closest('.wpaicg_chat_widget_content'); var chatbox = wpaicgWidgetContent.closest('.wpaicg_chat_widget'); if (wpaicgWidgetContent.classList.contains('wpaicg-fullscreened')) { var fullScreenBtn = wpaicgWidgetContent.getElementsByClassName('wpaicg-chatbox-fullscreen')[0]; wpaicgFullScreen(fullScreenBtn); } chatbox.getElementsByClassName('wpaicg_toggle')[0].click(); }) } } /** * Toggle fullscreen mode for either a shortcode chat or a widget chat. * Ensures that upon restore, the prior sizing/layout is fully restored, * including footer, text box, etc. * * @param {HTMLElement} btn - The clicked fullscreen button. */ function wpaicgFullScreen(btn) { const type = btn.getAttribute('data-type'); const isExitingFullscreen = btn.classList.contains('wpaicg-fullscreen-box'); const isMobile = window.innerWidth <= 767; if (type === 'shortcode') { const wpaicgChatShortcode = btn.closest('.wpaicg-chat-shortcode'); if (!wpaicgChatShortcode) return; // Exiting fullscreen if (isExitingFullscreen) { btn.classList.remove('wpaicg-fullscreen-box'); wpaicgChatShortcode.classList.remove('wpaicg-fullscreened'); // Restore old inline style const oldInlineStyle = wpaicgChatShortcode.getAttribute('data-old-inline-style') || ''; wpaicgChatShortcode.setAttribute('style', oldInlineStyle); wpaicgChatShortcode.removeAttribute('data-old-inline-style'); // Restore old data-width / data-height const oldDataWidth = wpaicgChatShortcode.getAttribute('data-old-width'); const oldDataHeight = wpaicgChatShortcode.getAttribute('data-old-height'); if (oldDataWidth !== null) { wpaicgChatShortcode.setAttribute('data-width', oldDataWidth); wpaicgChatShortcode.removeAttribute('data-old-width'); } if (oldDataHeight !== null) { wpaicgChatShortcode.setAttribute('data-height', oldDataHeight); wpaicgChatShortcode.removeAttribute('data-old-height'); } // Rerun sizing wpaicgChatShortcodeSize(); } // Entering fullscreen else { btn.classList.add('wpaicg-fullscreen-box'); wpaicgChatShortcode.classList.add('wpaicg-fullscreened'); // Save existing style const currentInline = wpaicgChatShortcode.getAttribute('style') || ''; wpaicgChatShortcode.setAttribute('data-old-inline-style', currentInline); // Save data-width / data-height const currentDataWidth = wpaicgChatShortcode.getAttribute('data-width') || ''; const currentDataHeight = wpaicgChatShortcode.getAttribute('data-height') || ''; wpaicgChatShortcode.setAttribute('data-old-width', currentDataWidth); wpaicgChatShortcode.setAttribute('data-old-height', currentDataHeight); // Apply fixed positioning for fullscreen wpaicgChatShortcode.style.position = 'fixed'; wpaicgChatShortcode.style.top = '0'; wpaicgChatShortcode.style.left = '0'; wpaicgChatShortcode.style.zIndex = '999999999'; wpaicgChatShortcode.style.width = '100%'; // Use slight offset so the text area & footer are fully visible wpaicgChatShortcode.style.height = 'calc(100vh - 20px)'; wpaicgChatShortcode.style.overflowY = 'auto'; // Update data-width / data-height to reflect 100% fill wpaicgChatShortcode.setAttribute('data-width', '100%'); // Use 100% but we do an inline "calc(100vh - 20px)" so that the JS dimensioning can handle subtracting wpaicgChatShortcode.setAttribute('data-height', '100%'); // Re-run sizing wpaicgChatShortcodeSize(); } } else { // For a widget const wpaicgWidgetContent = btn.closest('.wpaicg_chat_widget_content'); if (!wpaicgWidgetContent) return; const chatbox = wpaicgWidgetContent.querySelector('.wpaicg-chatbox'); if (!chatbox) return; // Exiting fullscreen if (isExitingFullscreen) { btn.classList.remove('wpaicg-fullscreen-box'); wpaicgWidgetContent.classList.remove('wpaicg-fullscreened'); // Don't restore original dimensions if on mobile if (!isMobile) { // Restore old inline style for wpaicg_chat_widget_content const oldWidgetStyle = wpaicgWidgetContent.getAttribute('data-old-inline-style') || ''; wpaicgWidgetContent.setAttribute('style', oldWidgetStyle); wpaicgWidgetContent.removeAttribute('data-old-inline-style'); // Restore old inline style for chatbox const oldChatboxStyle = chatbox.getAttribute('data-old-inline-style') || ''; chatbox.setAttribute('style', oldChatboxStyle); chatbox.removeAttribute('data-old-inline-style'); // Restore data-width / data-height const oldWidth = chatbox.getAttribute('data-old-width'); const oldHeight = chatbox.getAttribute('data-old-height'); if (oldWidth !== null) { chatbox.setAttribute('data-width', oldWidth); chatbox.removeAttribute('data-old-width'); } if (oldHeight !== null) { chatbox.setAttribute('data-height', oldHeight); chatbox.removeAttribute('data-old-height'); } } // Re-run the widget sizing wpaicgChatBoxSize(); } // Entering fullscreen else { btn.classList.add('wpaicg-fullscreen-box'); wpaicgWidgetContent.classList.add('wpaicg-fullscreened'); // Save wpaicg_chat_widget_content style const currentWidgetStyle = wpaicgWidgetContent.getAttribute('style') || ''; wpaicgWidgetContent.setAttribute('data-old-inline-style', currentWidgetStyle); // Save chatbox style const currentChatboxStyle = chatbox.getAttribute('style') || ''; chatbox.setAttribute('data-old-inline-style', currentChatboxStyle); // Also store data-width / data-height const currentDataWidth = chatbox.getAttribute('data-width') || ''; const currentDataHeight = chatbox.getAttribute('data-height') || ''; chatbox.setAttribute('data-old-width', currentDataWidth); chatbox.setAttribute('data-old-height', currentDataHeight); // Apply fullscreen styling with mobile-specific adjustments wpaicgWidgetContent.style.position = 'fixed'; wpaicgWidgetContent.style.top = '0'; wpaicgWidgetContent.style.left = '0'; wpaicgWidgetContent.style.width = '100%'; wpaicgWidgetContent.style.height = isMobile ? '100%' : 'calc(100vh - 20px)'; wpaicgWidgetContent.style.zIndex = '999999999'; wpaicgWidgetContent.style.overflowY = 'auto'; // Expand chatbox to fill container chatbox.style.width = '100%'; chatbox.style.height = isMobile ? '100%' : 'calc(100vh - 20px)'; chatbox.style.overflowY = 'auto'; // Set maximum sizes for mobile if (isMobile) { chatbox.style.maxWidth = '100%'; chatbox.style.maxHeight = '100%'; wpaicgWidgetContent.style.maxWidth = '100%'; wpaicgWidgetContent.style.maxHeight = '100%'; // Hide the fullscreen button on mobile devices when in fullscreen mode btn.style.display = 'none'; } // Update data attributes chatbox.setAttribute('data-width', '100%'); chatbox.setAttribute('data-height', '100%'); // Re-run widget sizing wpaicgChatBoxSize(); } } } if (wpaicgChatFullScreens.length) { for (var i = 0; i < wpaicgChatFullScreens.length; i++) { var wpaicgChatFullScreen = wpaicgChatFullScreens[i]; wpaicgChatFullScreen.addEventListener('click', function (e) { wpaicgFullScreen(e.currentTarget); }) } } function resizeChatWidgets() { if (wpaicg_chat_widgets !== null && wpaicg_chat_widgets.length) { for (var i = 0; i < wpaicg_chat_widgets.length; i++) { var wpaicg_chat_widget = wpaicg_chat_widgets[i]; if (window.innerWidth < 350) { wpaicg_chat_widget.getElementsByClassName('wpaicg-chatbox')[0].style.width = window.innerWidth + 'px'; wpaicg_chat_widget.getElementsByClassName('wpaicg_chat_widget_content')[0].style.width = window.innerWidth + 'px'; } } } } // Function to check if device is mobile based on width function isMobileDevice() { return window.innerWidth <= 767; } // Add a resize listener to handle orientation changes window.addEventListener('resize', function () { wpaicgChatBoxSize(); wpaicgChatShortcodeSize(); resizeChatWidgets(); // Check if device changed from mobile to desktop if (window.innerWidth > 767) { // Find all fullscreen buttons in fullscreened widgets document.querySelectorAll('.wpaicg-fullscreened .wpaicg-chatbox-fullscreen').forEach(btn => { // Make sure the button is visible on desktop btn.style.display = ''; }); } else { // Hide fullscreen button on mobile in fullscreen mode document.querySelectorAll('.wpaicg-fullscreened .wpaicg-chatbox-fullscreen').forEach(btn => { btn.style.display = 'none'; }); } // When orientation changes, ensure mobile fullscreen is maintained if (isMobileDevice()) { const openWidgets = document.querySelectorAll('.wpaicg_widget_open .wpaicg_chat_widget_content'); openWidgets.forEach(widget => { if (!widget.classList.contains('wpaicg-fullscreened')) { const fullscreenBtn = widget.querySelector('.wpaicg-chatbox-fullscreen'); if (fullscreenBtn) { wpaicgFullScreen(fullscreenBtn); // Hide the fullscreen button fullscreenBtn.style.display = 'none'; } } }); } }); wpaicgChatShortcodeSize(); wpaicgChatBoxSize(); resizeChatWidgets(); function wpaicgescapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function wpaicgstartChatRecording() { let constraints = { audio: true, video: false } navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { wpaicgaudioContext = new wpaicgChatAudioContext(); wpaicgChatStream = stream; wpaicgInput = wpaicgaudioContext.createMediaStreamSource(stream); wpaicgChatRec = new Recorder(wpaicgInput, { numChannels: 1 }); wpaicgChatRec.record(); }) } function wpaicgstopChatRecording(mic) { wpaicgChatRec.stop(); wpaicgChatStream.getAudioTracks()[0].stop(); wpaicgChatRec.exportWAV(function (blob) { let type = mic.getAttribute('data-type'); let parentChat; let chatContent; let chatTyping; if (type === 'widget') { parentChat = mic.closest('.wpaicg-chatbox'); chatContent = parentChat.querySelectorAll('.wpaicg-chatbox-content')[0]; chatTyping = parentChat.querySelectorAll('.wpaicg-chatbox-typing')[0]; } else { parentChat = mic.closest('.wpaicg-chat-shortcode'); chatContent = parentChat.querySelectorAll('.wpaicg-chat-shortcode-content')[0]; chatTyping = parentChat.querySelectorAll('.wpaicg-chat-shortcode-typing')[0]; } wpaicgSendChatMessage(parentChat, chatTyping, type, blob); }); } // Function to generate a random string function generateRandomString(length) { let result = ''; let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let charactersLength = characters.length; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } function setupButtonListeners(isCopyEnabled, isFeedbackEnabled, class_ai_item, emptyClipboardSVG, checkedClipboardSVG, thumbsUpSVG, thumbsDownSVG, showFeedbackModal, aiBg, fontColor, usrBg, chat, wpaicg_nonce, chatbot_identity) { let hideTimeout; // Show buttons on hover or touchstart of the icon container jQuery(document).on('mouseenter touchstart', `li.${class_ai_item} .wpaicg-icon-container`, function (event) { clearTimeout(hideTimeout); // Prevent triggering on scroll or unintended touches if (event.type === 'touchstart') { event.stopPropagation(); } const buttons = jQuery(this).find('.wpaicg-copy-button, .wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button'); // Display copy button if enabled if (isCopyEnabled) { buttons.filter('.wpaicg-copy-button').css({ display: 'inline-block', opacity: 1, visibility: 'visible' }); } // Display feedback buttons if enabled if (isFeedbackEnabled) { buttons.filter('.wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button').css({ display: 'inline-block', opacity: 1, visibility: 'visible' }); } }); // Hide buttons after leaving the icon container jQuery(document).on('mouseleave touchend', `li.${class_ai_item} .wpaicg-icon-container`, function () { const buttons = jQuery(this).find('.wpaicg-copy-button, .wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button'); hideTimeout = setTimeout(() => { buttons.css({ opacity: 0, visibility: 'hidden', display: 'none' }); }, 2000); }); // Copy text functionality remains unchanged jQuery(document).on('click', '.wpaicg-copy-button', function () { const chatId = jQuery(this).data('chat-id'); const messageText = document.getElementById(chatId).innerText; navigator.clipboard.writeText(messageText).then(() => { // Change icon to check mark jQuery(this).html(checkedClipboardSVG); setTimeout(() => { // Reset icon to original after 2 seconds jQuery(this).html(emptyClipboardSVG); }, 2000); }).catch(err => console.error('Failed to copy text: ', err)); }); // Feedback functionality remains unchanged jQuery(document).on('click', '.wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button', function () { const feedbackType = jQuery(this).hasClass('wpaicg-thumbs-up-button') ? 'up' : 'down'; const chatId = jQuery(this).data('chat-id').replace('wpaicg-chat-message-', ''); showFeedbackModal(feedbackType, chatId, aiBg, fontColor, usrBg, chat, wpaicg_nonce, chatbot_identity); }); } function showFeedbackModal(feedbackType, chatId, bgColor, textColor, usrBg, chat, wpaicg_nonce, chatbot_identity) { const chatWidget = jQuery('.wpaicg_chat_widget'); const feedbackTitle = chat.getAttribute('data-feedback_title') || 'Feedback'; const feedbackMessage = chat.getAttribute('data-feedback_message') || 'Please provide details: (optional)'; const feedbackSuccessMessage = chat.getAttribute('data-feedback_success') || 'Thank you for your feedback!'; const chatShortcode = jQuery(chat).closest('.wpaicg-chat-shortcode'); const wasFullscreen = chatShortcode.hasClass('wpaicg-fullscreened'); if (wasFullscreen) { // Exit fullscreen mode before showing feedback modal const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen'); wpaicgFullScreen(fullScreenBtn[0]); // Exit fullscreen } if (chatWidget.hasClass('wpaicg_widget_open')) { chatWidget.data('was-open', true); chatWidget.removeClass('wpaicg_widget_open'); } else { chatWidget.data('was-open', false); } const modalHtml = ` <style> @keyframes wpaicg-feedback-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .wpaicg-feedback-spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid ${textColor}; border-top: 2px solid ${bgColor}; border-radius: 50%; animation: wpaicg-feedback-spin 1s linear infinite; } .wpaicg-feedback-message { color: ${textColor}; } </style> <div class="wpaicg-feedback-modal-overlay"> <div class="wpaicg-feedback-modal" style="background-color:${bgColor};color:${textColor};position:relative;"> <button class="wpaicg-feedback-modal-close" style="position:absolute; top:10px; right:10px; background:none; border:none; color:${textColor}; font-size:18px; cursor:pointer;">×</button> <h2 style="background-color:${bgColor};color:${textColor};">${feedbackTitle}</h2> <p>${feedbackMessage}</p> <textarea class="wpaicg-feedback-textarea"></textarea> <div class="wpaicg-feedback-modal-buttons"> <div class="wpaicg-feedback-message" style="display:none;"></div> <button class="wpaicg-feedback-modal-submit" style="background-color:${usrBg};color:${textColor};border:none;" data-feedback-type="${feedbackType}" data-chat-id="${chatId}"> Submit <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> </button> </div> </div> </div> `; jQuery('body').append(modalHtml); jQuery('.wpaicg-feedback-modal-close').on('click', function () { jQuery('.wpaicg-feedback-modal-overlay').fadeOut(300, function () { jQuery(this).remove(); if (wasFullscreen) { // Restore fullscreen after feedback modal is closed const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen'); wpaicgFullScreen(fullScreenBtn[0]); // Re-enter fullscreen } if (chatWidget.data('was-open')) { chatWidget.addClass('wpaicg_widget_open'); } }); }); jQuery('.wpaicg-feedback-modal-submit').on('click', function () { const modal = jQuery(this).closest('.wpaicg-feedback-modal'); const feedbackText = modal.find('.wpaicg-feedback-textarea').val(); const feedbackType = jQuery(this).data('feedback-type'); const chatId = jQuery(this).data('chat-id'); const nonce = wpaicg_nonce; const submitButton = jQuery(this); const spinner = submitButton.find('.wpaicg-feedback-spinner'); const feedbackMessageElement = modal.find('.wpaicg-feedback-message'); spinner.show(); submitButton.prop('disabled', true); jQuery.ajax({ url: wpaicgParams.ajax_url, method: 'POST', data: { action: 'wpaicg_submit_feedback', chatId: chatId, feedbackType: feedbackType, feedbackDetails: feedbackText, _wpnonce: nonce, chatbot_id: chatbot_identity, }, success: function (response) { feedbackMessageElement.html(`<span style="color:${textColor};">${feedbackSuccessMessage}</span>`).fadeIn(300); setTimeout(() => { jQuery('.wpaicg-feedback-modal-overlay').fadeOut(300, function () { jQuery(this).remove(); if (chatWidget.data('was-open')) { chatWidget.addClass('wpaicg_widget_open'); } if (wasFullscreen) { const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen'); wpaicgFullScreen(fullScreenBtn[0]); // Re-enter fullscreen } }); }, 2000); }, error: function (error) { feedbackMessageElement.html(`<span style="color:${textColor};">Error. Please try again later.</span>`).fadeIn(300); setTimeout(() => { jQuery('.wpaicg-feedback-modal-overlay').fadeOut(300, function () { jQuery(this).remove(); if (chatWidget.data('was-open')) { chatWidget.addClass('wpaicg_widget_open'); } if (wasFullscreen) { const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen'); wpaicgFullScreen(fullScreenBtn[0]); // Re-enter fullscreen } }); }, 2000); }, complete: function () { spinner.hide(); submitButton.prop('disabled', false); } }); }); } /** * Sends a user message (text or audio) to the server, handles response streaming or non-streaming, * and updates the conversation history in localStorage. * * @param {HTMLElement} chat - The parent chat container (wpaicg-chat-shortcode or wpaicg-chatbox). * @param {HTMLTextAreaElement} typing - The textarea input element. * @param {string} type - Either "shortcode" or "widget", indicating chat type. * @param {Blob} [blob] - Optional audio blob if the user recorded microphone input. */ function wpaicgSendChatMessage(chat, typing, type, blob) { hideConversationStarters(); // Remove the lead form if it exists var leadFormMessage = chat.querySelector('.wpaicg-lead-form-message'); if (leadFormMessage) { leadFormMessage.remove(); // Optionally, set 'wpaicg_lead_form_shown' to '1' so it doesn't show again localStorage.setItem('wpaicg_lead_form_shown', '1'); } let botIdAudio = chat.getAttribute('data-bot-id') || '0'; let chatTypeAudio = chat.getAttribute('data-type') || 'shortcode'; let audioKey = `audio_${chatTypeAudio}_${botIdAudio}`; // Use the new key for audio state let isAudioEnabledByUser = wpaicgUserAudioEnabled[audioKey]; // Get the audio state for this bot instance let userVoiceControl = chat.getAttribute('data-user-voice-control'); let wpaicg_box_typing = typing; let wpaicg_ai_thinking, wpaicg_messages_box, class_user_item, class_ai_item; let wpaicgMessage = ''; let wpaicgData = new FormData(); let wpaicg_nonce = chat.getAttribute('data-nonce'); let wpaicg_bot_id = parseInt(chat.getAttribute('data-bot-id')); let wpaicg_user_bg = chat.getAttribute('data-user-bg-color'); let wpaicg_font_size = chat.getAttribute('data-fontsize'); let wpaicg_speech = chat.getAttribute('data-speech'); let wpaicg_voice = chat.getAttribute('data-voice'); let elevenlabs_model = chat.getAttribute('data-elevenlabs-model'); if (elevenlabs_model === null || elevenlabs_model === undefined) { elevenlabs_model = chat.getAttribute('data-elevenlabs_model'); } let elevenlabs_voice = chat.getAttribute('data-elevenlabs-voice'); if (elevenlabs_voice === null || elevenlabs_voice === undefined) { elevenlabs_voice = chat.getAttribute('data-elevenlabs_voice'); } let wpaicg_voice_error = chat.getAttribute('data-voice-error'); let wpaicg_typewriter_effect = chat.getAttribute('data-typewriter-effect'); let wpaicg_typewriter_speed = chat.getAttribute('data-typewriter-speed'); let url = chat.getAttribute('data-url'); let post_id = chat.getAttribute('data-post-id'); let wpaicg_ai_bg = chat.getAttribute('data-ai-bg-color'); let wpaicg_font_color = chat.getAttribute('data-color'); let voice_service = chat.getAttribute('data-voice_service'); let voice_language = chat.getAttribute('data-voice_language'); let voice_name = chat.getAttribute('data-voice_name'); let voice_device = chat.getAttribute('data-voice_device'); let openai_model = chat.getAttribute('data-openai_model'); let openai_voice = chat.getAttribute('data-openai_voice'); let openai_output_format = chat.getAttribute('data-openai_output_format'); let openai_voice_speed = chat.getAttribute('data-openai_voice_speed'); let openai_stream_nav = chat.getAttribute('data-openai_stream_nav'); let voice_speed = chat.getAttribute('data-voice_speed'); let voice_pitch = chat.getAttribute('data-voice_pitch'); var chat_pdf = chat.getAttribute('data-pdf'); // Handle image upload var imageInput = document.getElementById('imageUpload'); var imageUrl = ''; // Variable to store the URL of the uploaded image for preview if (imageInput) { if (imageInput.files && imageInput.files[0]) { var validImageTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp', 'image/gif']; if (!validImageTypes.includes(imageInput.files[0].type)) { alert('Invalid file type. Only PNG, JPEG, WEBP, and non-animated GIF images are allowed.'); return; } // Append image file to FormData object wpaicgData.append('image', imageInput.files[0], imageInput.files[0].name); // Create a URL for the uploaded image file for preview imageUrl = URL.createObjectURL(imageInput.files[0]); } } if (type === 'widget') { wpaicg_ai_thinking = chat.getElementsByClassName('wpaicg-bot-thinking')[0]; wpaicg_messages_box = chat.getElementsByClassName('wpaicg-chatbox-messages')[0]; class_user_item = 'wpaicg-chat-user-message'; class_ai_item = 'wpaicg-chat-ai-message'; wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight; // Retrieve all message elements const messages = wpaicg_messages_box.querySelectorAll('li'); // Ensure messages exist and scroll to the last message if (messages.length > 0) { messages[messages.length - 1].scrollIntoView(); } } else { wpaicg_ai_thinking = chat.getElementsByClassName('wpaicg-bot-thinking')[0]; wpaicg_messages_box = chat.getElementsByClassName('wpaicg-chat-shortcode-messages')[0]; class_user_item = 'wpaicg-user-message'; class_ai_item = 'wpaicg-ai-message'; } wpaicg_ai_thinking.style.display = 'block'; let wpaicg_question = wpaicgescapeHtml(wpaicg_box_typing.value); if (!wpaicg_question.trim() && blob === undefined) { wpaicg_ai_thinking.style.display = 'none'; return; // Exit the function if no message or blob is provided } wpaicgMessage += '<li class="' + class_user_item + '" style="background-color:' + wpaicg_user_bg + ';font-size: ' + wpaicg_font_size + 'px;color: ' + wpaicg_font_color + '">'; wpaicgData.append('_wpnonce', wpaicg_nonce); wpaicgData.append('post_id', post_id); if (chat_pdf && chat_pdf !== null) { wpaicgData.append('namespace', chat_pdf); } wpaicgData.append('url', url); if (type === 'widget') { wpaicgData.append('action', 'wpaicg_chatbox_message'); } else { wpaicgData.append('action', 'wpaicg_chat_shortcode_message'); } if (blob !== undefined) { let url = URL.createObjectURL(blob); wpaicgMessage += '<audio src="' + url + '" controls="true"></audio>'; wpaicgData.append('audio', blob, 'wpaicg-chat-recording.wav'); } else if (wpaicg_question !== '') { wpaicgData.append('message', wpaicg_question); wpaicgMessage += wpaicg_question.replace(/\n/g, '<br>'); } wpaicgData.append('bot_id', wpaicg_bot_id); wpaicgMessage += '</li>'; // If an image URL is available, add an <img> tag to display the image if (imageUrl !== '') { wpaicgMessage += '<li class="' + class_user_item + '" style="background-color:' + wpaicg_user_bg + ';font-size: ' + wpaicg_font_size + 'px;color: ' + wpaicg_font_color + '">'; wpaicgMessage += '<div style="max-width: 300px; height: auto; display: flex;">'; wpaicgMessage += '<img src="' + imageUrl + '" style="max-width: 100%; height: auto;" onload="this.parentElement.parentElement.parentElement.scrollTop = this.parentElement.parentElement.parentElement.scrollHeight;">'; wpaicgMessage += '</div>'; // Closing the div tag wpaicgMessage += '</li>'; } wpaicg_messages_box.innerHTML += wpaicgMessage; wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight; // Hide the thumbnail placeholder var thumbnailPlaceholder = document.querySelector('.wpaicg-thumbnail-placeholder'); if (thumbnailPlaceholder) { thumbnailPlaceholder.style.display = 'none'; // Hide the thumbnail after message is sent } // Reset the image input after sending the message if imageInput exists first if (imageInput) { imageInput.value = ''; } let chat_type = chat.getAttribute('data-type'); let stream_nav; let chatbot_identity; // Check if it's a bot with dynamic ID if (wpaicg_bot_id && wpaicg_bot_id !== "0") { stream_nav = openai_stream_nav; chatbot_identity = 'custom_bot_' + wpaicg_bot_id; } else { // Check if it's a shortcode or widget based on chat_type if (chat_type === "shortcode") { stream_nav = chat.getAttribute('data-openai_stream_nav'); chatbot_identity = 'shortcode'; } else if (chat_type === "widget") { stream_nav = chat.getAttribute('data-openai_stream_nav'); chatbot_identity = 'widget'; } } wpaicgData.append('chatbot_identity', chatbot_identity); // Check for existing client_id in localStorage let clientID = localStorage.getItem('wpaicg_chat_client_id'); if (!clientID) { // Generate and store a new client ID if not found clientID = generateRandomString(10); // Generate a 10 character string localStorage.setItem('wpaicg_chat_client_id', clientID); } let botId = chat.getAttribute('data-bot-id') || '0'; let assistantEnabled = chat.getAttribute('data-assistant-enabled') === 'true'; // Include local conversation if any let botIdAttr = botId || '0'; let activeConversationKey = `wpaicg_current_conversation_${botIdAttr}_${clientID}`; let conversationKey = localStorage.getItem(activeConversationKey); // If there's no active conversation yet, fallback to a default if (!conversationKey) { // e.g. wpaicg_chat_history_custom_bot_4367_{clientID}_1 let basePrefix = botIdAttr !== '0' ? `wpaicg_chat_history_custom_bot_${botIdAttr}_${clientID}` : `wpaicg_chat_history_${type}_${clientID}`; // Create the default index = 1 if not set let newKey = `${basePrefix}_1`; if (!localStorage.getItem(newKey)) { localStorage.setItem(newKey, JSON.stringify([])); } localStorage.setItem(activeConversationKey, newKey); conversationKey = newKey; } // Add the conversation history to form data let storedHistory = localStorage.getItem(conversationKey) || '[]'; wpaicgData.append('wpaicg_chat_history', storedHistory); //append client_id to wpaicgData wpaicgData.append('wpaicg_chat_client_id', clientID); // --------------- Assistant-Enabled logic for thread --------------- let activeThreadKey = `wpaicg_current_thread_${botIdAttr}_${clientID}`; let currentThreadStorageKey = null; if (assistantEnabled) { // We store or retrieve the "active thread" from localStorage currentThreadStorageKey = localStorage.getItem(activeThreadKey); if (!currentThreadStorageKey) { // If user never clicked "New Chat," fallback to default index=1 let baseThreadPrefix = botIdAttr !== '0' ? `custom_bot_${botIdAttr}_${clientID}` : `${type}_${clientID}`; let foundMaxThreadIndex = 0; const threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {}; for (let existingKey in threadListObj) { if (existingKey.startsWith(baseThreadPrefix)) { let suffixMatch = existingKey.match(/_(\d+)$/); if (suffixMatch) { let indexNum = parseInt(suffixMatch[1], 10); if (indexNum > foundMaxThreadIndex) foundMaxThreadIndex = indexNum; } } } let defaultThreadIndex = foundMaxThreadIndex + 1; currentThreadStorageKey = `${baseThreadPrefix}_${defaultThreadIndex}`; localStorage.setItem(activeThreadKey, currentThreadStorageKey); } // If there's a real thread ID in localStorage for that key, we pass it let threadListData = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {}; let existingThreadId = threadListData[currentThreadStorageKey]; if (existingThreadId) { wpaicgData.append('thread_id', existingThreadId); } } // Hide the image thumbnail after sending the message if (imageInputThumbnail) { imageInputThumbnail.style.display = 'none'; } if (stream_nav === "1") { // Update local storage updateChatHistory(wpaicg_question, 'user', wpaicg_randomnum, chat, chatbot_identity, clientID); if (assistantEnabled) { // Call the new assistant streaming function handleAssistantStreaming( wpaicgData, wpaicg_messages_box, wpaicg_box_typing, wpaicg_ai_thinking, class_ai_item, chat, chatbot_identity, clientID, wpaicg_nonce ); } else { handleStreaming(wpaicgData, wpaicg_messages_box, wpaicg_box_typing, wpaicg_ai_thinking, class_ai_item, chat, chatbot_identity, clientID, wpaicg_nonce); } } else { updateChatHistory(wpaicg_question, 'user', wpaicg_randomnum, chat, chatbot_identity, clientID); // We also pass a fresh random ID for chat message var wpaicg_randomnum = Math.floor((Math.random() * 100000) + 1); const chatId = `wpaicg-chat-message-${wpaicg_randomnum}`; // Extract copy and feedback settings from the chat element const copyEnabled = chat.getAttribute('data-copy_btn') === "1"; const feedbackEnabled = chat.getAttribute('data-feedback_btn') === "1"; const fontColor = chat.getAttribute('data-color'); const usrBg = chat.getAttribute('data-user-bg-color'); 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"> <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"/> </svg>`; 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"> <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"/> </svg>`; 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"> <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"/> </svg>`; 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"> <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"/> </svg>`; const xhttp = new XMLHttpRequest(); wpaicg_box_typing.value = ''; xhttp.open('POST', wpaicgParams.ajax_url, true); xhttp.send(wpaicgData); xhttp.onreadystatechange = function (oEvent) { if (xhttp.readyState === 4) { var wpaicg_message = ''; var wpaicg_response_text = ''; if (xhttp.status === 200) { var wpaicg_response = this.responseText; if (wpaicg_response !== '') { wpaicg_response = JSON.parse(wpaicg_response); wpaicg_ai_thinking.style.display = 'none' if (wpaicg_response.status === 'success') { // If assistantEnabled and the response contains thread_id, store it: if (assistantEnabled && wpaicg_response.thread_id) { let threadList = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {}; threadList[currentThreadStorageKey] = wpaicg_response.thread_id; localStorage.setItem('wpaicg_thread_list', JSON.stringify(threadList)); } wpaicg_response_text = wpaicg_response.data; wpaicg_message = ` <li class="${class_ai_item} wpaicg-icon-container" 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" id="${chatId}">${wpaicg_response_text}</span> ${copyEnabled ? `<button class="wpaicg-copy-button" data-chat-id="${chatId}">${emptyClipboardSVG}</button>` : ''} ${feedbackEnabled ? ` <button class="wpaicg-thumbs-up-button" data-chat-id="${chatId}">${thumbsUpSVG}</button> <button class="wpaicg-thumbs-down-button" data-chat-id="${chatId}">${thumbsDownSVG}</button>` : ''} </p> </li> `; } else { wpaicg_response_text = wpaicg_response.msg; 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>'; } } } else { 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>'; wpaicg_response_text = 'Something went wrong. Please clear your cache and try again.'; wpaicg_ai_thinking.style.display = 'none'; } if (wpaicg_response_text === 'null' || wpaicg_response_text === null) { wpaicg_response_text = 'Empty response from api. Check your server logs for more details.'; } setupButtonListeners(copyEnabled, feedbackEnabled, class_ai_item, emptyClipboardSVG, checkedClipboardSVG, thumbsUpSVG, thumbsDownSVG, showFeedbackModal, wpaicg_ai_bg, wpaicg_font_color, usrBg, chat, wpaicg_nonce, chatbot_identity); const simpleChatId = chatId.replace('wpaicg-chat-message-', ''); updateChatHistory(wpaicg_response_text, 'ai', simpleChatId, chat, chatbot_identity, clientID); if (wpaicg_response_text !== '' && wpaicg_message !== '') { if (parseInt(wpaicg_speech) == 1 && (userVoiceControl == "1" ? isAudioEnabledByUser : true)) { if (voice_service === 'google') { wpaicg_ai_thinking.style.display = 'block'; let speechData = new FormData(); speechData.append('nonce', wpaicg_nonce); speechData.append('action', 'wpaicg_google_speech'); speechData.append('language', voice_language); speechData.append('name', voice_name); speechData.append('device', voice_device); speechData.append('speed', voice_speed); speechData.append('pitch', voice_pitch); speechData.append('text', wpaicg_response_text); var speechRequest = new XMLHttpRequest(); speechRequest.open("POST", wpaicgParams.ajax_url); speechRequest.onload = function () { var result = speechRequest.responseText; try { result = JSON.parse(result); if (result.status === 'success') { var byteCharacters = atob(result.audio); const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); const blob = new Blob([byteArray], { type: 'audio/mp3' }); const blobUrl = URL.createObjectURL(blob); wpaicg_message += '<audio style="margin-top:6px;" controls="controls"><source type="audio/mpeg" src="' + blobUrl + '"></audio>'; wpaicg_message += '</p></li>'; wpaicg_ai_thinking.style.display = 'none'; // scroll to the bottom of the chatbox wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight; wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed); } else { var errorMessageDetail = 'Google: ' + result.msg; wpaicg_ai_thinking.style.display = 'none'; if (parseInt(wpaicg_voice_error) !== 1) { wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">' + errorMessageDetail + '</span>'; } else if (typeof wpaicg_response !== 'undefined' && typeof wpaicg_response.log !== 'undefined' && wpaicg_response.log !== '') { var speechLogMessage = new FormData(); speechLogMessage.append('nonce', wpaicg_nonce); speechLogMessage.append('log_id', wpaicg_response.log); speechLogMessage.append('message', errorMessageDetail); speechLogMessage.append('action', 'wpaicg_speech_error_log'); var speechErrorRequest = new XMLHttpRequest(); speechErrorRequest.open("POST", wpaicgParams.ajax_url); speechErrorRequest.send(speechLogMessage); } wpaicg_message += '</p></li>'; wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed); } } catch (errorSpeech) { } } speechRequest.send(speechData); } else if (voice_service === 'openai') { // OpenAI TTS code let speechData = new FormData(); speechData.append('action', 'wpaicg_openai_speech'); speechData.append('nonce', wpaicg_nonce); speechData.append('text', wpaicg_response_text); speechData.append('model', openai_model); speechData.append('voice', openai_voice); speechData.append('output_format', openai_output_format); speechData.append('speed', openai_voice_speed); // Display some sort of loading indicator wpaicg_ai_thinking.style.display = 'block'; var speechRequest = new XMLHttpRequest(); speechRequest.open("POST", wpaicgParams.ajax_url); speechRequest.responseType = "arraybuffer"; // Expecting raw audio data speechRequest.onload = function () { if (speechRequest.status === 200) { wpaicg_ai_thinking.style.display = 'none'; const audioData = speechRequest.response; const blobMimeType = getBlobMimeType(openai_output_format); // Get the MIME type based on the format const blob = new Blob([audioData], { type: blobMimeType }); const blobUrl = URL.createObjectURL(blob); // Update your message UI here wpaicg_message += '<audio style="margin-top:6px;" controls="controls"><source type="audio/mpeg" src="' + blobUrl + '"></audio>'; // scroll to the bottom of the chatbox wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight; wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed); } else { // Handle HTTP errors wpaicg_ai_thinking.style.display = 'none'; console.error('Error generating speech with OpenAI:', speechRequest.statusText); // Update your message UI to show the error wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">Error generating speech with OpenAI</span>'; wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed); } }; speechRequest.onerror = function () { // Handle network errors wpaicg_ai_thinking.style.display = 'none'; console.error('Network error during speech generation with OpenAI'); // Update your message UI to show the network error wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">Network error during speech generation</span>'; wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed); }; speechRequest.send(speechData); // Utility function to get the correct MIME type function getBlobMimeType(format) { switch (format) { case 'opus': return 'audio/opus'; case 'aac': return 'audio/aac'; case 'flac': return 'audio/flac'; default: return 'audio/mpeg'; // Default to MP3 } } } else { let speechData = new FormData(); speechData.append('nonce', wpaicg_nonce); speechData.append('message', wpaicg_response_text); speechData.append('voice', wpaicg_voice); speechData.append('elevenlabs_model', elevenlabs_model); speechData.append('action', 'wpaicg_text_to_speech'); wpaicg_ai_thinking.style.display = 'block'; var speechRequest = new XMLHttpRequest(); speechRequest.open("POST", wpaicgParams.ajax_url); speechRequest.responseType = "arraybuffer"; speechRequest.onload = function () { wpaicg_ai_thinking.style.display = 'none'; var blob = new Blob([speechRequest.response], { type: "audio/mpeg" }); var fr = new FileReader(); fr.onload = function () { var fileText = this.result; try { var errorMessage = JSON.parse(fileText); var errorMessageDetail = 'ElevenLabs: ' + errorMessage.detail.message; if (parseInt(wpaicg_voice_error) !== 1) { wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">' + errorMessageDetail + '</span>'; } else if (typeof wpaicg_response !== 'undefined' && typeof wpaicg_response.log !== 'undefined' && wpaicg_response.log !== '') { var speechLogMessage = new FormData(); speechLogMessage.append('nonce', wpaicg_nonce); speechLogMessage.append('log_id', wpaicg_response.log); speechLogMessage.append('message', errorMessageDetail); speechLogMessage.append('action', 'wpaicg_speech_error_log'); var speechErrorRequest = new XMLHttpRequest(); speechErrorRequest.open("POST", wpaicgParams.ajax_url); speechErrorRequest.send(speechLogMessage); } wpaicg_message += '</p></li>'; wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed); } catch (errorBlob) { var blobUrl = URL.createObjectURL(blob); wpaicg_message += '<audio style="margin-top:6px;" controls="controls"><source type="audio/mpeg" src="' + blobUrl + '"></audio>'; wpaicg_message += '</p></li>'; wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed); } } fr.readAsText(blob); } speechRequest.send(speechData); } } else { wpaicg_message += '</p></li>'; wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed); } } } } } } // Function to hide all conversation starters function hideConversationStarters() { const starters = document.querySelectorAll('.wpaicg-conversation-starters'); starters.forEach(starter => { starter.style.display = 'none'; }); } function handleStreaming(wpaicgData, wpaicg_messages_box, wpaicg_box_typing, wpaicg_ai_thinking, class_ai_item, chat, chatbot_identity, clientID, wpaicg_nonce) { // Remove the lead form if it exists var leadFormMessage = chat.querySelector('.wpaicg-lead-form-message'); if (leadFormMessage) { leadFormMessage.remove(); // Optionally, set 'wpaicg_lead_form_shown' to '1' so it doesn't show again localStorage.setItem('wpaicg_lead_form_shown', '1'); } const fontSize = chat.getAttribute('data-fontsize'); const aiBg = chat.getAttribute('data-ai-bg-color'); const fontColor = chat.getAttribute('data-color'); const usrBg = chat.getAttribute('data-user-bg-color'); const copyEnabled = chat.getAttribute('data-copy_btn') === "1"; const feedbackEnabled = chat.getAttribute('data-feedback_btn') === "1"; wpaicg_box_typing.value = ''; // add chatID to the query string const chatId = `wpaicg-chat-message-${Math.floor(Math.random() * 100000) + 1}`; const cleanedChatId = chatId.replace('wpaicg-chat-message-', ''); // Clean the chatId by removing the prefix wpaicgData.append('chat_id', cleanedChatId); 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"> <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"/> </svg>`; 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"> <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"/> </svg>`; 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"> <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"/> </svg>`; 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"> <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"/> </svg>`; const messageHtml = ` <li class="${class_ai_item} wpaicg-icon-container" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}"> <p style="width:100%"> <span class="wpaicg-chat-message" id="${chatId}"></span> ${copyEnabled ? `<button class="wpaicg-copy-button" data-chat-id="${chatId}">${emptyClipboardSVG}</button>` : ''} ${feedbackEnabled ? `<button class="wpaicg-thumbs-up-button" data-chat-id="${chatId}">${thumbsUpSVG}</button> <button class="wpaicg-thumbs-down-button" data-chat-id="${chatId}">${thumbsDownSVG}</button>` : ''} </p> </li> `; // Buffer to accumulate chunks let buffer = ''; let completeAIResponse = ''; let dataQueue = []; let isProcessing = false; function processBuffer() { processMarkdown(buffer, true, chatId); } function typeWriter(text, i, elementId, callback) { toggleBlinkingCursor(false); if (i < text.length) { const charToAdd = text.charAt(i); if (charToAdd === '<') { const tag = text.slice(i, i + 4); if (tag === '<br>') { jQuery(`#${elementId}`).append(tag); i += 4; } else { jQuery(`#${elementId}`).append(charToAdd); i++; } } else { jQuery(`#${elementId}`).append(charToAdd); i++; } setTimeout(() => typeWriter(text, i, elementId, callback), 1); } else if (callback) { callback(); scrollToBottom(); } } function scrollToBottom() { wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight; } function processQueue() { if (dataQueue.length && !isProcessing) { isProcessing = true; const nextChunk = dataQueue.shift(); typeWriter(nextChunk, 0, chatId, () => { isProcessing = false; processQueue(); }); } else { toggleBlinkingCursor(false); } } function toggleBlinkingCursor(isVisible) { const cursorElement = jQuery(`#${chatId} .blinking-cursor`); if (isVisible) { if (!cursorElement.length) { jQuery(`#${chatId}`).append('<span class="blinking-cursor">|</span>'); } } else { cursorElement.remove(); } } // Fetch POST request for streaming fetch(wpaicgParams.ajax_url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams(wpaicgData).toString(), }) .then(response => response.body) .then(async (reader) => { const decoder = new TextDecoder(); const stream = reader.getReader(); toggleBlinkingCursor(true); wpaicg_ai_thinking.style.display = 'none'; wpaicg_messages_box.innerHTML += messageHtml; let partial = ''; while (true) { const { done, value } = await stream.read(); if (done) { toggleBlinkingCursor(false); wpaicg_ai_thinking.style.display = 'none'; const simpleChatId = chatId.replace('wpaicg-chat-message-', ''); updateChatHistory(completeAIResponse, 'ai', simpleChatId, chat, chatbot_identity, clientID); break; } partial += decoder.decode(value); // Append chunk // Split the partial data by lines const lines = partial.split('\n'); for (let i = 0; i < lines.length - 1; i++) { let line = lines[i]; if (line.trim() === '' || !line.startsWith('data: ')) { continue; } // Remove 'data: ' prefix line = line.slice(6); // Handle [DONE] signal if (line === "[DONE]") { toggleBlinkingCursor(false); wpaicg_ai_thinking.style.display = 'none'; scrollToBottom(); const simpleChatId = chatId.replace('wpaicg-chat-message-', ''); updateChatHistory(completeAIResponse, 'ai', simpleChatId, chat, chatbot_identity, clientID); if (!localStorage.getItem('wpaicg_lead_form_shown')) { maybeShowLeadForm(chat, chatId); // scroll to the bottom of the chatbox scrollToBottom(); } return; } // Try to parse the remaining JSON data try { const resultData = JSON.parse(line); if (resultData.tokenLimitReached || resultData.messageFlagged || resultData.pineconeError || resultData.ipBanned || resultData.modflag) { document.getElementById(chatId).innerHTML = `<span class="wpaicg-chat-message">${resultData.msg}</span>`; wpaicg_ai_thinking.style.display = 'none'; toggleBlinkingCursor(false); scrollToBottom(); return; } if (resultData.error) { dataQueue.push(resultData.error.message); } else { const content = resultData.choices?.[0]?.delta?.content || resultData.choices?.[0]?.text || ''; buffer += content; processBuffer(); completeAIResponse += content; } processQueue(); scrollToBottom(); } catch (err) { console.error('Error parsing JSON:', err, line); } } partial = lines[lines.length - 1]; // Keep last partial line } // Process any remaining partial data after the loop ends if (partial.trim() !== '') { const lines = partial.split('\n'); for (let line of lines) { if (line.trim() === '' || !line.startsWith('data: ')) { continue; } // Remove 'data: ' prefix line = line.slice(6); // Handle [DONE] signal if (line === "[DONE]") { toggleBlinkingCursor(false); return; } // Try to parse the remaining JSON data try { const resultData = JSON.parse(line); if (resultData.tokenLimitReached || resultData.messageFlagged || resultData.pineconeError || resultData.ipBanned || resultData.modflag) { document.getElementById(chatId).innerHTML = `<span class="wpaicg-chat-message">${resultData.msg}</span>`; wpaicg_ai_thinking.style.display = 'none'; toggleBlinkingCursor(false); scrollToBottom(); return; } if (resultData.error) { dataQueue.push(resultData.error.message); } else { const content = resultData.choices?.[0]?.delta?.content || resultData.choices?.[0]?.text || ''; buffer += content; processBuffer(); completeAIResponse += content; } processQueue(); scrollToBottom(); } catch (err) { console.error('Error parsing JSON after stream end:', err, line); } } } }) .catch(error => { console.log("Fetch failed:", error); toggleBlinkingCursor(false); wpaicg_ai_thinking.style.display = 'none'; }); // Setup button listeners for the copy and feedback buttons setupButtonListeners(copyEnabled, feedbackEnabled, class_ai_item, emptyClipboardSVG, checkedClipboardSVG, thumbsUpSVG, thumbsDownSVG, showFeedbackModal, aiBg, fontColor, usrBg, chat, wpaicg_nonce, chatbot_identity); } function handleAssistantStreaming( wpaicgData, wpaicg_messages_box, wpaicg_box_typing, wpaicg_ai_thinking, class_ai_item, chat, chatbot_identity, clientID, wpaicg_nonce ) { // Remove the lead form if it exists const leadFormMessage = chat.querySelector('.wpaicg-lead-form-message'); if (leadFormMessage) { leadFormMessage.remove(); // Optionally, set 'wpaicg_lead_form_shown' to '1' so it doesn't show again localStorage.setItem('wpaicg_lead_form_shown', '1'); } // Retrieve UI configurations const fontSize = chat.getAttribute('data-fontsize'); const aiBg = chat.getAttribute('data-ai-bg-color'); const fontColor = chat.getAttribute('data-color'); const usrBg = chat.getAttribute('data-user-bg-color'); const copyEnabled = chat.getAttribute('data-copy_btn') === "1"; const feedbackEnabled = chat.getAttribute('data-feedback_btn') === "1"; // Clear the typing box wpaicg_box_typing.value = ''; // Generate a unique chat ID const chatId = `wpaicg-chat-message-${Math.floor(Math.random() * 100000) + 1}`; const cleanedChatId = chatId.replace('wpaicg-chat-message-', ''); // Clean the chatId by removing the prefix wpaicgData.append('chat_id', cleanedChatId); 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"> <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"/> </svg>`; 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"> <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"/> </svg>`; 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"> <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"/> </svg>`; 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"> <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"/> </svg>`; const messageHtml = ` <li class="${class_ai_item} wpaicg-icon-container" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}"> <span class="wpaicg-chat-message" id="${chatId}"></span> ${copyEnabled ? `<button class="wpaicg-copy-button" data-chat-id="${chatId}">${emptyClipboardSVG}</button>` : ''} ${feedbackEnabled ? `<button class="wpaicg-thumbs-up-button" data-chat-id="${chatId}">${thumbsUpSVG}</button> <button class="wpaicg-thumbs-down-button" data-chat-id="${chatId}">${thumbsDownSVG}</button>` : ''} </li> `; let completeAIResponse = ''; let aiMessageAdded = false; let accumulatedBuffer = ''; // Buffer to accumulate chunks let doneCalled = false; // To store the thread ID logic (multiple threads) const botId = chat.getAttribute('data-bot-id') || '0'; const activeThreadKey = `wpaicg_current_thread_${botId}_${clientID}`; const existingThreadKey = localStorage.getItem(activeThreadKey); // If we appended a real thread_id in wpaicgData, handle that in the server streaming function scrollToBottom() { wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight; } function toggleBlinkingCursor(isVisible) { const cursorElement = jQuery(`#${chatId} .blinking-cursor`); if (isVisible) { if (!cursorElement.length) { jQuery(`#${chatId}`).append('<span class="blinking-cursor">|</span>'); } } else { cursorElement.remove(); } } // Fetch POST request for Assistant API streaming fetch(wpaicgParams.ajax_url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams(wpaicgData).toString(), }) .then(response => { const reader = response.body.getReader(); const decoder = new TextDecoder(); let partial = ''; let currentEvent = null; function read() { return reader.read().then(({ done, value }) => { if (done) { handleDoneEvent(); return; } partial += decoder.decode(value, { stream: true }); // attempt to parse the partial response as JSON try { const json = JSON.parse(partial); if (json.status === 'error' && json.msg) { handleErrorEvent(json.msg); // use the existing error handler return; // stop further processing } } catch (e) { // ignore JSON parse errors here, continue streaming } const lines = partial.split('\n'); for (let i = 0; i < lines.length - 1; i++) { const line = lines[i].trim(); if (line === '') continue; if (line.startsWith('event: ')) { currentEvent = line.slice(7).trim(); } else if (line.startsWith('data: ')) { const data = line.slice(6).trim(); handleAssistantStreamEvent(currentEvent, data); } } partial = lines[lines.length - 1]; return read(); }); } return read(); }) .catch(error => { console.log("Fetch failed:", error); toggleBlinkingCursor(false); wpaicg_ai_thinking.style.display = 'none'; }); // Function to handle Assistant API streaming events function handleAssistantStreamEvent(event, data) { switch (event) { case 'thread_id': handleThreadIdEvent(data); break; case 'thread.message.delta': handleMessageDeltaEvent(data); break; case 'thread.message.completed': handleMessageCompletedEvent(data); break; case 'assistant_error': handleErrorEvent(data); break; case 'done': handleDoneEvent(); break; case null: handleErrorEvent(data); break; } } function handleMessageDeltaEvent(data) { const deltaData = JSON.parse(data); if (deltaData && deltaData.delta && deltaData.delta.content) { const contentArray = deltaData.delta.content; for (const contentItem of contentArray) { if (contentItem.type === 'text' && contentItem.text && contentItem.text.value) { const messageChunk = contentItem.text.value; if (!aiMessageAdded) { wpaicg_messages_box.innerHTML += messageHtml; aiMessageAdded = true; wpaicg_ai_thinking.style.display = 'none'; } // Accumulate chunks accumulatedBuffer += messageChunk; completeAIResponse += messageChunk; // Convert Markdown to HTML const htmlContent = marked.parse(accumulatedBuffer); // Update the chat message content document.getElementById(chatId).innerHTML = htmlContent; scrollToBottom(); } } } } function handleMessageCompletedEvent(data) { toggleBlinkingCursor(false); wpaicg_ai_thinking.style.display = 'none'; scrollToBottom(); } function handleThreadIdEvent(threadId) { // store thread id for the active thread localStorage if (existingThreadKey) { let threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {}; threadListObj[existingThreadKey] = threadId; localStorage.setItem('wpaicg_thread_list', JSON.stringify(threadListObj)); } } // Handler for 'assistant_error' events function handleErrorEvent(errorData) { console.log('Assistant Error:', errorData); let errorMessage = 'An unknown error occurred.'; try { // check if errorData is a string and try to parse it as JSON if (typeof errorData === 'string') { try { const parsedError = JSON.parse(errorData); errorMessage = parsedError.msg || errorMessage; // use parsed message if available } catch (jsonParseError) { // if parsing fails, fall back to using the raw string errorMessage = errorData; } } else if (errorData && errorData.msg) { errorMessage = errorData.msg; // directly use the msg field if it's an object } } catch (e) { console.error('Error handling error data:', e); // log any unexpected issues } // parse the error message with marked for markdown or URL formatting const parsedHtml = marked.parse(errorMessage); const chatElement = document.getElementById(chatId); if (chatElement) { chatElement.innerHTML = `<span class="wpaicg-chat-message">${parsedHtml}</span>`; } else { // fallback if chatId doesn't exist yet wpaicg_messages_box.innerHTML += ` <li class="${class_ai_item}" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}"> <span class="wpaicg-chat-message">${parsedHtml}</span> </li>`; } wpaicg_ai_thinking.style.display = 'none'; toggleBlinkingCursor(false); scrollToBottom(); } // Handler for 'done' events function handleDoneEvent() { if (doneCalled) return; doneCalled = true; toggleBlinkingCursor(false); wpaicg_ai_thinking.style.display = 'none'; if (!localStorage.getItem('wpaicg_lead_form_shown')) { maybeShowLeadForm(chat, chatId); scrollToBottom(); } // update chat history const simpleChatId = chatId.replace('wpaicg-chat-message-', ''); updateChatHistory(completeAIResponse, 'ai', simpleChatId, chat, chatbot_identity, clientID); } // Setup button listeners for the copy and feedback buttons setupButtonListeners( copyEnabled, feedbackEnabled, class_ai_item, emptyClipboardSVG, checkedClipboardSVG, thumbsUpSVG, thumbsDownSVG, showFeedbackModal, aiBg, fontColor, usrBg, chat, wpaicg_nonce, chatbot_identity ); } function processMarkdown(inputText, isStream = false, chatId = null) { inputText = inputText !== '' ? inputText.trim() : ''; // parse the markdown using the marked library const formattedText = marked.parse(inputText); // if in stream mode and chatId exists, update the DOM if (isStream && chatId) { const element = document.getElementById(chatId); if (element) { element.innerHTML = formattedText; } } return formattedText; } // Scroll function to adjust. function scrollToAdjust(wpaicg_messages_box) { requestAnimationFrame(() => { wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight; }); } function wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed) { var chatContainerforLead = wpaicg_messages_box.closest('.wpaicg-chat-shortcode') || wpaicg_messages_box.closest('.wpaicg-chatbox'); wpaicg_messages_box.insertAdjacentHTML('beforeend', wpaicg_message); var wpaicg_current_message = document.getElementById('wpaicg-chat-message-' + wpaicg_randomnum); // Ensure the current message is found if (wpaicg_current_message) { // Get the next sibling element, which should be the audio element var nextElement = wpaicg_current_message.closest('li').nextElementSibling; // Check if the next element is an audio tag and play it if found if (nextElement && nextElement.tagName.toLowerCase() === 'audio') { nextElement.play(); } else { console.log('No audio found next to the current message.'); } } else { console.log('Current message not found.'); } // Apply formatting to the entire response text first var formattedText = marked.parse(wpaicg_response_text); if (wpaicg_typewriter_effect) { let index = 0; // Starting index of the substring function typeWriter() { if (index < formattedText.length) { wpaicg_current_message.innerHTML = formattedText.slice(0, index + 1); index++; setTimeout(typeWriter, wpaicg_typewriter_speed); //scroll to the latest message if needed scrollToAdjust(wpaicg_messages_box); } else { // Once complete, ensure scrolling if needed scrollToAdjust(wpaicg_messages_box); } } typeWriter(); // Start the typewriter effect } else { wpaicg_current_message.innerHTML = formattedText; // Scroll to the latest message if needed scrollToAdjust(wpaicg_messages_box); } if (!localStorage.getItem('wpaicg_lead_form_shown')) { maybeShowLeadForm(chatContainerforLead, 'wpaicg-chat-message-' + wpaicg_randomnum); // scroll to the bottom of the chatbox scrollToAdjust(wpaicg_messages_box); } } function wpaicgMicEvent(mic) { if (mic.classList.contains('wpaicg-recording')) { mic.innerHTML = ''; mic.innerHTML = wpaicgMicIcon; mic.classList.remove('wpaicg-recording'); wpaicgstopChatRecording(mic) } else { let checkRecording = document.querySelectorAll('.wpaicg-recording'); if (checkRecording && checkRecording.length) { alert('Please finish previous recording'); } else { mic.innerHTML = ''; mic.innerHTML = wpaicgStopIcon; mic.classList.add('wpaicg-recording'); wpaicgstartChatRecording(); } } } if (wpaicgChatTyping && wpaicgChatTyping.length) { for (let i = 0; i < wpaicgChatTyping.length; i++) { wpaicgChatTyping[i].addEventListener('keyup', function (event) { if ((event.which === 13 || event.keyCode === 13) && !event.shiftKey) { let parentChat = wpaicgChatTyping[i].closest('.wpaicg-chatbox'); let chatTyping = parentChat.querySelectorAll('.wpaicg-chatbox-typing')[0]; wpaicgSendChatMessage(parentChat, chatTyping, 'widget'); } }) } } if (wpaicgShortcodeTyping && wpaicgShortcodeTyping.length) { for (let i = 0; i < wpaicgShortcodeTyping.length; i++) { wpaicgShortcodeTyping[i].addEventListener('keyup', function (event) { if ((event.which === 13 || event.keyCode === 13) && !event.shiftKey) { let parentChat = wpaicgShortcodeTyping[i].closest('.wpaicg-chat-shortcode'); let chatTyping = parentChat.querySelectorAll('.wpaicg-chat-shortcode-typing')[0]; wpaicgSendChatMessage(parentChat, chatTyping, 'shortcode'); } }) } } if (wpaicgChatSend && wpaicgChatSend.length) { for (let i = 0; i < wpaicgChatSend.length; i++) { wpaicgChatSend[i].addEventListener('click', function (event) { let parentChat = wpaicgChatSend[i].closest('.wpaicg-chatbox'); let chatTyping = parentChat.querySelectorAll('.wpaicg-chatbox-typing')[0]; wpaicgSendChatMessage(parentChat, chatTyping, 'widget'); }) } } if (wpaicgShortcodeSend && wpaicgShortcodeSend.length) { for (let i = 0; i < wpaicgShortcodeSend.length; i++) { wpaicgShortcodeSend[i].addEventListener('click', function (event) { let parentChat = wpaicgShortcodeSend[i].closest('.wpaicg-chat-shortcode'); let chatTyping = parentChat.querySelectorAll('.wpaicg-chat-shortcode-typing')[0]; wpaicgSendChatMessage(parentChat, chatTyping, 'shortcode'); }) } } if (wpaicgMicBtns && wpaicgMicBtns.length) { for (let i = 0; i < wpaicgMicBtns.length; i++) { wpaicgMicBtns[i].addEventListener('click', function () { wpaicgMicEvent(wpaicgMicBtns[i]); }); } } } // Initialize Sidebar Toggle function initSidebarToggle() { const toggleButtons = document.querySelectorAll('.wpaicg-sidebar-toggle'); const savedSidebarState = localStorage.getItem('wpaicg_sidebar_state') || 'closed'; // Set initial state for all sidebars based on the saved preference. document.querySelectorAll('.wpaicg-sidebar').forEach(sidebar => { if (savedSidebarState === 'open') { sidebar.classList.add('open'); } else { sidebar.classList.remove('open'); } }); toggleButtons.forEach(toggle => { toggle.addEventListener('click', function () { const chatContainer = this.closest('.wpaicg-chat-shortcode, .wpaicg-chatbox'); const sidebar = chatContainer.querySelector('.wpaicg-sidebar'); sidebar.classList.toggle('open'); const newState = sidebar.classList.contains('open') ? 'open' : 'closed'; localStorage.setItem('wpaicg_sidebar_state', newState); }); // Allow toggle via keyboard (Enter key) toggle.addEventListener('keypress', function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.click(); } }); }); } /** * Builds the list of all conversations in the sidebar for each chat container, * sorted by their "last updated" timestamp in descending order. * We look for the first user message (prefixed "Human:") to name the conversation. */ function loadConversationList() { const containers = ['.wpaicg-chat-shortcode', '.wpaicg-chatbox']; containers.forEach(containerSelector => { const chatContainers = document.querySelectorAll(containerSelector); chatContainers.forEach(chatContainer => { // Find the sidebar within this container const sidebar = chatContainer.querySelector('.wpaicg-sidebar'); if (!sidebar) return; // Conversation list UL inside the sidebar const conversationList = sidebar.querySelector('.wpaicg-conversation-list'); if (!conversationList) return; // Clear out any old items conversationList.innerHTML = ''; // Identify which bot const botId = chatContainer.getAttribute('data-bot-id') || '0'; const chatType = chatContainer.getAttribute('data-type') || 'shortcode'; // Need the clientID from localStorage let clientID = localStorage.getItem('wpaicg_chat_client_id'); if (!clientID) return; // Base prefix for conversation keys let conversationKeyBase = ''; if (botId !== '0') { // custom bot conversationKeyBase = `wpaicg_chat_history_custom_bot_${botId}_${clientID}`; } else { // default shortcodes or widgets conversationKeyBase = `wpaicg_chat_history_${chatType}_${clientID}`; } // We'll collect all conversations, then sort let conversationData = []; // Loop through localStorage to find all conversation keys for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); // Must match our base prefix plus the underscore for the index if (key.startsWith(conversationKeyBase + '_')) { // Ensure we're looking specifically at the conversation array, not the _timestamp if (key.endsWith('_timestamp')) continue; const rawData = localStorage.getItem(key); if (!rawData) continue; let chatHistory; try { chatHistory = JSON.parse(rawData); } catch (e) { chatHistory = []; } // Default name let title = "Untitled Conversation"; // We look specifically for the FIRST user message ("Human:") if (Array.isArray(chatHistory) && chatHistory.length > 0) { for (let j = 0; j < chatHistory.length; j++) { let msg = chatHistory[j].text; if (typeof msg === 'string' && msg.startsWith('Human:')) { // Remove "Human:" const userMsg = msg.replace(/^Human:\s*/, '').trim(); title = userMsg.substring(0, 20) + (userMsg.length > 20 ? '…' : ''); break; // we found our user message } } } // Check if there's a timestamp const timestampKey = key + '_timestamp'; const rawTimestamp = localStorage.getItem(timestampKey); const lastUpdated = rawTimestamp ? parseInt(rawTimestamp, 10) : 0; // Collect conversationData.push({ key, lastUpdated, title }); } } // Sort conversationData by lastUpdated descending conversationData.sort((a, b) => b.lastUpdated - a.lastUpdated); // Now build <li> for each conversationData.forEach(item => { const li = document.createElement('li'); li.textContent = item.title; li.dataset.conversationKey = item.key; // --- NEW CODE: Add a "trash" icon on hover for deletion --- const trashIcon = document.createElement('span'); // Dashicons classes for a trash icon + our custom class trashIcon.classList.add('dashicons', 'dashicons-trash', 'wpaicg-delete-icon'); // On click, remove from local storage & UI trashIcon.addEventListener('click', e => { e.stopPropagation(); // Prevent loading the conversation localStorage.removeItem(item.key); localStorage.removeItem(item.key + '_timestamp'); li.remove(); }); li.appendChild(trashIcon); // Clicking the li itself loads the conversation li.addEventListener('click', function () { loadSelectedConversation(item.key, chatContainer); }); conversationList.appendChild(li); }); }); }); } /** * Loads the selected conversation key from localStorage * into the chat UI, and sets it as active for subsequent messages. * Also handles assistant thread alignment if assistant is enabled. * * @param {string} conversationKey - The exact localStorage key for this conversation. * @param {HTMLElement} chatContainer - The .wpaicg-chat-shortcode or .wpaicg-chatbox container. */ function loadSelectedConversation(conversationKey, chatContainer) { // Clear the display var chatBox = chatContainer.querySelector('.wpaicg-chatbox-messages, .wpaicg-chat-shortcode-messages'); if (!chatBox) return; chatBox.innerHTML = ''; // Retrieve the conversation var rawHistory = localStorage.getItem(conversationKey); if (!rawHistory) return; var chatHistory = JSON.parse(rawHistory); if (!Array.isArray(chatHistory)) chatHistory = []; // Reconstruct the conversation in the UI chatHistory.forEach(message => { reconstructMessage(chatBox, message, chatContainer); }); // Scroll to bottom chatBox.scrollTop = chatBox.scrollHeight; // Mark this conversationKey as the current "active" conversation const botId = chatContainer.getAttribute('data-bot-id') || '0'; const clientID = localStorage.getItem('wpaicg_chat_client_id') || ''; const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientID}`; localStorage.setItem(activeConversationKey, conversationKey); // If assistant is enabled, we want to align the active thread similarly let assistantEnabled = chatContainer.getAttribute('data-assistant-enabled') === 'true'; if (assistantEnabled) { // Example conversationKey: // wpaicg_chat_history_custom_bot_4377_dc8XxpH90T_2 // we want to map the portion after "custom_bot_4377_dc8XxpH90T_" => "2" // so the thread key is "custom_bot_4377_dc8XxpH90T_2" // or if it's the default short/widget fallback let suffixParts = conversationKey.split('_'); if (suffixParts.length >= 2) { // For custom bot conversation keys => wpaicg_chat_history_custom_bot_${botId}_${clientID}_${index} // suffixParts might be: [ 'wpaicg','chat','history','custom','bot','4377','dc8XxpH90T','2' ] // We'll find the last item => index let lastIndex = suffixParts[suffixParts.length - 1]; // Rebuild the base to "custom_bot_{botId}_{clientID}_{lastIndex}" or for short/widget => "shortcode_{clientID}_{lastIndex}" // Actually we can slice from the third piece onward // e.g. conversationKeyBase = "custom_bot_4377_dc8XxpH90T_2" let conversationKeyBase = suffixParts.slice(3).join('_'); // e.g. "custom_bot_4377_dc8XxpH90T_2" // Create the "activeThreadKey" let activeThreadKeyName = `wpaicg_current_thread_${botId}_${clientID}`; localStorage.setItem(activeThreadKeyName, conversationKeyBase); } } } // Call the init function when the document is ready document.addEventListener('DOMContentLoaded', function () { wpaicgChatInit(); loadConversations(); initSidebarToggle(); loadConversationList(); initNewChatButtons(); });