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