File "wpaicg-chat.js"
Full Path: /home/digimqhe/flashdigi.uk/comment-content/plugins/gpt3-ai-content-generator/public/js/wpaicg-chat.js
File size: 163.82 KB
MIME-type: text/plain
Charset: utf-8
/**
* Loads or initializes conversations on page load, but now
* simply displays the *latest* conversation instead of forcing
* a brand-new conversation each time.
*/
function loadConversations() {
// Attempt to retrieve existing clientId from localStorage; create if missing
var clientId = localStorage.getItem('wpaicg_chat_client_id');
if (!clientId) {
clientId = generateRandomString(10);
localStorage.setItem('wpaicg_chat_client_id', clientId);
}
// Load chat interfaces (shortcode + widget) on the page
loadChatInterface('.wpaicg-chat-shortcode', 'shortcode', clientId);
loadChatInterface('.wpaicg-chatbox', 'widget', clientId);
// For each container, set its active conversation to the highest
// existing index. If none exist, create a new conversation once.
var containers = document.querySelectorAll('.wpaicg-chat-shortcode, .wpaicg-chatbox');
containers.forEach(container => {
let botId = container.getAttribute('data-bot-id') || '0';
let chatType = container.getAttribute('data-type') || 'shortcode';
// Build base localStorage prefix
let conversationKeyBase = (botId !== '0')
? `wpaicg_chat_history_custom_bot_${botId}_${clientId}`
: `wpaicg_chat_history_${chatType}_${clientId}`;
// Find existing conversation indexes
let highestIndex = 0;
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i);
if (key.startsWith(conversationKeyBase)) {
let suffixMatch = key.match(/_(\d+)$/);
if (suffixMatch) {
let indexNum = parseInt(suffixMatch[1], 10);
if (indexNum > highestIndex) {
highestIndex = indexNum;
}
}
}
}
// If no existing conversation found, create one
if (highestIndex === 0) {
highestIndex = 1;
let firstConversationKey = `${conversationKeyBase}_${highestIndex}`;
localStorage.setItem(firstConversationKey, JSON.stringify([]));
}
// Mark the highest (latest) conversation as active
let latestConversationKey = `${conversationKeyBase}_${highestIndex}`;
let activeConversationKey = `wpaicg_current_conversation_${botId}_${clientId}`;
localStorage.setItem(activeConversationKey, latestConversationKey);
// If assistant is enabled, do the same for the "thread" logic
let assistantEnabled = container.getAttribute('data-assistant-enabled') === 'true';
if (assistantEnabled) {
let threadBase = (botId !== '0')
? `custom_bot_${botId}_${clientId}`
: `${chatType}_${clientId}`;
let threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
let highestThreadIndex = 0;
for (let existingKey in threadListObj) {
if (existingKey.startsWith(threadBase)) {
let suffixMatch = existingKey.match(/_(\d+)$/);
if (suffixMatch) {
let threadIndexNum = parseInt(suffixMatch[1], 10);
if (threadIndexNum > highestThreadIndex) {
highestThreadIndex = threadIndexNum;
}
}
}
}
// If no existing thread, create a new one
if (highestThreadIndex === 0) {
highestThreadIndex = 1;
let newThreadKey = `${threadBase}_${highestThreadIndex}`;
localStorage.setItem(`wpaicg_current_thread_${botId}_${clientId}`, newThreadKey);
} else {
// Otherwise, set the highest as active
let existingThreadKey = `${threadBase}_${highestThreadIndex}`;
localStorage.setItem(`wpaicg_current_thread_${botId}_${clientId}`, existingThreadKey);
}
}
});
// Update the conversation list in the sidebar
loadConversationList();
}
/**
* Initialize "New Chat" buttons. When a user clicks "New Chat," we create
* a fresh conversation in localStorage and load it in the chat interface.
* This updated version also immediately refreshes the conversation list
* in the sidebar so users see the new chat appear instantly.
*/
function initNewChatButtons() {
const newChatButtons = document.querySelectorAll('.wpaicg-new-chat-button');
newChatButtons.forEach(button => {
button.addEventListener('click', function () {
// Find the parent chat container (shortcode or widget)
const chatContainer = button.closest('.wpaicg-chat-shortcode, .wpaicg-chatbox');
if (!chatContainer) return;
// Identify chat type, e.g. 'custom_bot_4367' or 'shortcode' or 'widget'
const botId = chatContainer.getAttribute('data-bot-id') || '0';
const chatType = chatContainer.getAttribute('data-type') || 'shortcode';
const assistantEnabled = chatContainer.getAttribute('data-assistant-enabled') === 'true';
let conversationKeyBase = '';
// Distinguish custom bots vs default shortcodes or widgets
if (botId !== '0') {
// custom bot format
conversationKeyBase = `wpaicg_chat_history_custom_bot_${botId}`;
} else {
// fallback: default shortcodes or widgets
conversationKeyBase = `wpaicg_chat_history_${chatType}`;
}
// Grab or create the clientID
let clientID = localStorage.getItem('wpaicg_chat_client_id');
if (!clientID) {
clientID = generateRandomString(10);
localStorage.setItem('wpaicg_chat_client_id', clientID);
}
conversationKeyBase += `_${clientID}`;
// Count how many existing conversations use this prefix
let highestIndex = 0;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith(conversationKeyBase)) {
const suffixMatch = key.match(/_(\d+)$/);
if (suffixMatch) {
const indexNum = parseInt(suffixMatch[1], 10);
if (indexNum > highestIndex) highestIndex = indexNum;
}
}
}
// The new conversation will be next integer
const newConversationIndex = highestIndex + 1;
const newConversationKey = `${conversationKeyBase}_${newConversationIndex}`;
// Initialize an empty array for that conversation
localStorage.setItem(newConversationKey, JSON.stringify([]));
// Also set a timestamp for sorting
localStorage.setItem(`${newConversationKey}_timestamp`, Date.now().toString());
// Also set this new conversation key as the "active conversation"
const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientID}`;
localStorage.setItem(activeConversationKey, newConversationKey);
// ----- Assistant-enabled logic for multi-thread handling -----
if (assistantEnabled) {
// We store the "active thread" localStorage
const threadBase = botId !== '0'
? `custom_bot_${botId}_${clientID}`
: `${chatType}_${clientID}`;
// Count how many existing "thread" keys exist for this base
let highestThreadIndex = 0;
const threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
for (let existingKey in threadListObj) {
if (existingKey.startsWith(threadBase)) {
const suffixMatch = existingKey.match(/_(\d+)$/);
if (suffixMatch) {
const threadIndexNum = parseInt(suffixMatch[1], 10);
if (threadIndexNum > highestThreadIndex) highestThreadIndex = threadIndexNum;
}
}
}
const newThreadIndex = highestThreadIndex + 1;
const newThreadKey = `${threadBase}_${newThreadIndex}`;
// Now set the "active thread" localStorage
const activeThreadKey = `wpaicg_current_thread_${botId}_${clientID}`;
localStorage.setItem(activeThreadKey, newThreadKey);
}
// Immediately load the newly created conversation so user sees empty chat
loadSelectedConversation(newConversationKey, chatContainer);
// Refresh the conversation list in the sidebar so user sees it
loadConversationList();
});
});
}
/**
* Utility function that the code base already uses or references for client ID generation.
* If you already have a similar function, you may omit or adapt this.
*/
function generateRandomString(length) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
function showAllConversationStarters() {
// Target both interfaces
var containers = ['.wpaicg-chat-shortcode', '.wpaicg-chatbox'];
containers.forEach(containerSelector => {
var chatContainers = document.querySelectorAll(containerSelector);
chatContainers.forEach(chatContainer => {
showConversationStarters(chatContainer);
});
});
}
/**
* Appends the user/AI message to the localStorage conversation, respecting memory limits
* and triggers a refresh of the conversation list. Also stores a timestamp for sorting.
*
* @param {string} message The raw message text from the user or AI.
* @param {string} sender Either 'user' or 'ai'.
* @param {string} uniqueId A unique identifier for the message (e.g., a random integer).
* @param {HTMLElement} chat The parent chat container (.wpaicg-chat-shortcode or .wpaicg-chatbox).
* @param {string} chatbot_identity Identity of the chatbot ('custom_bot_####' or 'shortcode'/'widget').
* @param {string} clientID The user's local client/session ID from localStorage.
*/
function updateChatHistory(message, sender, uniqueId, chat, chatbot_identity, clientID) {
// Identify which bot is being used
const botId = chat.getAttribute('data-bot-id') || '0';
// Active conversation key in localStorage
const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientID}`;
let conversationKey = localStorage.getItem(activeConversationKey);
// If no active conversation yet, create one
if (!conversationKey) {
let defaultIndex = 1;
if (botId !== '0') {
// custom bot
conversationKey = `wpaicg_chat_history_custom_bot_${botId}_${clientID}_${defaultIndex}`;
} else {
// fallback for shortcodes or widgets
const type = chat.getAttribute('data-type') || 'shortcode';
conversationKey = `wpaicg_chat_history_${type}_${clientID}_${defaultIndex}`;
}
localStorage.setItem(conversationKey, JSON.stringify([]));
localStorage.setItem(activeConversationKey, conversationKey);
}
// Load existing history
let history = JSON.parse(localStorage.getItem(conversationKey) || '[]');
// Memory limit from data attribute or default
const memoryLimit = parseInt(chat.getAttribute('data-memory-limit'), 10) || 100;
// Format the message with "Human: " or "AI: "
const formattedMessage = (sender === 'user' ? "Human: " : "AI: ") + (message.trim() || '');
const numericId = typeof uniqueId === 'string' ? parseInt(uniqueId, 10) : uniqueId;
// Push new message to array
history.push({
id: numericId || '',
text: formattedMessage
});
// Trim the array to memoryLimit
if (history.length > memoryLimit) {
history = history.slice(-memoryLimit);
}
// Save updated history
localStorage.setItem(conversationKey, JSON.stringify(history));
// Update the 'last updated' timestamp in a separate localStorage key
// This will be used for sorting conversations in descending order
localStorage.setItem(`${conversationKey}_timestamp`, Date.now().toString());
// Refresh the conversation list so the user sees updated titles/timestamps
loadConversationList();
}
function loadChatInterface(containerSelector, type, clientId) {
var chatContainers = document.querySelectorAll(containerSelector);
chatContainers.forEach(chatContainer => {
// Read autoload chat conversations setting, default to '1' if not set
var autoloadConversations = chatContainer.getAttribute('data-autoload_chat_conversations');
if (autoloadConversations === null) {
autoloadConversations = '1'; // Default value if attribute does not exist
}
// Fetch the bot ID based on the type
var botId = chatContainer.getAttribute('data-bot-id') || '0';
if (autoloadConversations === '0') {
// Get the active conversation key for this chat/bot
var activeConversationKey = `wpaicg_current_conversation_${botId}_${clientId}`;
var conversationKey = localStorage.getItem(activeConversationKey);
// If no active conversation has been set, find the latest one
if (!conversationKey) {
let highestIndex = 0;
let conversationKeyBase = botId !== '0'
? `wpaicg_chat_history_custom_bot_${botId}_${clientId}`
: `wpaicg_chat_history_${type}_${clientId}`;
// Find the highest index (latest conversation)
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i);
if (key.startsWith(conversationKeyBase)) {
let suffixMatch = key.match(/_(\d+)$/);
if (suffixMatch) {
let indexNum = parseInt(suffixMatch[1], 10);
if (indexNum > highestIndex) {
highestIndex = indexNum;
}
}
}
}
// If we found a conversation, use it
if (highestIndex > 0) {
conversationKey = `${conversationKeyBase}_${highestIndex}`;
}
}
// Retrieve and display the chat history if we have a valid key
if (conversationKey && localStorage.getItem(conversationKey)) {
var chatHistory = localStorage.getItem(conversationKey);
chatHistory = JSON.parse(chatHistory);
// Convert old format messages to new format
chatHistory = chatHistory.map(message => {
if (typeof message === 'string') {
return {
id: '', // Old messages won't have an ID, so leave it empty
text: message
};
}
return message; // Leave new format messages as is
});
// Save the updated history back to localStorage in the new format
localStorage.setItem(conversationKey, JSON.stringify(chatHistory));
var chatBox = chatContainer.querySelector('.wpaicg-chatbox-messages, .wpaicg-chat-shortcode-messages'); // Generalized selector
if (!chatBox) {
console.error(`No chat box found within the ${type} container.`);
return;
}
chatBox.innerHTML = ''; // Clears the chat box
chatHistory.forEach(message => {
reconstructMessage(chatBox, message, chatContainer);
});
chatBox.appendChild(document.createElement('br'));
requestAnimationFrame(() => {
requestAnimationFrame(() => {
chatBox.scrollTop = chatBox.scrollHeight; // Scrolls to the bottom
});
});
hideConversationStarter(chatContainer);
} else {
showConversationStarters(chatContainer);
}
} else {
showConversationStarters(chatContainer);
}
});
}
function reconstructMessage(chatBox, message, chatContainer) {
var messageElement = document.createElement('li');
// Check if message is an object and has a 'text' property
var messageText = typeof message === 'object' && message.text ? message.text : message;
// Determine if the message is from the user or AI
var isUserMessage = messageText.startsWith('Human:');
var isWidget = chatContainer.classList.contains('wpaicg-chatbox');
// Apply the correct class based on message source and container type
if (isUserMessage) {
messageElement.className = isWidget ? 'wpaicg-chat-user-message' : 'wpaicg-user-message';
} else {
messageElement.className = isWidget ? 'wpaicg-chat-ai-message' : 'wpaicg-ai-message';
}
var formattedMessage = messageText.replace('Human:', '').replace('AI:', '');
formattedMessage = marked.parse(formattedMessage);
var userBgColor = chatContainer.getAttribute('data-user-bg-color');
var aiBgColor = chatContainer.getAttribute('data-ai-bg-color');
var fontSize = chatContainer.getAttribute('data-fontsize');
var fontColor = chatContainer.getAttribute('data-color');
messageElement.style.backgroundColor = isUserMessage ? userBgColor : aiBgColor;
messageElement.style.color = fontColor;
messageElement.style.fontSize = fontSize;
messageElement.innerHTML = `<span class="wpaicg-chat-message">${formattedMessage}</span>`;
chatBox.appendChild(messageElement);
}
function hideConversationStarter(chatContainer) {
var starters = chatContainer.querySelectorAll('.wpaicg-conversation-starters');
starters.forEach(starter => starter.style.display = 'none');
}
function showConversationStarters(chatContainer) {
const startersContainer = chatContainer.querySelector('.wpaicg-conversation-starters');
if (startersContainer) { // Check if the container exists
startersContainer.style.visibility = 'visible'; // Make the container visible if it exists
const starters = startersContainer.querySelectorAll('.wpaicg-conversation-starter');
starters.forEach((starter, index) => {
setTimeout(() => {
starter.style.opacity = "1";
starter.style.transform = "translateY(0)";
}, index * 150); // Staggered appearance
});
}
}
/**
* Adjusts the size of all shortcode-based chat windows (.wpaicg-chat-shortcode),
* including handling fullscreen mode and mobile layout. Now adds an extra offset
* on mobile when there is *no* footer to prevent the text box from being cut off.
*/
function wpaicgChatShortcodeSize() {
var wpaicgWindowWidth = window.innerWidth;
var wpaicgWindowHeight = window.innerHeight;
var chatShortcodes = document.getElementsByClassName('wpaicg-chat-shortcode');
if (chatShortcodes !== null && chatShortcodes.length) {
for (var i = 0; i < chatShortcodes.length; i++) {
var chatShortcode = chatShortcodes[i];
var parentChat = chatShortcode.parentElement;
var parentWidth = parentChat.offsetWidth;
var chatWidth = chatShortcode.getAttribute('data-width');
var chatHeight = chatShortcode.getAttribute('data-height');
var chatFooter = chatShortcode.getAttribute('data-footer') === 'true';
var chatBar = chatShortcode.getAttribute('data-has-bar') === 'true';
var chatRounded = parseFloat(chatShortcode.getAttribute('data-chat_rounded'));
var textRounded = parseFloat(chatShortcode.getAttribute('data-text_rounded'));
var textHeight = parseFloat(chatShortcode.getAttribute('data-text_height'));
var textInput = chatShortcode.getElementsByClassName('wpaicg-chat-shortcode-typing')[0];
// Set text area dimensions
textInput.style.height = textHeight + 'px';
textInput.style.borderRadius = textRounded + 'px';
// Round the chat container
chatShortcode.style.borderRadius = chatRounded + 'px';
chatShortcode.style.overflow = 'hidden';
// Fallback dimension if not supplied
chatWidth = chatWidth !== null ? chatWidth : '350';
chatHeight = chatHeight !== null ? chatHeight : '400';
// FULLSCREEN MODE
if (chatShortcode.classList.contains('wpaicg-fullscreened')) {
// Occupy the full window
parentWidth = wpaicgWindowWidth;
// On very narrow mobile, subtract a bit more from height
if (wpaicgWindowWidth < 480) {
chatWidth = wpaicgWindowWidth;
chatHeight = wpaicgWindowHeight - 40;
} else {
chatWidth = resolveDimension(chatWidth, parentWidth);
chatHeight = resolveDimension(chatHeight, wpaicgWindowHeight);
}
chatShortcode.style.width = chatWidth + 'px';
chatShortcode.style.maxWidth = chatWidth + 'px';
chatShortcode.style.height = chatHeight + 'px';
chatShortcode.style.maxHeight = chatHeight + 'px';
chatShortcode.style.marginTop = 0;
// Deduce space for header(s), action bar, etc.
var deduceHeight = 69; // Base
if (chatFooter) {
deduceHeight += 60; // Footer
}
if (chatBar) {
deduceHeight += 30; // Action bar
}
deduceHeight += 20; // Additional spacing
// On mobile, add some extra
if (wpaicgWindowWidth < 480) {
deduceHeight += 20;
}
// NEW: If no footer and on mobile, add offset so text box is visible
if (!chatFooter && wpaicgWindowWidth < 480) {
deduceHeight += 60;
}
var chatMessages = chatShortcode.getElementsByClassName('wpaicg-chat-shortcode-messages')[0];
chatMessages.style.height = (chatHeight - deduceHeight - textHeight) + 'px';
// Force scroll to bottom so new messages or input are visible
requestAnimationFrame(() => {
requestAnimationFrame(() => {
chatMessages.scrollTop = chatMessages.scrollHeight;
});
});
}
// NON-FULLSCREEN MODE
else {
// Resolve percentage or px for chatWidth
if (chatWidth.indexOf('%') < 0) {
if (chatWidth.indexOf('px') < 0) {
chatWidth = parseFloat(chatWidth);
} else {
chatWidth = parseFloat(chatWidth.replace(/px/g, ''));
}
} else {
chatWidth = parseFloat(chatWidth.replace(/%/g, ''));
if (chatWidth < 100) {
chatWidth = chatWidth * parentWidth / 100;
} else {
chatWidth = '';
}
}
// Resolve percentage or px for chatHeight
if (chatHeight.indexOf('%') < 0) {
if (chatHeight.indexOf('px') < 0) {
chatHeight = parseFloat(chatHeight);
} else {
chatHeight = parseFloat(chatHeight.replace(/px/g, ''));
}
} else {
chatHeight = parseFloat(chatHeight.replace(/%/g, ''));
chatHeight = chatHeight * wpaicgWindowHeight / 100;
}
// Apply final computed width & height
if (chatWidth !== '') {
chatShortcode.style.width = chatWidth + 'px';
chatShortcode.style.maxWidth = chatWidth + 'px';
} else {
chatShortcode.style.width = '';
chatShortcode.style.maxWidth = '';
}
if (chatShortcode.classList.contains('wpaicg-fullscreened')) {
chatShortcode.style.marginTop = 0;
} else {
chatShortcode.style.marginTop = '';
}
var chatMessages = chatShortcode.getElementsByClassName('wpaicg-chat-shortcode-messages')[0];
var deduceHeight = 69; // Base
if (chatFooter) {
deduceHeight += 60; // Footer
}
if (chatBar) {
deduceHeight += 30; // Action bar
}
deduceHeight += 20; // Additional spacing
// On mobile, add some extra
if (wpaicgWindowWidth < 480) {
deduceHeight += 20;
}
// NEW: If no footer and on mobile, add offset so text box is visible
if (!chatFooter && wpaicgWindowWidth < 480) {
deduceHeight += 60;
}
// Subtract the typed text box height
chatMessages.style.height = (chatHeight - deduceHeight - textHeight) + 'px';
}
}
}
}
/**
* Adjusts the size of widget-based chat windows (.wpaicg_chat_widget_content
* containing .wpaicg-chatbox). Also adds an extra offset on mobile when there's
* no footer so the text box remains visible in fullscreen.
*/
function wpaicgChatBoxSize() {
var wpaicgWindowWidth = window.innerWidth;
var wpaicgWindowHeight = window.innerHeight;
var chatWidgets = document.getElementsByClassName('wpaicg_chat_widget_content');
var isMobile = wpaicgWindowWidth <= 767;
if (chatWidgets.length) {
for (var i = 0; i < chatWidgets.length; i++) {
var chatWidget = chatWidgets[i];
var chatbox = chatWidget.getElementsByClassName('wpaicg-chatbox')[0];
if (!chatbox) continue;
var chatWidth = chatbox.getAttribute('data-width') || '350';
var chatHeight = chatbox.getAttribute('data-height') || '400';
var chatFooter = chatbox.getAttribute('data-footer') === 'true';
var chatboxBar = chatbox.getElementsByClassName('wpaicg-chatbox-action-bar');
var textHeight = parseFloat(chatbox.getAttribute('data-text_height'));
// If the widget container is in fullscreen mode or if it's a mobile device
if (chatWidget.classList.contains('wpaicg-fullscreened') || isMobile) {
// For mobile or fullscreen, use the full window dimensions
chatWidth = wpaicgWindowWidth;
chatHeight = wpaicgWindowHeight - (isMobile ? 60 : 20); // Extra offset for mobile
// Apply mobile-specific styling
if (isMobile) {
chatWidget.style.position = 'fixed';
chatWidget.style.top = '0';
chatWidget.style.left = '0';
chatWidget.style.right = '0';
chatWidget.style.bottom = '0';
chatWidget.style.zIndex = '999999';
chatWidget.style.width = '100%';
chatWidget.style.height = '100%';
chatWidget.style.maxWidth = 'none';
chatWidget.style.maxHeight = 'none';
}
} else {
// For desktop, use the user-defined dimensions
chatWidth = resolveDimension(chatWidth, wpaicgWindowWidth);
chatHeight = resolveDimension(chatHeight, wpaicgWindowHeight);
}
// Set dimensions for the chatbox and widget content
chatbox.style.width = chatWidth + 'px';
chatbox.style.height = chatHeight + 'px';
chatWidget.style.width = chatWidth + 'px';
chatWidget.style.height = chatHeight + 'px';
// Calculate the content height by deducting header, footer, action bar etc.
var actionBarHeight = chatboxBar.length ? 40 : 0;
var footerHeight = chatFooter ? 60 : 0;
var extraGap = (isMobile) ? 40 : 20;
// If no footer on mobile, still add 60px so text box isn't hidden
if (!chatFooter && isMobile) {
footerHeight = 60;
}
var contentHeight = chatHeight - textHeight - actionBarHeight - footerHeight - extraGap;
var messagesHeight = contentHeight - 20;
var chatboxContent = chatWidget.getElementsByClassName('wpaicg-chatbox-content')[0];
var chatboxMessages = chatWidget.getElementsByClassName('wpaicg-chatbox-messages')[0];
chatboxContent.style.height = contentHeight + 'px';
chatboxMessages.style.height = messagesHeight + 'px';
// Ensure we are scrolled to bottom for existing messages
chatboxMessages.scrollTop = chatboxMessages.scrollHeight;
}
}
}
function resolveDimension(value, totalSize) {
if (value.includes('%')) {
return parseFloat(value) / 100 * totalSize;
} else if (value.includes('px')) {
return parseFloat(value.replace('px', ''));
}
return parseFloat(value); // Default to parsing the value as pixels if no units are specified
}
function wpaicgChatInit() {
let wpaicgMicIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M176 0C123 0 80 43 80 96V256c0 53 43 96 96 96s96-43 96-96V96c0-53-43-96-96-96zM48 216c0-13.3-10.7-24-24-24s-24 10.7-24 24v40c0 89.1 66.2 162.7 152 174.4V464H104c-13.3 0-24 10.7-24 24s10.7 24 24 24h72 72c13.3 0 24-10.7 24-24s-10.7-24-24-24H200V430.4c85.8-11.7 152-85.3 152-174.4V216c0-13.3-10.7-24-24-24s-24 10.7-24 24v40c0 70.7-57.3 128-128 128s-128-57.3-128-128V216z"/></svg>';
let wpaicgStopIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm256-96a96 96 0 1 1 0 192 96 96 0 1 1 0-192zm0 224a128 128 0 1 0 0-256 128 128 0 1 0 0 256zm0-96a32 32 0 1 0 0-64 32 32 0 1 0 0 64z"/></svg>';
var wpaicgChatStream;
var wpaicgChatRec;
var wpaicgInput;
var wpaicgChatAudioContext = window.AudioContext || window['webkitAudioContext'];
var wpaicgaudioContext;
var wpaicgMicBtns = document.querySelectorAll('.wpaicg-mic-icon');
var wpaicgChatTyping = document.querySelectorAll('.wpaicg-chatbox-typing');
var wpaicgShortcodeTyping = document.querySelectorAll('.wpaicg-chat-shortcode-typing');
var wpaicgChatSend = document.querySelectorAll('.wpaicg-chatbox-send');
var wpaicgShortcodeSend = document.querySelectorAll('.wpaicg-chat-shortcode-send');
var wpaicgChatFullScreens = document.getElementsByClassName('wpaicg-chatbox-fullscreen');
var wpaicgChatCloseButtons = document.getElementsByClassName('wpaicg-chatbox-close-btn');
var wpaicgChatDownloadButtons = document.getElementsByClassName('wpaicg-chatbox-download-btn');
var wpaicg_chat_widget_toggles = document.getElementsByClassName('wpaicg_toggle');
var wpaicg_chat_widgets = document.getElementsByClassName('wpaicg_chat_widget');
var imageInputThumbnail = null; // Variable to store the image thumbnail
function setupConversationStarters() {
const starters = document.querySelectorAll('.wpaicg-conversation-starter');
starters.forEach(starter => {
starter.addEventListener('click', function () {
const messageText = starter.innerText || starter.textContent;
const chatContainer = starter.closest('.wpaicg-chat-shortcode') || starter.closest('.wpaicg-chatbox');
const type = chatContainer.classList.contains('wpaicg-chat-shortcode') ? 'shortcode' : 'widget';
const typingInput = type === 'shortcode' ? chatContainer.querySelector('.wpaicg-chat-shortcode-typing') : chatContainer.querySelector('.wpaicg-chatbox-typing');
typingInput.value = messageText;
wpaicgSendChatMessage(chatContainer, typingInput, type);
// Hide all starters
starters.forEach(starter => {
starter.style.display = 'none';
});
});
});
}
function maybeShowLeadForm(chat, chatId) {
// Helper function to interpret truthy values
function isTruthy(value) {
if (value === null || value === undefined) return false;
return value === '1' || value.toLowerCase() === 'true';
}
// Get the necessary data attributes
let leadCollectionEnabled = isTruthy(chat.getAttribute('data-lead-collection'));
if (!leadCollectionEnabled) {
return;
}
// Check if form has already been shown
let leadFormShown = localStorage.getItem('wpaicg_lead_form_shown');
if (leadFormShown === '1') {
return;
}
// Get the enable fields
let enableLeadName = isTruthy(chat.getAttribute('data-enable-lead-name'));
let enableLeadEmail = isTruthy(chat.getAttribute('data-enable-lead-email'));
let enableLeadPhone = isTruthy(chat.getAttribute('data-enable-lead-phone'));
// Check if at least one field is enabled
if (!(enableLeadName || enableLeadEmail || enableLeadPhone)) {
return;
}
// Get other data attributes
let leadTitle = chat.getAttribute('data-lead-title') || 'Contact Information';
let leadNameLabel = chat.getAttribute('data-lead-name') || 'Name';
let leadEmailLabel = chat.getAttribute('data-lead-email') || 'Email';
let leadPhoneLabel = chat.getAttribute('data-lead-phone') || 'Phone';
let wpaicg_nonce = chat.getAttribute('data-nonce');
let aiBg = chat.getAttribute('data-ai-bg-color');
let fontSize = chat.getAttribute('data-fontsize');
let fontColor = chat.getAttribute('data-color');
let text_field_bgcolor = chat.getAttribute('data-bg_text_field');
let text_field_font_color = chat.getAttribute('data-bg_text_field_font_color');
let text_field_border_color = chat.getAttribute('data-bg_text_field_border_color');
// Now, construct the form in JavaScript
// Define the form HTML within a chat message
let formHtml = `
<li class="wpaicg-lead-form-message" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}">
<div class="wpaicg-lead-form-container">
<button class="wpaicg-lead-form-close" style="float:right;">×</button>
<h2>${leadTitle}</h2>
<form>
`;
if (enableLeadName) {
formHtml += `
<div class="wpaicg-lead-form-field">
<label>${leadNameLabel}</label>
<input type="text" name="lead_name"/>
</div>
`;
}
if (enableLeadEmail) {
formHtml += `
<div class="wpaicg-lead-form-field">
<label>${leadEmailLabel}</label>
<input type="email" name="lead_email" />
</div>
`;
}
if (enableLeadPhone) {
formHtml += `
<div class="wpaicg-lead-form-field">
<label>${leadPhoneLabel}</label>
<input type="tel" name="lead_phone"/>
</div>
`;
}
// Add error message container
formHtml += `
<div class="wpaicg-lead-form-error" style="color: red; display: none;"></div>
`;
formHtml += `
<div class="svg-submit-button-container">
<button type="submit" class="svg-submit-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill=${fontColor} aria-hidden="true" width="24" height="24"><path d="M3.478 2.405a.75.75 0 00-.926.94l2.432 7.905H13.5a.75.75 0 010 1.5H4.984l-2.432 7.905a.75.75 0 00.926.94 60.519 60.519 0 0018.445-8.986.75.75 0 000-1.218A60.517 60.517 0 003.478 2.405z"></path></svg>
</button>
</div>
</form>
</div>
</li>
`;
// Append the form after the last AI message
let messagesList = chat.querySelector('.wpaicg-chatbox-messages') || chat.querySelector('.wpaicg-chat-shortcode-messages');
if (messagesList) {
messagesList.insertAdjacentHTML('beforeend', formHtml);
}
// Add CSS styles
let styles = `
.wpaicg-lead-form-message {
list-style: none;
padding: 10px;
margin-bottom: 10px;
position: relative;
}
.wpaicg-lead-form-container {
display: block;
}
.wpaicg-lead-form-container h2 {
margin-top: 0;
font-size: 1.2em;
margin-bottom: 10px;
color: ${fontColor};
padding-right: 40px;
}
.wpaicg-lead-form-close {
position: absolute;
top: 10px;
right: 10px;
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: ${fontColor};
}
.wpaicg-lead-form-field {
margin-bottom: 15px;
}
.wpaicg-lead-form-field label {
display: block;
margin-bottom: 5px;
}
.wpaicg-lead-form-field input {
color: ${text_field_font_color};
background-color: ${text_field_bgcolor};
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid ${text_field_border_color};
}
.svg-submit-button {
background-color: ${aiBg};
border: none;
cursor: pointer;
padding: 10px;
outline: none;
}
.svg-submit-button svg {
fill: ${fontColor}; /* Dynamic color */
transition: fill 0.3s ease;
}
/* Spinner inside button */
.wpaicg-lead-spinner {
display: inline-flex;
justify-content: center;
align-items: center;
}
.wpaicg-lead-spinner .dot {
font-size: 16px;
color: ${fontColor};
animation: jump 1s infinite;
margin: 0 2px;
}
/* Align submit button to the right */
.svg-submit-button-container {
text-align: right; /* This aligns the button to the right */
}
@keyframes jump {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-6px);
}
}
`;
// Create a style element and append to the head
let styleSheet = document.createElement("style");
styleSheet.innerText = styles;
document.head.appendChild(styleSheet);
// Add event listeners for form submission and close button
let formMessage = messagesList.querySelector('.wpaicg-lead-form-message');
let closeButton = formMessage.querySelector('.wpaicg-lead-form-close');
let form = formMessage.querySelector('form');
let errorDiv = form.querySelector('.wpaicg-lead-form-error');
closeButton.addEventListener('click', function () {
// Hide the form
formMessage.remove();
// Set that form has been shown
localStorage.setItem('wpaicg_lead_form_shown', '1');
});
form.addEventListener('submit', function (event) {
event.preventDefault();
// Validate that at least one field is filled
let nameInput = form.querySelector('input[name="lead_name"]');
let emailInput = form.querySelector('input[name="lead_email"]');
let phoneInput = form.querySelector('input[name="lead_phone"]');
let nameValue = nameInput ? nameInput.value.trim() : '';
let emailValue = emailInput ? emailInput.value.trim() : '';
let phoneValue = phoneInput ? phoneInput.value.trim() : '';
// If all fields are empty, display an error message and stop submission
if (!nameValue && !emailValue && !phoneValue) {
errorDiv.textContent = 'Please fill in at least one field.';
errorDiv.style.display = 'block';
return; // Stop form submission
} else {
errorDiv.textContent = '';
errorDiv.style.display = 'none';
}
// Get the submit button
let submitButton = form.querySelector('.svg-submit-button');
// Replace the button content with the spinner (jumping dots)
submitButton.innerHTML = `
<div class="wpaicg-lead-spinner">
<span class="dot">•</span>
<span class="dot">•</span>
<span class="dot">•</span>
</div>
`;
submitButton.disabled = true; // Disable the button while submitting
// Collect the data
let formData = new FormData(form);
formData.append('action', 'wpaicg_submit_lead');
formData.append('_wpnonce', wpaicg_nonce);
// Include chatId
formData.append('chatId', chatId);
// Send the data via AJAX
fetch(wpaicgParams.ajax_url, {
method: 'POST',
body: formData
}).then(response => response.json())
.then(data => {
formMessage.remove();
// Mark form as shown
localStorage.setItem('wpaicg_lead_form_shown', '1');
}).catch(error => {
console.error('Error submitting lead form:', error);
formMessage.remove();
localStorage.setItem('wpaicg_lead_form_shown', '1');
}).finally(() => {
// Restore original SVG icon in the button and re-enable it
submitButton.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="${fontColor}" class="bi bi-send" viewBox="0 0 16 16">
<path d="M15.5 0.5a.5.5 0 0 0-.854-.353L.646 14.646a.5.5 0 0 0 .708.708L15.5 0.854A.5.5 0 0 0 15.5 0.5z"/>
<path d="M6.646 15.646l8-8a.5.5 0 0 0-.708-.708l-8 8a.5.5 0 1 0 .708.708z"/>
<path d="M4.5 3.5a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6z"/>
</svg>
`;
submitButton.disabled = false;
});
});
}
setupConversationStarters();
var wpaicgUserAudioEnabled = {}; // Object to track audio settings per chat instance
// Function to set up event listeners on audio buttons
function setupAudioButtons() {
var wpaicgAudioButtons = document.querySelectorAll('.wpaicg-chatbox-audio-btn');
wpaicgAudioButtons.forEach(button => {
var chatContainer = button.closest('.wpaicg-chat-shortcode, .wpaicg-chatbox');
var botId = chatContainer.getAttribute('data-bot-id') || '0';
var chatType = chatContainer.getAttribute('data-type') || 'shortcode';
// Create a unique key for each bot based on bot-id and chat-type
var audioKey = `audio_${chatType}_${botId}`;
// Get muted state from data attribute (1 = muted, 0 or absent = unmuted)
var voiceMutedByDefault = chatContainer.getAttribute('data-voice-muted-by-default') === '1';
// Initialize the audio button state based on the default mode
if (voiceMutedByDefault) {
// Audio is muted by default
wpaicgUserAudioEnabled[audioKey] = false;
button.classList.remove('wpaicg-audio-enabled');
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3zM425 167l55 55 55-55c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-55 55 55 55c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-55-55-55 55c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l55-55-55-55c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0z"/></svg>'; // Muted icon SVG
} else {
// Audio is not muted by default
wpaicgUserAudioEnabled[audioKey] = true;
button.classList.add('wpaicg-audio-enabled');
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M533.6 32.5C598.5 85.2 640 165.8 640 256s-41.5 170.7-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z"/></svg>'; // Unmuted icon SVG
}
// Set up click listener to toggle audio state
button.addEventListener('click', function () {
wpaicgUserAudioEnabled[audioKey] = !wpaicgUserAudioEnabled[audioKey];
if (wpaicgUserAudioEnabled[audioKey]) {
// Audio is now enabled
button.classList.add('wpaicg-audio-enabled');
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M533.6 32.5C598.5 85.2 640 165.8 640 256s-41.5 170.7-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z"/></svg>'; // Unmuted icon SVG
} else {
// Audio is now disabled
button.classList.remove('wpaicg-audio-enabled');
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3zM425 167l55 55 55-55c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-55 55 55 55c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-55-55-55 55c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l55-55-55-55c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0z"/></svg>'; // Muted icon SVG
}
});
});
}
// Call the setup function
setupAudioButtons();
var imageIcon = document.querySelector('.wpaicg-img-icon');
var spinner = document.querySelector('.wpaicg-img-spinner');
var thumbnailPlaceholder = document.querySelector('.wpaicg-thumbnail-placeholder');
if (imageIcon) {
imageIcon.addEventListener('click', function () {
var imageInput = document.getElementById('imageUpload');
imageInput.click();
});
}
var imageInput = document.getElementById('imageUpload');
if (imageInput) {
imageInput.addEventListener('change', function () {
if (this.files && this.files[0]) {
var file = this.files[0]; // Store the file reference here
// Show the spinner and hide the image icon
imageIcon.style.display = 'none';
spinner.style.display = 'inline-block';
imageIcon.title = file.name; // Optional: show image name on hover
// Hide the spinner and show the image icon and thumbnail after a delay
setTimeout(function () {
spinner.style.display = 'none';
imageIcon.style.display = 'inline-block'; // Re-display image icon
// Now set the thumbnail image using the stored file reference
thumbnailPlaceholder.style.backgroundImage = `url(${URL.createObjectURL(file)})`;
thumbnailPlaceholder.style.backgroundSize = 'cover';
thumbnailPlaceholder.style.backgroundPosition = 'center';
thumbnailPlaceholder.style.backgroundRepeat = 'no-repeat';
thumbnailPlaceholder.style.display = 'inline-block'; // Display thumbnail
}, 2000);
}
});
}
// Function to set up event listeners on all clear chat buttons
function setupClearChatButtons() {
var wpaicgChatClearButtons = document.querySelectorAll('.wpaicg-chatbox-clear-btn');
wpaicgChatClearButtons.forEach(button => {
button.addEventListener('click', function () {
var chatContainer = button.closest('[data-bot-id]'); // Finds the nearest parent with 'data-bot-id'
if (chatContainer) {
var botId = chatContainer.getAttribute('data-bot-id') || '0';
var clientId = localStorage.getItem('wpaicg_chat_client_id');
clearChatHistory(botId, clientId, chatContainer);
}
});
});
}
// Function to clear the chat history from local storage and the display
function clearChatHistory(botId, clientId, chatContainer) {
var isCustomBot = botId !== '0';
var type = chatContainer.classList.contains('wpaicg-chat-shortcode') ? 'shortcode' : 'widget'; // Determine the type based on class
// First, get the active conversation key
const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientId}`;
const currentConversationKey = localStorage.getItem(activeConversationKey);
if (currentConversationKey) {
// Remove the active conversation from localStorage
localStorage.removeItem(currentConversationKey);
// Also remove its timestamp
localStorage.removeItem(`${currentConversationKey}_timestamp`);
} else {
// Fallback if no active conversation: clear using base key
var historyKey = isCustomBot
? `wpaicg_chat_history_custom_bot_${botId}_${clientId}`
: `wpaicg_chat_history_${type}_${clientId}`; // Adjust history key based on type
// Get all keys that start with this prefix
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith(historyKey)) {
localStorage.removeItem(key);
}
}
}
// Clear the chat display
var chatBoxSelector = '.wpaicg-chatbox-messages, .wpaicg-chat-shortcode-messages'; // Generalized selector for both types
var chatBox = chatContainer.querySelector(chatBoxSelector);
if (chatBox) {
chatBox.innerHTML = ''; // Clear the chat box visually
}
// delete wpaicg_lead_form_shown if exists
localStorage.removeItem('wpaicg_lead_form_shown');
// Check if wpaicg_thread_list exists in local storage (for assistant API)
var threadList = localStorage.getItem('wpaicg_thread_list');
if (threadList) {
try {
var threadListObj = JSON.parse(threadList);
// Construct the key for the thread to be deleted
var threadKey = isCustomBot
? `custom_bot_${botId}_${clientId}`
: `${type}_${clientId}`;
// If we have a specific active thread key, clear only that one
const activeThreadKey = `wpaicg_current_thread_${botId}_${clientId}`;
const currentThreadKey = localStorage.getItem(activeThreadKey);
if (currentThreadKey && threadListObj[currentThreadKey]) {
// Delete this specific thread
delete threadListObj[currentThreadKey];
} else {
// Otherwise delete all threads matching the base prefix
for (let existingKey in threadListObj) {
if (existingKey.startsWith(threadKey)) {
delete threadListObj[existingKey];
}
}
}
// Update the local storage with the modified thread list
localStorage.setItem('wpaicg_thread_list', JSON.stringify(threadListObj));
} catch (error) {
console.error('Error parsing wpaicg_thread_list:', error);
}
}
// Show conversation starters after clearing
showConversationStarters(chatContainer);
// Refresh the conversation list in the sidebar
loadConversationList();
}
// Call this function once your DOM is fully loaded or at the end of your script
setupClearChatButtons();
if (wpaicg_chat_widget_toggles !== null && wpaicg_chat_widgets.length) {
for (var i = 0; i < wpaicg_chat_widget_toggles.length; i++) {
var wpaicg_chat_widget_toggle = wpaicg_chat_widget_toggles[i];
var wpaicg_chat_widget = wpaicg_chat_widget_toggle.closest('.wpaicg_chat_widget');
wpaicg_chat_widget_toggle.addEventListener('click', function (e) {
e.preventDefault();
wpaicg_chat_widget_toggle = e.currentTarget;
if (wpaicg_chat_widget_toggle.classList.contains('wpaicg_widget_open')) {
wpaicg_chat_widget_toggle.classList.remove('wpaicg_widget_open');
wpaicg_chat_widget.classList.remove('wpaicg_widget_open');
} else {
wpaicg_chat_widget.classList.add('wpaicg_widget_open');
wpaicg_chat_widget_toggle.classList.add('wpaicg_widget_open');
// Check if it's a mobile device (width <= 767px)
if (window.innerWidth <= 767) {
// Get the widget content
var widgetContent = wpaicg_chat_widget.querySelector('.wpaicg_chat_widget_content');
// Set it to fullscreen for mobile
if (widgetContent && !widgetContent.classList.contains('wpaicg-fullscreened')) {
var fullscreenBtn = widgetContent.querySelector('.wpaicg-chatbox-fullscreen');
if (fullscreenBtn) {
// Trigger fullscreen mode
wpaicgFullScreen(fullscreenBtn);
// Hide the fullscreen button on mobile
fullscreenBtn.style.display = 'none';
}
}
}
}
});
}
}
if (wpaicgChatDownloadButtons.length) {
for (var i = 0; i < wpaicgChatDownloadButtons.length; i++) {
var wpaicgChatDownloadButton = wpaicgChatDownloadButtons[i];
wpaicgChatDownloadButton.addEventListener('click', function (e) {
wpaicgChatDownloadButton = e.currentTarget;
var type = wpaicgChatDownloadButton.getAttribute('data-type');
var wpaicgWidgetContent, listMessages;
if (type === 'shortcode') {
wpaicgWidgetContent = wpaicgChatDownloadButton.closest('.wpaicg-chat-shortcode');
listMessages = wpaicgWidgetContent.getElementsByClassName('wpaicg-chat-shortcode-messages');
}
else {
wpaicgWidgetContent = wpaicgChatDownloadButton.closest('.wpaicg_chat_widget_content');
listMessages = wpaicgWidgetContent.getElementsByClassName('wpaicg-chatbox-messages');
}
if (listMessages.length) {
var listMessage = listMessages[0];
var messages = [];
var chatMessages = listMessage.getElementsByTagName('li');
if (chatMessages.length) {
for (var i = 0; i < chatMessages.length; i++) {
messages.push(chatMessages[i].innerText.replace("\n", ' '));
}
}
var messagesDownload = messages.join("\n");
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(messagesDownload));
element.setAttribute('download', 'chat.txt');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
})
}
}
if (wpaicgChatCloseButtons.length) {
for (var i = 0; i < wpaicgChatCloseButtons.length; i++) {
var wpaicgChatCloseButton = wpaicgChatCloseButtons[i];
wpaicgChatCloseButton.addEventListener('click', function (e) {
wpaicgChatCloseButton = e.currentTarget;
var wpaicgWidgetContent = wpaicgChatCloseButton.closest('.wpaicg_chat_widget_content');
var chatbox = wpaicgWidgetContent.closest('.wpaicg_chat_widget');
if (wpaicgWidgetContent.classList.contains('wpaicg-fullscreened')) {
var fullScreenBtn = wpaicgWidgetContent.getElementsByClassName('wpaicg-chatbox-fullscreen')[0];
wpaicgFullScreen(fullScreenBtn);
}
chatbox.getElementsByClassName('wpaicg_toggle')[0].click();
})
}
}
/**
* Toggle fullscreen mode for either a shortcode chat or a widget chat.
* Ensures that upon restore, the prior sizing/layout is fully restored,
* including footer, text box, etc.
*
* @param {HTMLElement} btn - The clicked fullscreen button.
*/
function wpaicgFullScreen(btn) {
const type = btn.getAttribute('data-type');
const isExitingFullscreen = btn.classList.contains('wpaicg-fullscreen-box');
const isMobile = window.innerWidth <= 767;
if (type === 'shortcode') {
const wpaicgChatShortcode = btn.closest('.wpaicg-chat-shortcode');
if (!wpaicgChatShortcode) return;
// Exiting fullscreen
if (isExitingFullscreen) {
btn.classList.remove('wpaicg-fullscreen-box');
wpaicgChatShortcode.classList.remove('wpaicg-fullscreened');
// Restore old inline style
const oldInlineStyle = wpaicgChatShortcode.getAttribute('data-old-inline-style') || '';
wpaicgChatShortcode.setAttribute('style', oldInlineStyle);
wpaicgChatShortcode.removeAttribute('data-old-inline-style');
// Restore old data-width / data-height
const oldDataWidth = wpaicgChatShortcode.getAttribute('data-old-width');
const oldDataHeight = wpaicgChatShortcode.getAttribute('data-old-height');
if (oldDataWidth !== null) {
wpaicgChatShortcode.setAttribute('data-width', oldDataWidth);
wpaicgChatShortcode.removeAttribute('data-old-width');
}
if (oldDataHeight !== null) {
wpaicgChatShortcode.setAttribute('data-height', oldDataHeight);
wpaicgChatShortcode.removeAttribute('data-old-height');
}
// Rerun sizing
wpaicgChatShortcodeSize();
}
// Entering fullscreen
else {
btn.classList.add('wpaicg-fullscreen-box');
wpaicgChatShortcode.classList.add('wpaicg-fullscreened');
// Save existing style
const currentInline = wpaicgChatShortcode.getAttribute('style') || '';
wpaicgChatShortcode.setAttribute('data-old-inline-style', currentInline);
// Save data-width / data-height
const currentDataWidth = wpaicgChatShortcode.getAttribute('data-width') || '';
const currentDataHeight = wpaicgChatShortcode.getAttribute('data-height') || '';
wpaicgChatShortcode.setAttribute('data-old-width', currentDataWidth);
wpaicgChatShortcode.setAttribute('data-old-height', currentDataHeight);
// Apply fixed positioning for fullscreen
wpaicgChatShortcode.style.position = 'fixed';
wpaicgChatShortcode.style.top = '0';
wpaicgChatShortcode.style.left = '0';
wpaicgChatShortcode.style.zIndex = '999999999';
wpaicgChatShortcode.style.width = '100%';
// Use slight offset so the text area & footer are fully visible
wpaicgChatShortcode.style.height = 'calc(100vh - 20px)';
wpaicgChatShortcode.style.overflowY = 'auto';
// Update data-width / data-height to reflect 100% fill
wpaicgChatShortcode.setAttribute('data-width', '100%');
// Use 100% but we do an inline "calc(100vh - 20px)" so that the JS dimensioning can handle subtracting
wpaicgChatShortcode.setAttribute('data-height', '100%');
// Re-run sizing
wpaicgChatShortcodeSize();
}
}
else {
// For a widget
const wpaicgWidgetContent = btn.closest('.wpaicg_chat_widget_content');
if (!wpaicgWidgetContent) return;
const chatbox = wpaicgWidgetContent.querySelector('.wpaicg-chatbox');
if (!chatbox) return;
// Exiting fullscreen
if (isExitingFullscreen) {
btn.classList.remove('wpaicg-fullscreen-box');
wpaicgWidgetContent.classList.remove('wpaicg-fullscreened');
// Don't restore original dimensions if on mobile
if (!isMobile) {
// Restore old inline style for wpaicg_chat_widget_content
const oldWidgetStyle = wpaicgWidgetContent.getAttribute('data-old-inline-style') || '';
wpaicgWidgetContent.setAttribute('style', oldWidgetStyle);
wpaicgWidgetContent.removeAttribute('data-old-inline-style');
// Restore old inline style for chatbox
const oldChatboxStyle = chatbox.getAttribute('data-old-inline-style') || '';
chatbox.setAttribute('style', oldChatboxStyle);
chatbox.removeAttribute('data-old-inline-style');
// Restore data-width / data-height
const oldWidth = chatbox.getAttribute('data-old-width');
const oldHeight = chatbox.getAttribute('data-old-height');
if (oldWidth !== null) {
chatbox.setAttribute('data-width', oldWidth);
chatbox.removeAttribute('data-old-width');
}
if (oldHeight !== null) {
chatbox.setAttribute('data-height', oldHeight);
chatbox.removeAttribute('data-old-height');
}
}
// Re-run the widget sizing
wpaicgChatBoxSize();
}
// Entering fullscreen
else {
btn.classList.add('wpaicg-fullscreen-box');
wpaicgWidgetContent.classList.add('wpaicg-fullscreened');
// Save wpaicg_chat_widget_content style
const currentWidgetStyle = wpaicgWidgetContent.getAttribute('style') || '';
wpaicgWidgetContent.setAttribute('data-old-inline-style', currentWidgetStyle);
// Save chatbox style
const currentChatboxStyle = chatbox.getAttribute('style') || '';
chatbox.setAttribute('data-old-inline-style', currentChatboxStyle);
// Also store data-width / data-height
const currentDataWidth = chatbox.getAttribute('data-width') || '';
const currentDataHeight = chatbox.getAttribute('data-height') || '';
chatbox.setAttribute('data-old-width', currentDataWidth);
chatbox.setAttribute('data-old-height', currentDataHeight);
// Apply fullscreen styling with mobile-specific adjustments
wpaicgWidgetContent.style.position = 'fixed';
wpaicgWidgetContent.style.top = '0';
wpaicgWidgetContent.style.left = '0';
wpaicgWidgetContent.style.width = '100%';
wpaicgWidgetContent.style.height = isMobile ? '100%' : 'calc(100vh - 20px)';
wpaicgWidgetContent.style.zIndex = '999999999';
wpaicgWidgetContent.style.overflowY = 'auto';
// Expand chatbox to fill container
chatbox.style.width = '100%';
chatbox.style.height = isMobile ? '100%' : 'calc(100vh - 20px)';
chatbox.style.overflowY = 'auto';
// Set maximum sizes for mobile
if (isMobile) {
chatbox.style.maxWidth = '100%';
chatbox.style.maxHeight = '100%';
wpaicgWidgetContent.style.maxWidth = '100%';
wpaicgWidgetContent.style.maxHeight = '100%';
// Hide the fullscreen button on mobile devices when in fullscreen mode
btn.style.display = 'none';
}
// Update data attributes
chatbox.setAttribute('data-width', '100%');
chatbox.setAttribute('data-height', '100%');
// Re-run widget sizing
wpaicgChatBoxSize();
}
}
}
if (wpaicgChatFullScreens.length) {
for (var i = 0; i < wpaicgChatFullScreens.length; i++) {
var wpaicgChatFullScreen = wpaicgChatFullScreens[i];
wpaicgChatFullScreen.addEventListener('click', function (e) {
wpaicgFullScreen(e.currentTarget);
})
}
}
function resizeChatWidgets() {
if (wpaicg_chat_widgets !== null && wpaicg_chat_widgets.length) {
for (var i = 0; i < wpaicg_chat_widgets.length; i++) {
var wpaicg_chat_widget = wpaicg_chat_widgets[i];
if (window.innerWidth < 350) {
wpaicg_chat_widget.getElementsByClassName('wpaicg-chatbox')[0].style.width = window.innerWidth + 'px';
wpaicg_chat_widget.getElementsByClassName('wpaicg_chat_widget_content')[0].style.width = window.innerWidth + 'px';
}
}
}
}
// Function to check if device is mobile based on width
function isMobileDevice() {
return window.innerWidth <= 767;
}
// Add a resize listener to handle orientation changes
window.addEventListener('resize', function () {
wpaicgChatBoxSize();
wpaicgChatShortcodeSize();
resizeChatWidgets();
// Check if device changed from mobile to desktop
if (window.innerWidth > 767) {
// Find all fullscreen buttons in fullscreened widgets
document.querySelectorAll('.wpaicg-fullscreened .wpaicg-chatbox-fullscreen').forEach(btn => {
// Make sure the button is visible on desktop
btn.style.display = '';
});
} else {
// Hide fullscreen button on mobile in fullscreen mode
document.querySelectorAll('.wpaicg-fullscreened .wpaicg-chatbox-fullscreen').forEach(btn => {
btn.style.display = 'none';
});
}
// When orientation changes, ensure mobile fullscreen is maintained
if (isMobileDevice()) {
const openWidgets = document.querySelectorAll('.wpaicg_widget_open .wpaicg_chat_widget_content');
openWidgets.forEach(widget => {
if (!widget.classList.contains('wpaicg-fullscreened')) {
const fullscreenBtn = widget.querySelector('.wpaicg-chatbox-fullscreen');
if (fullscreenBtn) {
wpaicgFullScreen(fullscreenBtn);
// Hide the fullscreen button
fullscreenBtn.style.display = 'none';
}
}
});
}
});
wpaicgChatShortcodeSize();
wpaicgChatBoxSize();
resizeChatWidgets();
function wpaicgescapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function wpaicgstartChatRecording() {
let constraints = { audio: true, video: false }
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
wpaicgaudioContext = new wpaicgChatAudioContext();
wpaicgChatStream = stream;
wpaicgInput = wpaicgaudioContext.createMediaStreamSource(stream);
wpaicgChatRec = new Recorder(wpaicgInput, { numChannels: 1 });
wpaicgChatRec.record();
})
}
function wpaicgstopChatRecording(mic) {
wpaicgChatRec.stop();
wpaicgChatStream.getAudioTracks()[0].stop();
wpaicgChatRec.exportWAV(function (blob) {
let type = mic.getAttribute('data-type');
let parentChat;
let chatContent;
let chatTyping;
if (type === 'widget') {
parentChat = mic.closest('.wpaicg-chatbox');
chatContent = parentChat.querySelectorAll('.wpaicg-chatbox-content')[0];
chatTyping = parentChat.querySelectorAll('.wpaicg-chatbox-typing')[0];
} else {
parentChat = mic.closest('.wpaicg-chat-shortcode');
chatContent = parentChat.querySelectorAll('.wpaicg-chat-shortcode-content')[0];
chatTyping = parentChat.querySelectorAll('.wpaicg-chat-shortcode-typing')[0];
}
wpaicgSendChatMessage(parentChat, chatTyping, type, blob);
});
}
// Function to generate a random string
function generateRandomString(length) {
let result = '';
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
function setupButtonListeners(isCopyEnabled, isFeedbackEnabled, class_ai_item, emptyClipboardSVG, checkedClipboardSVG, thumbsUpSVG, thumbsDownSVG, showFeedbackModal, aiBg, fontColor, usrBg, chat, wpaicg_nonce, chatbot_identity) {
let hideTimeout;
// Show buttons on hover or touchstart of the icon container
jQuery(document).on('mouseenter touchstart', `li.${class_ai_item} .wpaicg-icon-container`, function (event) {
clearTimeout(hideTimeout);
// Prevent triggering on scroll or unintended touches
if (event.type === 'touchstart') {
event.stopPropagation();
}
const buttons = jQuery(this).find('.wpaicg-copy-button, .wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button');
// Display copy button if enabled
if (isCopyEnabled) {
buttons.filter('.wpaicg-copy-button').css({
display: 'inline-block',
opacity: 1,
visibility: 'visible'
});
}
// Display feedback buttons if enabled
if (isFeedbackEnabled) {
buttons.filter('.wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button').css({
display: 'inline-block',
opacity: 1,
visibility: 'visible'
});
}
});
// Hide buttons after leaving the icon container
jQuery(document).on('mouseleave touchend', `li.${class_ai_item} .wpaicg-icon-container`, function () {
const buttons = jQuery(this).find('.wpaicg-copy-button, .wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button');
hideTimeout = setTimeout(() => {
buttons.css({
opacity: 0,
visibility: 'hidden',
display: 'none'
});
}, 2000);
});
// Copy text functionality remains unchanged
jQuery(document).on('click', '.wpaicg-copy-button', function () {
const chatId = jQuery(this).data('chat-id');
const messageText = document.getElementById(chatId).innerText;
navigator.clipboard.writeText(messageText).then(() => {
// Change icon to check mark
jQuery(this).html(checkedClipboardSVG);
setTimeout(() => {
// Reset icon to original after 2 seconds
jQuery(this).html(emptyClipboardSVG);
}, 2000);
}).catch(err => console.error('Failed to copy text: ', err));
});
// Feedback functionality remains unchanged
jQuery(document).on('click', '.wpaicg-thumbs-up-button, .wpaicg-thumbs-down-button', function () {
const feedbackType = jQuery(this).hasClass('wpaicg-thumbs-up-button') ? 'up' : 'down';
const chatId = jQuery(this).data('chat-id').replace('wpaicg-chat-message-', '');
showFeedbackModal(feedbackType, chatId, aiBg, fontColor, usrBg, chat, wpaicg_nonce, chatbot_identity);
});
}
function showFeedbackModal(feedbackType, chatId, bgColor, textColor, usrBg, chat, wpaicg_nonce, chatbot_identity) {
const chatWidget = jQuery('.wpaicg_chat_widget');
const feedbackTitle = chat.getAttribute('data-feedback_title') || 'Feedback';
const feedbackMessage = chat.getAttribute('data-feedback_message') || 'Please provide details: (optional)';
const feedbackSuccessMessage = chat.getAttribute('data-feedback_success') || 'Thank you for your feedback!';
const chatShortcode = jQuery(chat).closest('.wpaicg-chat-shortcode');
const wasFullscreen = chatShortcode.hasClass('wpaicg-fullscreened');
if (wasFullscreen) {
// Exit fullscreen mode before showing feedback modal
const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen');
wpaicgFullScreen(fullScreenBtn[0]); // Exit fullscreen
}
if (chatWidget.hasClass('wpaicg_widget_open')) {
chatWidget.data('was-open', true);
chatWidget.removeClass('wpaicg_widget_open');
} else {
chatWidget.data('was-open', false);
}
const modalHtml = `
<style>
@keyframes wpaicg-feedback-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.wpaicg-feedback-spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid ${textColor};
border-top: 2px solid ${bgColor};
border-radius: 50%;
animation: wpaicg-feedback-spin 1s linear infinite;
}
.wpaicg-feedback-message {
color: ${textColor};
}
</style>
<div class="wpaicg-feedback-modal-overlay">
<div class="wpaicg-feedback-modal" style="background-color:${bgColor};color:${textColor};position:relative;">
<button class="wpaicg-feedback-modal-close" style="position:absolute; top:10px; right:10px; background:none; border:none; color:${textColor}; font-size:18px; cursor:pointer;">×</button>
<h2 style="background-color:${bgColor};color:${textColor};">${feedbackTitle}</h2>
<p>${feedbackMessage}</p>
<textarea class="wpaicg-feedback-textarea"></textarea>
<div class="wpaicg-feedback-modal-buttons">
<div class="wpaicg-feedback-message" style="display:none;"></div>
<button class="wpaicg-feedback-modal-submit" style="background-color:${usrBg};color:${textColor};border:none;" data-feedback-type="${feedbackType}" data-chat-id="${chatId}">
Submit
<span class="wpaicg-feedback-spinner" style="display:none; margin-left:5px; border: 2px solid ${textColor}; border-top: 2px solid ${bgColor}; border-radius: 50%; width: 16px; height: 16px; animation: wpaicg-feedback-spin 1s linear infinite;"></span>
</button>
</div>
</div>
</div>
`;
jQuery('body').append(modalHtml);
jQuery('.wpaicg-feedback-modal-close').on('click', function () {
jQuery('.wpaicg-feedback-modal-overlay').fadeOut(300, function () {
jQuery(this).remove();
if (wasFullscreen) {
// Restore fullscreen after feedback modal is closed
const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen');
wpaicgFullScreen(fullScreenBtn[0]); // Re-enter fullscreen
}
if (chatWidget.data('was-open')) {
chatWidget.addClass('wpaicg_widget_open');
}
});
});
jQuery('.wpaicg-feedback-modal-submit').on('click', function () {
const modal = jQuery(this).closest('.wpaicg-feedback-modal');
const feedbackText = modal.find('.wpaicg-feedback-textarea').val();
const feedbackType = jQuery(this).data('feedback-type');
const chatId = jQuery(this).data('chat-id');
const nonce = wpaicg_nonce;
const submitButton = jQuery(this);
const spinner = submitButton.find('.wpaicg-feedback-spinner');
const feedbackMessageElement = modal.find('.wpaicg-feedback-message');
spinner.show();
submitButton.prop('disabled', true);
jQuery.ajax({
url: wpaicgParams.ajax_url,
method: 'POST',
data: {
action: 'wpaicg_submit_feedback',
chatId: chatId,
feedbackType: feedbackType,
feedbackDetails: feedbackText,
_wpnonce: nonce,
chatbot_id: chatbot_identity,
},
success: function (response) {
feedbackMessageElement.html(`<span style="color:${textColor};">${feedbackSuccessMessage}</span>`).fadeIn(300);
setTimeout(() => {
jQuery('.wpaicg-feedback-modal-overlay').fadeOut(300, function () {
jQuery(this).remove();
if (chatWidget.data('was-open')) {
chatWidget.addClass('wpaicg_widget_open');
}
if (wasFullscreen) {
const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen');
wpaicgFullScreen(fullScreenBtn[0]); // Re-enter fullscreen
}
});
}, 2000);
},
error: function (error) {
feedbackMessageElement.html(`<span style="color:${textColor};">Error. Please try again later.</span>`).fadeIn(300);
setTimeout(() => {
jQuery('.wpaicg-feedback-modal-overlay').fadeOut(300, function () {
jQuery(this).remove();
if (chatWidget.data('was-open')) {
chatWidget.addClass('wpaicg_widget_open');
}
if (wasFullscreen) {
const fullScreenBtn = chatShortcode.find('.wpaicg-chatbox-fullscreen');
wpaicgFullScreen(fullScreenBtn[0]); // Re-enter fullscreen
}
});
}, 2000);
},
complete: function () {
spinner.hide();
submitButton.prop('disabled', false);
}
});
});
}
/**
* Sends a user message (text or audio) to the server, handles response streaming or non-streaming,
* and updates the conversation history in localStorage.
*
* @param {HTMLElement} chat - The parent chat container (wpaicg-chat-shortcode or wpaicg-chatbox).
* @param {HTMLTextAreaElement} typing - The textarea input element.
* @param {string} type - Either "shortcode" or "widget", indicating chat type.
* @param {Blob} [blob] - Optional audio blob if the user recorded microphone input.
*/
function wpaicgSendChatMessage(chat, typing, type, blob) {
hideConversationStarters();
// Remove the lead form if it exists
var leadFormMessage = chat.querySelector('.wpaicg-lead-form-message');
if (leadFormMessage) {
leadFormMessage.remove();
// Optionally, set 'wpaicg_lead_form_shown' to '1' so it doesn't show again
localStorage.setItem('wpaicg_lead_form_shown', '1');
}
let botIdAudio = chat.getAttribute('data-bot-id') || '0';
let chatTypeAudio = chat.getAttribute('data-type') || 'shortcode';
let audioKey = `audio_${chatTypeAudio}_${botIdAudio}`; // Use the new key for audio state
let isAudioEnabledByUser = wpaicgUserAudioEnabled[audioKey]; // Get the audio state for this bot instance
let userVoiceControl = chat.getAttribute('data-user-voice-control');
let wpaicg_box_typing = typing;
let wpaicg_ai_thinking, wpaicg_messages_box, class_user_item, class_ai_item;
let wpaicgMessage = '';
let wpaicgData = new FormData();
let wpaicg_nonce = chat.getAttribute('data-nonce');
let wpaicg_bot_id = parseInt(chat.getAttribute('data-bot-id'));
let wpaicg_user_bg = chat.getAttribute('data-user-bg-color');
let wpaicg_font_size = chat.getAttribute('data-fontsize');
let wpaicg_speech = chat.getAttribute('data-speech');
let wpaicg_voice = chat.getAttribute('data-voice');
let elevenlabs_model = chat.getAttribute('data-elevenlabs-model');
if (elevenlabs_model === null || elevenlabs_model === undefined) {
elevenlabs_model = chat.getAttribute('data-elevenlabs_model');
}
let elevenlabs_voice = chat.getAttribute('data-elevenlabs-voice');
if (elevenlabs_voice === null || elevenlabs_voice === undefined) {
elevenlabs_voice = chat.getAttribute('data-elevenlabs_voice');
}
let wpaicg_voice_error = chat.getAttribute('data-voice-error');
let wpaicg_typewriter_effect = chat.getAttribute('data-typewriter-effect');
let wpaicg_typewriter_speed = chat.getAttribute('data-typewriter-speed');
let url = chat.getAttribute('data-url');
let post_id = chat.getAttribute('data-post-id');
let wpaicg_ai_bg = chat.getAttribute('data-ai-bg-color');
let wpaicg_font_color = chat.getAttribute('data-color');
let voice_service = chat.getAttribute('data-voice_service');
let voice_language = chat.getAttribute('data-voice_language');
let voice_name = chat.getAttribute('data-voice_name');
let voice_device = chat.getAttribute('data-voice_device');
let openai_model = chat.getAttribute('data-openai_model');
let openai_voice = chat.getAttribute('data-openai_voice');
let openai_output_format = chat.getAttribute('data-openai_output_format');
let openai_voice_speed = chat.getAttribute('data-openai_voice_speed');
let openai_stream_nav = chat.getAttribute('data-openai_stream_nav');
let voice_speed = chat.getAttribute('data-voice_speed');
let voice_pitch = chat.getAttribute('data-voice_pitch');
var chat_pdf = chat.getAttribute('data-pdf');
// Handle image upload
var imageInput = document.getElementById('imageUpload');
var imageUrl = ''; // Variable to store the URL of the uploaded image for preview
if (imageInput) {
if (imageInput.files && imageInput.files[0]) {
var validImageTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp', 'image/gif'];
if (!validImageTypes.includes(imageInput.files[0].type)) {
alert('Invalid file type. Only PNG, JPEG, WEBP, and non-animated GIF images are allowed.');
return;
}
// Append image file to FormData object
wpaicgData.append('image', imageInput.files[0], imageInput.files[0].name);
// Create a URL for the uploaded image file for preview
imageUrl = URL.createObjectURL(imageInput.files[0]);
}
}
if (type === 'widget') {
wpaicg_ai_thinking = chat.getElementsByClassName('wpaicg-bot-thinking')[0];
wpaicg_messages_box = chat.getElementsByClassName('wpaicg-chatbox-messages')[0];
class_user_item = 'wpaicg-chat-user-message';
class_ai_item = 'wpaicg-chat-ai-message';
wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
// Retrieve all message elements
const messages = wpaicg_messages_box.querySelectorAll('li');
// Ensure messages exist and scroll to the last message
if (messages.length > 0) {
messages[messages.length - 1].scrollIntoView();
}
} else {
wpaicg_ai_thinking = chat.getElementsByClassName('wpaicg-bot-thinking')[0];
wpaicg_messages_box = chat.getElementsByClassName('wpaicg-chat-shortcode-messages')[0];
class_user_item = 'wpaicg-user-message';
class_ai_item = 'wpaicg-ai-message';
}
wpaicg_ai_thinking.style.display = 'block';
let wpaicg_question = wpaicgescapeHtml(wpaicg_box_typing.value);
if (!wpaicg_question.trim() && blob === undefined) {
wpaicg_ai_thinking.style.display = 'none';
return; // Exit the function if no message or blob is provided
}
wpaicgMessage += '<li class="' + class_user_item + '" style="background-color:' + wpaicg_user_bg + ';font-size: ' + wpaicg_font_size + 'px;color: ' + wpaicg_font_color + '">';
wpaicgData.append('_wpnonce', wpaicg_nonce);
wpaicgData.append('post_id', post_id);
if (chat_pdf && chat_pdf !== null) {
wpaicgData.append('namespace', chat_pdf);
}
wpaicgData.append('url', url);
if (type === 'widget') {
wpaicgData.append('action', 'wpaicg_chatbox_message');
} else {
wpaicgData.append('action', 'wpaicg_chat_shortcode_message');
}
if (blob !== undefined) {
let url = URL.createObjectURL(blob);
wpaicgMessage += '<audio src="' + url + '" controls="true"></audio>';
wpaicgData.append('audio', blob, 'wpaicg-chat-recording.wav');
} else if (wpaicg_question !== '') {
wpaicgData.append('message', wpaicg_question);
wpaicgMessage += wpaicg_question.replace(/\n/g, '<br>');
}
wpaicgData.append('bot_id', wpaicg_bot_id);
wpaicgMessage += '</li>';
// If an image URL is available, add an <img> tag to display the image
if (imageUrl !== '') {
wpaicgMessage += '<li class="' + class_user_item + '" style="background-color:' + wpaicg_user_bg + ';font-size: ' + wpaicg_font_size + 'px;color: ' + wpaicg_font_color + '">';
wpaicgMessage += '<div style="max-width: 300px; height: auto; display: flex;">';
wpaicgMessage += '<img src="' + imageUrl + '" style="max-width: 100%; height: auto;" onload="this.parentElement.parentElement.parentElement.scrollTop = this.parentElement.parentElement.parentElement.scrollHeight;">';
wpaicgMessage += '</div>'; // Closing the div tag
wpaicgMessage += '</li>';
}
wpaicg_messages_box.innerHTML += wpaicgMessage;
wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
// Hide the thumbnail placeholder
var thumbnailPlaceholder = document.querySelector('.wpaicg-thumbnail-placeholder');
if (thumbnailPlaceholder) {
thumbnailPlaceholder.style.display = 'none'; // Hide the thumbnail after message is sent
}
// Reset the image input after sending the message if imageInput exists first
if (imageInput) {
imageInput.value = '';
}
let chat_type = chat.getAttribute('data-type');
let stream_nav;
let chatbot_identity;
// Check if it's a bot with dynamic ID
if (wpaicg_bot_id && wpaicg_bot_id !== "0") {
stream_nav = openai_stream_nav;
chatbot_identity = 'custom_bot_' + wpaicg_bot_id;
} else {
// Check if it's a shortcode or widget based on chat_type
if (chat_type === "shortcode") {
stream_nav = chat.getAttribute('data-openai_stream_nav');
chatbot_identity = 'shortcode';
} else if (chat_type === "widget") {
stream_nav = chat.getAttribute('data-openai_stream_nav');
chatbot_identity = 'widget';
}
}
wpaicgData.append('chatbot_identity', chatbot_identity);
// Check for existing client_id in localStorage
let clientID = localStorage.getItem('wpaicg_chat_client_id');
if (!clientID) {
// Generate and store a new client ID if not found
clientID = generateRandomString(10); // Generate a 10 character string
localStorage.setItem('wpaicg_chat_client_id', clientID);
}
let botId = chat.getAttribute('data-bot-id') || '0';
let assistantEnabled = chat.getAttribute('data-assistant-enabled') === 'true';
// Include local conversation if any
let botIdAttr = botId || '0';
let activeConversationKey = `wpaicg_current_conversation_${botIdAttr}_${clientID}`;
let conversationKey = localStorage.getItem(activeConversationKey);
// If there's no active conversation yet, fallback to a default
if (!conversationKey) {
// e.g. wpaicg_chat_history_custom_bot_4367_{clientID}_1
let basePrefix = botIdAttr !== '0'
? `wpaicg_chat_history_custom_bot_${botIdAttr}_${clientID}`
: `wpaicg_chat_history_${type}_${clientID}`;
// Create the default index = 1 if not set
let newKey = `${basePrefix}_1`;
if (!localStorage.getItem(newKey)) {
localStorage.setItem(newKey, JSON.stringify([]));
}
localStorage.setItem(activeConversationKey, newKey);
conversationKey = newKey;
}
// Add the conversation history to form data
let storedHistory = localStorage.getItem(conversationKey) || '[]';
wpaicgData.append('wpaicg_chat_history', storedHistory);
//append client_id to wpaicgData
wpaicgData.append('wpaicg_chat_client_id', clientID);
// --------------- Assistant-Enabled logic for thread ---------------
let activeThreadKey = `wpaicg_current_thread_${botIdAttr}_${clientID}`;
let currentThreadStorageKey = null;
if (assistantEnabled) {
// We store or retrieve the "active thread" from localStorage
currentThreadStorageKey = localStorage.getItem(activeThreadKey);
if (!currentThreadStorageKey) {
// If user never clicked "New Chat," fallback to default index=1
let baseThreadPrefix = botIdAttr !== '0'
? `custom_bot_${botIdAttr}_${clientID}`
: `${type}_${clientID}`;
let foundMaxThreadIndex = 0;
const threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
for (let existingKey in threadListObj) {
if (existingKey.startsWith(baseThreadPrefix)) {
let suffixMatch = existingKey.match(/_(\d+)$/);
if (suffixMatch) {
let indexNum = parseInt(suffixMatch[1], 10);
if (indexNum > foundMaxThreadIndex) foundMaxThreadIndex = indexNum;
}
}
}
let defaultThreadIndex = foundMaxThreadIndex + 1;
currentThreadStorageKey = `${baseThreadPrefix}_${defaultThreadIndex}`;
localStorage.setItem(activeThreadKey, currentThreadStorageKey);
}
// If there's a real thread ID in localStorage for that key, we pass it
let threadListData = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
let existingThreadId = threadListData[currentThreadStorageKey];
if (existingThreadId) {
wpaicgData.append('thread_id', existingThreadId);
}
}
// Hide the image thumbnail after sending the message
if (imageInputThumbnail) {
imageInputThumbnail.style.display = 'none';
}
if (stream_nav === "1") {
// Update local storage
updateChatHistory(wpaicg_question, 'user', wpaicg_randomnum, chat, chatbot_identity, clientID);
if (assistantEnabled) {
// Call the new assistant streaming function
handleAssistantStreaming(
wpaicgData,
wpaicg_messages_box,
wpaicg_box_typing,
wpaicg_ai_thinking,
class_ai_item,
chat,
chatbot_identity,
clientID,
wpaicg_nonce
);
} else {
handleStreaming(wpaicgData, wpaicg_messages_box, wpaicg_box_typing, wpaicg_ai_thinking, class_ai_item, chat, chatbot_identity, clientID, wpaicg_nonce);
}
}
else {
updateChatHistory(wpaicg_question, 'user', wpaicg_randomnum, chat, chatbot_identity, clientID);
// We also pass a fresh random ID for chat message
var wpaicg_randomnum = Math.floor((Math.random() * 100000) + 1);
const chatId = `wpaicg-chat-message-${wpaicg_randomnum}`;
// Extract copy and feedback settings from the chat element
const copyEnabled = chat.getAttribute('data-copy_btn') === "1";
const feedbackEnabled = chat.getAttribute('data-feedback_btn') === "1";
const fontColor = chat.getAttribute('data-color');
const usrBg = chat.getAttribute('data-user-bg-color');
const emptyClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-copy" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/>
</svg>`;
const checkedClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-check2" viewBox="0 0 16 16">
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0"/>
</svg>`;
const thumbsUpSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">
<path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2 2 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a10 10 0 0 0-.443.05 9.4 9.4 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a9 9 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.2 2.2 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.9.9 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>
</svg>`;
const thumbsDownSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-hand-thumbs-down" viewBox="0 0 16 16">
<path d="M8.864 15.674c-.956.24-1.843-.484-1.908-1.42-.072-1.05-.23-2.015-.428-2.59-.125-.36-.479-1.012-1.04-1.638-.557-.624-1.282-1.179-2.131-1.41C2.685 8.432 2 7.85 2 7V3c0-.845.682-1.464 1.448-1.546 1.07-.113 1.564-.415 2.068-.723l.048-.029c.272-.166.578-.349.97-.484C6.931.08 7.395 0 8 0h3.5c.937 0 1.599.478 1.934 1.064.164.287.254.607.254.913 0 .152-.023.312-.077.464.201.262.38.577.488.9.11.33.172.762.004 1.15.069.13.12.268.159.403.077.27.113.567.113.856s-.036.586-.113.856c-.035.12-.08.244-.138.363.394.571.418 1.2.234 1.733-.206.592-.682 1.1-1.2 1.272-.847.283-1.803.276-2.516.211a10 10 0 0 1-.443-.05 9.36 9.36 0 0 1-.062 4.51c-.138.508-.55.848-1.012.964zM11.5 1H8c-.51 0-.863.068-1.14.163-.281.097-.506.229-.776.393l-.04.025c-.555.338-1.198.73-2.49.868-.333.035-.554.29-.554.55V7c0 .255.226.543.62.65 1.095.3 1.977.997 2.614 1.709.635.71 1.064 1.475 1.238 1.977.243.7.407 1.768.482 2.85.025.362.36.595.667.518l.262-.065c.16-.04.258-.144.288-.255a8.34 8.34 0 0 0-.145-4.726.5.5 0 0 1 .595-.643h.003l.014.004.058.013a9 9 0 0 0 1.036.157c.663.06 1.457.054 2.11-.163.175-.059.45-.301.57-.651.107-.308.087-.67-.266-1.021L12.793 7l.353-.354c.043-.042.105-.14.154-.315.048-.167.075-.37.075-.581s-.027-.414-.075-.581c-.05-.174-.111-.273-.154-.315l-.353-.354.353-.354c.047-.047.109-.176.005-.488a2.2 2.2 0 0 0-.505-.804l-.353-.354.353-.354c.006-.005.041-.05.041-.17a.9.9 0 0 0-.121-.415C12.4 1.272 12.063 1 11.5 1"/>
</svg>`;
const xhttp = new XMLHttpRequest();
wpaicg_box_typing.value = '';
xhttp.open('POST', wpaicgParams.ajax_url, true);
xhttp.send(wpaicgData);
xhttp.onreadystatechange = function (oEvent) {
if (xhttp.readyState === 4) {
var wpaicg_message = '';
var wpaicg_response_text = '';
if (xhttp.status === 200) {
var wpaicg_response = this.responseText;
if (wpaicg_response !== '') {
wpaicg_response = JSON.parse(wpaicg_response);
wpaicg_ai_thinking.style.display = 'none'
if (wpaicg_response.status === 'success') {
// If assistantEnabled and the response contains thread_id, store it:
if (assistantEnabled && wpaicg_response.thread_id) {
let threadList = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
threadList[currentThreadStorageKey] = wpaicg_response.thread_id;
localStorage.setItem('wpaicg_thread_list', JSON.stringify(threadList));
}
wpaicg_response_text = wpaicg_response.data;
wpaicg_message = `
<li class="${class_ai_item} wpaicg-icon-container" style="background-color:${wpaicg_ai_bg};font-size:${wpaicg_font_size}px;color:${wpaicg_font_color}">
<p style="width:100%">
<span class="wpaicg-chat-message" id="${chatId}">${wpaicg_response_text}</span>
${copyEnabled ? `<button class="wpaicg-copy-button" data-chat-id="${chatId}">${emptyClipboardSVG}</button>` : ''}
${feedbackEnabled ? `
<button class="wpaicg-thumbs-up-button" data-chat-id="${chatId}">${thumbsUpSVG}</button>
<button class="wpaicg-thumbs-down-button" data-chat-id="${chatId}">${thumbsDownSVG}</button>` : ''}
</p>
</li>
`;
} else {
wpaicg_response_text = wpaicg_response.msg;
wpaicg_message = '<li class="' + class_ai_item + '" style="background-color:' + wpaicg_ai_bg + ';font-size: ' + wpaicg_font_size + 'px;color: ' + wpaicg_font_color + '"><p style="width:100%"><span class="wpaicg-chat-message wpaicg-chat-message-error" id="wpaicg-chat-message-' + wpaicg_randomnum + '"></span>';
}
}
} else {
wpaicg_message = '<li class="' + class_ai_item + '" style="background-color:' + wpaicg_ai_bg + ';font-size: ' + wpaicg_font_size + 'px;color: ' + wpaicg_font_color + '"><p style="width:100%"><span class="wpaicg-chat-message wpaicg-chat-message-error" id="wpaicg-chat-message-' + wpaicg_randomnum + '"></span>';
wpaicg_response_text = 'Something went wrong. Please clear your cache and try again.';
wpaicg_ai_thinking.style.display = 'none';
}
if (wpaicg_response_text === 'null' || wpaicg_response_text === null) {
wpaicg_response_text = 'Empty response from api. Check your server logs for more details.';
}
setupButtonListeners(copyEnabled, feedbackEnabled, class_ai_item, emptyClipboardSVG, checkedClipboardSVG, thumbsUpSVG, thumbsDownSVG, showFeedbackModal, wpaicg_ai_bg, wpaicg_font_color, usrBg, chat, wpaicg_nonce, chatbot_identity);
const simpleChatId = chatId.replace('wpaicg-chat-message-', '');
updateChatHistory(wpaicg_response_text, 'ai', simpleChatId, chat, chatbot_identity, clientID);
if (wpaicg_response_text !== '' && wpaicg_message !== '') {
if (parseInt(wpaicg_speech) == 1 && (userVoiceControl == "1" ? isAudioEnabledByUser : true)) {
if (voice_service === 'google') {
wpaicg_ai_thinking.style.display = 'block';
let speechData = new FormData();
speechData.append('nonce', wpaicg_nonce);
speechData.append('action', 'wpaicg_google_speech');
speechData.append('language', voice_language);
speechData.append('name', voice_name);
speechData.append('device', voice_device);
speechData.append('speed', voice_speed);
speechData.append('pitch', voice_pitch);
speechData.append('text', wpaicg_response_text);
var speechRequest = new XMLHttpRequest();
speechRequest.open("POST", wpaicgParams.ajax_url);
speechRequest.onload = function () {
var result = speechRequest.responseText;
try {
result = JSON.parse(result);
if (result.status === 'success') {
var byteCharacters = atob(result.audio);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: 'audio/mp3' });
const blobUrl = URL.createObjectURL(blob);
wpaicg_message += '<audio style="margin-top:6px;" controls="controls"><source type="audio/mpeg" src="' + blobUrl + '"></audio>';
wpaicg_message += '</p></li>';
wpaicg_ai_thinking.style.display = 'none';
// scroll to the bottom of the chatbox
wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
}
else {
var errorMessageDetail = 'Google: ' + result.msg;
wpaicg_ai_thinking.style.display = 'none';
if (parseInt(wpaicg_voice_error) !== 1) {
wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">' + errorMessageDetail + '</span>';
}
else if (typeof wpaicg_response !== 'undefined' && typeof wpaicg_response.log !== 'undefined' && wpaicg_response.log !== '') {
var speechLogMessage = new FormData();
speechLogMessage.append('nonce', wpaicg_nonce);
speechLogMessage.append('log_id', wpaicg_response.log);
speechLogMessage.append('message', errorMessageDetail);
speechLogMessage.append('action', 'wpaicg_speech_error_log');
var speechErrorRequest = new XMLHttpRequest();
speechErrorRequest.open("POST", wpaicgParams.ajax_url);
speechErrorRequest.send(speechLogMessage);
}
wpaicg_message += '</p></li>';
wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
}
}
catch (errorSpeech) {
}
}
speechRequest.send(speechData);
}
else if (voice_service === 'openai') {
// OpenAI TTS code
let speechData = new FormData();
speechData.append('action', 'wpaicg_openai_speech');
speechData.append('nonce', wpaicg_nonce);
speechData.append('text', wpaicg_response_text);
speechData.append('model', openai_model);
speechData.append('voice', openai_voice);
speechData.append('output_format', openai_output_format);
speechData.append('speed', openai_voice_speed);
// Display some sort of loading indicator
wpaicg_ai_thinking.style.display = 'block';
var speechRequest = new XMLHttpRequest();
speechRequest.open("POST", wpaicgParams.ajax_url);
speechRequest.responseType = "arraybuffer"; // Expecting raw audio data
speechRequest.onload = function () {
if (speechRequest.status === 200) {
wpaicg_ai_thinking.style.display = 'none';
const audioData = speechRequest.response;
const blobMimeType = getBlobMimeType(openai_output_format); // Get the MIME type based on the format
const blob = new Blob([audioData], { type: blobMimeType });
const blobUrl = URL.createObjectURL(blob);
// Update your message UI here
wpaicg_message += '<audio style="margin-top:6px;" controls="controls"><source type="audio/mpeg" src="' + blobUrl + '"></audio>';
// scroll to the bottom of the chatbox
wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
} else {
// Handle HTTP errors
wpaicg_ai_thinking.style.display = 'none';
console.error('Error generating speech with OpenAI:', speechRequest.statusText);
// Update your message UI to show the error
wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">Error generating speech with OpenAI</span>';
wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
}
};
speechRequest.onerror = function () {
// Handle network errors
wpaicg_ai_thinking.style.display = 'none';
console.error('Network error during speech generation with OpenAI');
// Update your message UI to show the network error
wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">Network error during speech generation</span>';
wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
};
speechRequest.send(speechData);
// Utility function to get the correct MIME type
function getBlobMimeType(format) {
switch (format) {
case 'opus':
return 'audio/opus';
case 'aac':
return 'audio/aac';
case 'flac':
return 'audio/flac';
default:
return 'audio/mpeg'; // Default to MP3
}
}
}
else {
let speechData = new FormData();
speechData.append('nonce', wpaicg_nonce);
speechData.append('message', wpaicg_response_text);
speechData.append('voice', wpaicg_voice);
speechData.append('elevenlabs_model', elevenlabs_model);
speechData.append('action', 'wpaicg_text_to_speech');
wpaicg_ai_thinking.style.display = 'block';
var speechRequest = new XMLHttpRequest();
speechRequest.open("POST", wpaicgParams.ajax_url);
speechRequest.responseType = "arraybuffer";
speechRequest.onload = function () {
wpaicg_ai_thinking.style.display = 'none';
var blob = new Blob([speechRequest.response], { type: "audio/mpeg" });
var fr = new FileReader();
fr.onload = function () {
var fileText = this.result;
try {
var errorMessage = JSON.parse(fileText);
var errorMessageDetail = 'ElevenLabs: ' + errorMessage.detail.message;
if (parseInt(wpaicg_voice_error) !== 1) {
wpaicg_message += '<span style="width: 100%;display: block;font-size: 11px;">' + errorMessageDetail + '</span>';
} else if (typeof wpaicg_response !== 'undefined' && typeof wpaicg_response.log !== 'undefined' && wpaicg_response.log !== '') {
var speechLogMessage = new FormData();
speechLogMessage.append('nonce', wpaicg_nonce);
speechLogMessage.append('log_id', wpaicg_response.log);
speechLogMessage.append('message', errorMessageDetail);
speechLogMessage.append('action', 'wpaicg_speech_error_log');
var speechErrorRequest = new XMLHttpRequest();
speechErrorRequest.open("POST", wpaicgParams.ajax_url);
speechErrorRequest.send(speechLogMessage);
}
wpaicg_message += '</p></li>';
wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
} catch (errorBlob) {
var blobUrl = URL.createObjectURL(blob);
wpaicg_message += '<audio style="margin-top:6px;" controls="controls"><source type="audio/mpeg" src="' + blobUrl + '"></audio>';
wpaicg_message += '</p></li>';
wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
}
}
fr.readAsText(blob);
}
speechRequest.send(speechData);
}
}
else {
wpaicg_message += '</p></li>';
wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed);
}
}
}
}
}
}
// Function to hide all conversation starters
function hideConversationStarters() {
const starters = document.querySelectorAll('.wpaicg-conversation-starters');
starters.forEach(starter => {
starter.style.display = 'none';
});
}
function handleStreaming(wpaicgData, wpaicg_messages_box, wpaicg_box_typing, wpaicg_ai_thinking, class_ai_item, chat, chatbot_identity, clientID, wpaicg_nonce) {
// Remove the lead form if it exists
var leadFormMessage = chat.querySelector('.wpaicg-lead-form-message');
if (leadFormMessage) {
leadFormMessage.remove();
// Optionally, set 'wpaicg_lead_form_shown' to '1' so it doesn't show again
localStorage.setItem('wpaicg_lead_form_shown', '1');
}
const fontSize = chat.getAttribute('data-fontsize');
const aiBg = chat.getAttribute('data-ai-bg-color');
const fontColor = chat.getAttribute('data-color');
const usrBg = chat.getAttribute('data-user-bg-color');
const copyEnabled = chat.getAttribute('data-copy_btn') === "1";
const feedbackEnabled = chat.getAttribute('data-feedback_btn') === "1";
wpaicg_box_typing.value = '';
// add chatID to the query string
const chatId = `wpaicg-chat-message-${Math.floor(Math.random() * 100000) + 1}`;
const cleanedChatId = chatId.replace('wpaicg-chat-message-', ''); // Clean the chatId by removing the prefix
wpaicgData.append('chat_id', cleanedChatId);
const emptyClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-copy" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/>
</svg>`;
const checkedClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-check2" viewBox="0 0 16 16">
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0"/>
</svg>`;
const thumbsUpSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">
<path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2 2 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a10 10 0 0 0-.443.05 9.4 9.4 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a9 9 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.2 2.2 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.9.9 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>
</svg>`;
const thumbsDownSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-hand-thumbs-down" viewBox="0 0 16 16">
<path d="M8.864 15.674c-.956.24-1.843-.484-1.908-1.42-.072-1.05-.23-2.015-.428-2.59-.125-.36-.479-1.012-1.04-1.638-.557-.624-1.282-1.179-2.131-1.41C2.685 8.432 2 7.85 2 7V3c0-.845.682-1.464 1.448-1.546 1.07-.113 1.564-.415 2.068-.723l.048-.029c.272-.166.578-.349.97-.484C6.931.08 7.395 0 8 0h3.5c.937 0 1.599.478 1.934 1.064.164.287.254.607.254.913 0 .152-.023.312-.077.464.201.262.38.577.488.9.11.33.172.762.004 1.15.069.13.12.268.159.403.077.27.113.567.113.856s-.036.586-.113.856c-.035.12-.08.244-.138.363.394.571.418 1.2.234 1.733-.206.592-.682 1.1-1.2 1.272-.847.283-1.803.276-2.516.211a10 10 0 0 1-.443-.05 9.36 9.36 0 0 1-.062 4.51c-.138.508-.55.848-1.012.964zM11.5 1H8c-.51 0-.863.068-1.14.163-.281.097-.506.229-.776.393l-.04.025c-.555.338-1.198.73-2.49.868-.333.035-.554.29-.554.55V7c0 .255.226.543.62.65 1.095.3 1.977.997 2.614 1.709.635.71 1.064 1.475 1.238 1.977.243.7.407 1.768.482 2.85.025.362.36.595.667.518l.262-.065c.16-.04.258-.144.288-.255a8.34 8.34 0 0 0-.145-4.726.5.5 0 0 1 .595-.643h.003l.014.004.058.013a9 9 0 0 0 1.036.157c.663.06 1.457.054 2.11-.163.175-.059.45-.301.57-.651.107-.308.087-.67-.266-1.021L12.793 7l.353-.354c.043-.042.105-.14.154-.315.048-.167.075-.37.075-.581s-.027-.414-.075-.581c-.05-.174-.111-.273-.154-.315l-.353-.354.353-.354c.047-.047.109-.176.005-.488a2.2 2.2 0 0 0-.505-.804l-.353-.354.353-.354c.006-.005.041-.05.041-.17a.9.9 0 0 0-.121-.415C12.4 1.272 12.063 1 11.5 1"/>
</svg>`;
const messageHtml = `
<li class="${class_ai_item} wpaicg-icon-container" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}">
<p style="width:100%">
<span class="wpaicg-chat-message" id="${chatId}"></span>
${copyEnabled ? `<button class="wpaicg-copy-button" data-chat-id="${chatId}">${emptyClipboardSVG}</button>` : ''}
${feedbackEnabled ? `<button class="wpaicg-thumbs-up-button" data-chat-id="${chatId}">${thumbsUpSVG}</button>
<button class="wpaicg-thumbs-down-button" data-chat-id="${chatId}">${thumbsDownSVG}</button>` : ''}
</p>
</li>
`;
// Buffer to accumulate chunks
let buffer = '';
let completeAIResponse = '';
let dataQueue = [];
let isProcessing = false;
function processBuffer() {
processMarkdown(buffer, true, chatId);
}
function typeWriter(text, i, elementId, callback) {
toggleBlinkingCursor(false);
if (i < text.length) {
const charToAdd = text.charAt(i);
if (charToAdd === '<') {
const tag = text.slice(i, i + 4);
if (tag === '<br>') {
jQuery(`#${elementId}`).append(tag);
i += 4;
} else {
jQuery(`#${elementId}`).append(charToAdd);
i++;
}
} else {
jQuery(`#${elementId}`).append(charToAdd);
i++;
}
setTimeout(() => typeWriter(text, i, elementId, callback), 1);
} else if (callback) {
callback();
scrollToBottom();
}
}
function scrollToBottom() {
wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
}
function processQueue() {
if (dataQueue.length && !isProcessing) {
isProcessing = true;
const nextChunk = dataQueue.shift();
typeWriter(nextChunk, 0, chatId, () => {
isProcessing = false;
processQueue();
});
} else {
toggleBlinkingCursor(false);
}
}
function toggleBlinkingCursor(isVisible) {
const cursorElement = jQuery(`#${chatId} .blinking-cursor`);
if (isVisible) {
if (!cursorElement.length) {
jQuery(`#${chatId}`).append('<span class="blinking-cursor">|</span>');
}
} else {
cursorElement.remove();
}
}
// Fetch POST request for streaming
fetch(wpaicgParams.ajax_url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(wpaicgData).toString(),
})
.then(response => response.body)
.then(async (reader) => {
const decoder = new TextDecoder();
const stream = reader.getReader();
toggleBlinkingCursor(true);
wpaicg_ai_thinking.style.display = 'none';
wpaicg_messages_box.innerHTML += messageHtml;
let partial = '';
while (true) {
const { done, value } = await stream.read();
if (done) {
toggleBlinkingCursor(false);
wpaicg_ai_thinking.style.display = 'none';
const simpleChatId = chatId.replace('wpaicg-chat-message-', '');
updateChatHistory(completeAIResponse, 'ai', simpleChatId, chat, chatbot_identity, clientID);
break;
}
partial += decoder.decode(value); // Append chunk
// Split the partial data by lines
const lines = partial.split('\n');
for (let i = 0; i < lines.length - 1; i++) {
let line = lines[i];
if (line.trim() === '' || !line.startsWith('data: ')) {
continue;
}
// Remove 'data: ' prefix
line = line.slice(6);
// Handle [DONE] signal
if (line === "[DONE]") {
toggleBlinkingCursor(false);
wpaicg_ai_thinking.style.display = 'none';
scrollToBottom();
const simpleChatId = chatId.replace('wpaicg-chat-message-', '');
updateChatHistory(completeAIResponse, 'ai', simpleChatId, chat, chatbot_identity, clientID);
if (!localStorage.getItem('wpaicg_lead_form_shown')) {
maybeShowLeadForm(chat, chatId);
// scroll to the bottom of the chatbox
scrollToBottom();
}
return;
}
// Try to parse the remaining JSON data
try {
const resultData = JSON.parse(line);
if (resultData.tokenLimitReached || resultData.messageFlagged || resultData.pineconeError || resultData.ipBanned || resultData.modflag) {
document.getElementById(chatId).innerHTML = `<span class="wpaicg-chat-message">${resultData.msg}</span>`;
wpaicg_ai_thinking.style.display = 'none';
toggleBlinkingCursor(false);
scrollToBottom();
return;
}
if (resultData.error) {
dataQueue.push(resultData.error.message);
} else {
const content = resultData.choices?.[0]?.delta?.content || resultData.choices?.[0]?.text || '';
buffer += content;
processBuffer();
completeAIResponse += content;
}
processQueue();
scrollToBottom();
} catch (err) {
console.error('Error parsing JSON:', err, line);
}
}
partial = lines[lines.length - 1]; // Keep last partial line
}
// Process any remaining partial data after the loop ends
if (partial.trim() !== '') {
const lines = partial.split('\n');
for (let line of lines) {
if (line.trim() === '' || !line.startsWith('data: ')) {
continue;
}
// Remove 'data: ' prefix
line = line.slice(6);
// Handle [DONE] signal
if (line === "[DONE]") {
toggleBlinkingCursor(false);
return;
}
// Try to parse the remaining JSON data
try {
const resultData = JSON.parse(line);
if (resultData.tokenLimitReached || resultData.messageFlagged || resultData.pineconeError || resultData.ipBanned || resultData.modflag) {
document.getElementById(chatId).innerHTML = `<span class="wpaicg-chat-message">${resultData.msg}</span>`;
wpaicg_ai_thinking.style.display = 'none';
toggleBlinkingCursor(false);
scrollToBottom();
return;
}
if (resultData.error) {
dataQueue.push(resultData.error.message);
} else {
const content = resultData.choices?.[0]?.delta?.content || resultData.choices?.[0]?.text || '';
buffer += content;
processBuffer();
completeAIResponse += content;
}
processQueue();
scrollToBottom();
} catch (err) {
console.error('Error parsing JSON after stream end:', err, line);
}
}
}
})
.catch(error => {
console.log("Fetch failed:", error);
toggleBlinkingCursor(false);
wpaicg_ai_thinking.style.display = 'none';
});
// Setup button listeners for the copy and feedback buttons
setupButtonListeners(copyEnabled, feedbackEnabled, class_ai_item, emptyClipboardSVG, checkedClipboardSVG, thumbsUpSVG, thumbsDownSVG, showFeedbackModal, aiBg, fontColor, usrBg, chat, wpaicg_nonce, chatbot_identity);
}
function handleAssistantStreaming(
wpaicgData,
wpaicg_messages_box,
wpaicg_box_typing,
wpaicg_ai_thinking,
class_ai_item,
chat,
chatbot_identity,
clientID,
wpaicg_nonce
) {
// Remove the lead form if it exists
const leadFormMessage = chat.querySelector('.wpaicg-lead-form-message');
if (leadFormMessage) {
leadFormMessage.remove();
// Optionally, set 'wpaicg_lead_form_shown' to '1' so it doesn't show again
localStorage.setItem('wpaicg_lead_form_shown', '1');
}
// Retrieve UI configurations
const fontSize = chat.getAttribute('data-fontsize');
const aiBg = chat.getAttribute('data-ai-bg-color');
const fontColor = chat.getAttribute('data-color');
const usrBg = chat.getAttribute('data-user-bg-color');
const copyEnabled = chat.getAttribute('data-copy_btn') === "1";
const feedbackEnabled = chat.getAttribute('data-feedback_btn') === "1";
// Clear the typing box
wpaicg_box_typing.value = '';
// Generate a unique chat ID
const chatId = `wpaicg-chat-message-${Math.floor(Math.random() * 100000) + 1}`;
const cleanedChatId = chatId.replace('wpaicg-chat-message-', ''); // Clean the chatId by removing the prefix
wpaicgData.append('chat_id', cleanedChatId);
const emptyClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-copy" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/>
</svg>`;
const checkedClipboardSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-check2" viewBox="0 0 16 16">
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0"/>
</svg>`;
const thumbsUpSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">
<path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2 2 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a10 10 0 0 0-.443.05 9.4 9.4 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a9 9 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.2 2.2 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.9.9 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>
</svg>`;
const thumbsDownSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${fontColor}" class="bi bi-hand-thumbs-down" viewBox="0 0 16 16">
<path d="M8.864 15.674c-.956.24-1.843-.484-1.908-1.42-.072-1.05-.23-2.015-.428-2.59-.125-.36-.479-1.012-1.04-1.638-.557-.624-1.282-1.179-2.131-1.41C2.685 8.432 2 7.85 2 7V3c0-.845.682-1.464 1.448-1.546 1.07-.113 1.564-.415 2.068-.723l.048-.029c.272-.166.578-.349.97-.484C6.931.08 7.395 0 8 0h3.5c.937 0 1.599.478 1.934 1.064.164.287.254.607.254.913 0 .152-.023.312-.077.464.201.262.38.577.488.9.11.33.172.762.004 1.15.069.13.12.268.159.403.077.27.113.567.113.856s-.036.586-.113.856c-.035.12-.08.244-.138.363.394.571.418 1.2.234 1.733-.206.592-.682 1.1-1.2 1.272-.847.283-1.803.276-2.516.211a10 10 0 0 1-.443-.05 9.36 9.36 0 0 1-.062 4.51c-.138.508-.55.848-1.012.964zM11.5 1H8c-.51 0-.863.068-1.14.163-.281.097-.506.229-.776.393l-.04.025c-.555.338-1.198.73-2.49.868-.333.035-.554.29-.554.55V7c0 .255.226.543.62.65 1.095.3 1.977.997 2.614 1.709.635.71 1.064 1.475 1.238 1.977.243.7.407 1.768.482 2.85.025.362.36.595.667.518l.262-.065c.16-.04.258-.144.288-.255a8.34 8.34 0 0 0-.145-4.726.5.5 0 0 1 .595-.643h.003l.014.004.058.013a9 9 0 0 0 1.036.157c.663.06 1.457.054 2.11-.163.175-.059.45-.301.57-.651.107-.308.087-.67-.266-1.021L12.793 7l.353-.354c.043-.042.105-.14.154-.315.048-.167.075-.37.075-.581s-.027-.414-.075-.581c-.05-.174-.111-.273-.154-.315l-.353-.354.353-.354c.047-.047.109-.176.005-.488a2.2 2.2 0 0 0-.505-.804l-.353-.354.353-.354c.006-.005.041-.05.041-.17a.9.9 0 0 0-.121-.415C12.4 1.272 12.063 1 11.5 1"/>
</svg>`;
const messageHtml = `
<li class="${class_ai_item} wpaicg-icon-container" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}">
<span class="wpaicg-chat-message" id="${chatId}"></span>
${copyEnabled ? `<button class="wpaicg-copy-button" data-chat-id="${chatId}">${emptyClipboardSVG}</button>` : ''}
${feedbackEnabled ? `<button class="wpaicg-thumbs-up-button" data-chat-id="${chatId}">${thumbsUpSVG}</button>
<button class="wpaicg-thumbs-down-button" data-chat-id="${chatId}">${thumbsDownSVG}</button>` : ''}
</li>
`;
let completeAIResponse = '';
let aiMessageAdded = false;
let accumulatedBuffer = ''; // Buffer to accumulate chunks
let doneCalled = false;
// To store the thread ID logic (multiple threads)
const botId = chat.getAttribute('data-bot-id') || '0';
const activeThreadKey = `wpaicg_current_thread_${botId}_${clientID}`;
const existingThreadKey = localStorage.getItem(activeThreadKey);
// If we appended a real thread_id in wpaicgData, handle that in the server streaming
function scrollToBottom() {
wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
}
function toggleBlinkingCursor(isVisible) {
const cursorElement = jQuery(`#${chatId} .blinking-cursor`);
if (isVisible) {
if (!cursorElement.length) {
jQuery(`#${chatId}`).append('<span class="blinking-cursor">|</span>');
}
} else {
cursorElement.remove();
}
}
// Fetch POST request for Assistant API streaming
fetch(wpaicgParams.ajax_url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(wpaicgData).toString(),
})
.then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let partial = '';
let currentEvent = null;
function read() {
return reader.read().then(({ done, value }) => {
if (done) {
handleDoneEvent();
return;
}
partial += decoder.decode(value, { stream: true });
// attempt to parse the partial response as JSON
try {
const json = JSON.parse(partial);
if (json.status === 'error' && json.msg) {
handleErrorEvent(json.msg); // use the existing error handler
return; // stop further processing
}
} catch (e) {
// ignore JSON parse errors here, continue streaming
}
const lines = partial.split('\n');
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i].trim();
if (line === '') continue;
if (line.startsWith('event: ')) {
currentEvent = line.slice(7).trim();
} else if (line.startsWith('data: ')) {
const data = line.slice(6).trim();
handleAssistantStreamEvent(currentEvent, data);
}
}
partial = lines[lines.length - 1];
return read();
});
}
return read();
})
.catch(error => {
console.log("Fetch failed:", error);
toggleBlinkingCursor(false);
wpaicg_ai_thinking.style.display = 'none';
});
// Function to handle Assistant API streaming events
function handleAssistantStreamEvent(event, data) {
switch (event) {
case 'thread_id':
handleThreadIdEvent(data);
break;
case 'thread.message.delta':
handleMessageDeltaEvent(data);
break;
case 'thread.message.completed':
handleMessageCompletedEvent(data);
break;
case 'assistant_error':
handleErrorEvent(data);
break;
case 'done':
handleDoneEvent();
break;
case null:
handleErrorEvent(data);
break;
}
}
function handleMessageDeltaEvent(data) {
const deltaData = JSON.parse(data);
if (deltaData && deltaData.delta && deltaData.delta.content) {
const contentArray = deltaData.delta.content;
for (const contentItem of contentArray) {
if (contentItem.type === 'text' && contentItem.text && contentItem.text.value) {
const messageChunk = contentItem.text.value;
if (!aiMessageAdded) {
wpaicg_messages_box.innerHTML += messageHtml;
aiMessageAdded = true;
wpaicg_ai_thinking.style.display = 'none';
}
// Accumulate chunks
accumulatedBuffer += messageChunk;
completeAIResponse += messageChunk;
// Convert Markdown to HTML
const htmlContent = marked.parse(accumulatedBuffer);
// Update the chat message content
document.getElementById(chatId).innerHTML = htmlContent;
scrollToBottom();
}
}
}
}
function handleMessageCompletedEvent(data) {
toggleBlinkingCursor(false);
wpaicg_ai_thinking.style.display = 'none';
scrollToBottom();
}
function handleThreadIdEvent(threadId) {
// store thread id for the active thread localStorage
if (existingThreadKey) {
let threadListObj = JSON.parse(localStorage.getItem('wpaicg_thread_list')) || {};
threadListObj[existingThreadKey] = threadId;
localStorage.setItem('wpaicg_thread_list', JSON.stringify(threadListObj));
}
}
// Handler for 'assistant_error' events
function handleErrorEvent(errorData) {
console.log('Assistant Error:', errorData);
let errorMessage = 'An unknown error occurred.';
try {
// check if errorData is a string and try to parse it as JSON
if (typeof errorData === 'string') {
try {
const parsedError = JSON.parse(errorData);
errorMessage = parsedError.msg || errorMessage; // use parsed message if available
} catch (jsonParseError) {
// if parsing fails, fall back to using the raw string
errorMessage = errorData;
}
} else if (errorData && errorData.msg) {
errorMessage = errorData.msg; // directly use the msg field if it's an object
}
} catch (e) {
console.error('Error handling error data:', e); // log any unexpected issues
}
// parse the error message with marked for markdown or URL formatting
const parsedHtml = marked.parse(errorMessage);
const chatElement = document.getElementById(chatId);
if (chatElement) {
chatElement.innerHTML = `<span class="wpaicg-chat-message">${parsedHtml}</span>`;
} else {
// fallback if chatId doesn't exist yet
wpaicg_messages_box.innerHTML += `
<li class="${class_ai_item}" style="background-color:${aiBg};font-size:${fontSize}px;color:${fontColor}">
<span class="wpaicg-chat-message">${parsedHtml}</span>
</li>`;
}
wpaicg_ai_thinking.style.display = 'none';
toggleBlinkingCursor(false);
scrollToBottom();
}
// Handler for 'done' events
function handleDoneEvent() {
if (doneCalled) return;
doneCalled = true;
toggleBlinkingCursor(false);
wpaicg_ai_thinking.style.display = 'none';
if (!localStorage.getItem('wpaicg_lead_form_shown')) {
maybeShowLeadForm(chat, chatId);
scrollToBottom();
}
// update chat history
const simpleChatId = chatId.replace('wpaicg-chat-message-', '');
updateChatHistory(completeAIResponse, 'ai', simpleChatId, chat, chatbot_identity, clientID);
}
// Setup button listeners for the copy and feedback buttons
setupButtonListeners(
copyEnabled,
feedbackEnabled,
class_ai_item,
emptyClipboardSVG,
checkedClipboardSVG,
thumbsUpSVG,
thumbsDownSVG,
showFeedbackModal,
aiBg,
fontColor,
usrBg,
chat,
wpaicg_nonce,
chatbot_identity
);
}
function processMarkdown(inputText, isStream = false, chatId = null) {
inputText = inputText !== '' ? inputText.trim() : '';
// parse the markdown using the marked library
const formattedText = marked.parse(inputText);
// if in stream mode and chatId exists, update the DOM
if (isStream && chatId) {
const element = document.getElementById(chatId);
if (element) {
element.innerHTML = formattedText;
}
}
return formattedText;
}
// Scroll function to adjust.
function scrollToAdjust(wpaicg_messages_box) {
requestAnimationFrame(() => {
wpaicg_messages_box.scrollTop = wpaicg_messages_box.scrollHeight;
});
}
function wpaicgWriteMessage(wpaicg_messages_box, wpaicg_message, wpaicg_randomnum, wpaicg_response_text, wpaicg_typewriter_effect, wpaicg_typewriter_speed) {
var chatContainerforLead = wpaicg_messages_box.closest('.wpaicg-chat-shortcode') || wpaicg_messages_box.closest('.wpaicg-chatbox');
wpaicg_messages_box.insertAdjacentHTML('beforeend', wpaicg_message);
var wpaicg_current_message = document.getElementById('wpaicg-chat-message-' + wpaicg_randomnum);
// Ensure the current message is found
if (wpaicg_current_message) {
// Get the next sibling element, which should be the audio element
var nextElement = wpaicg_current_message.closest('li').nextElementSibling;
// Check if the next element is an audio tag and play it if found
if (nextElement && nextElement.tagName.toLowerCase() === 'audio') {
nextElement.play();
} else {
console.log('No audio found next to the current message.');
}
} else {
console.log('Current message not found.');
}
// Apply formatting to the entire response text first
var formattedText = marked.parse(wpaicg_response_text);
if (wpaicg_typewriter_effect) {
let index = 0; // Starting index of the substring
function typeWriter() {
if (index < formattedText.length) {
wpaicg_current_message.innerHTML = formattedText.slice(0, index + 1);
index++;
setTimeout(typeWriter, wpaicg_typewriter_speed);
//scroll to the latest message if needed
scrollToAdjust(wpaicg_messages_box);
} else {
// Once complete, ensure scrolling if needed
scrollToAdjust(wpaicg_messages_box);
}
}
typeWriter(); // Start the typewriter effect
} else {
wpaicg_current_message.innerHTML = formattedText;
// Scroll to the latest message if needed
scrollToAdjust(wpaicg_messages_box);
}
if (!localStorage.getItem('wpaicg_lead_form_shown')) {
maybeShowLeadForm(chatContainerforLead, 'wpaicg-chat-message-' + wpaicg_randomnum);
// scroll to the bottom of the chatbox
scrollToAdjust(wpaicg_messages_box);
}
}
function wpaicgMicEvent(mic) {
if (mic.classList.contains('wpaicg-recording')) {
mic.innerHTML = '';
mic.innerHTML = wpaicgMicIcon;
mic.classList.remove('wpaicg-recording');
wpaicgstopChatRecording(mic)
} else {
let checkRecording = document.querySelectorAll('.wpaicg-recording');
if (checkRecording && checkRecording.length) {
alert('Please finish previous recording');
} else {
mic.innerHTML = '';
mic.innerHTML = wpaicgStopIcon;
mic.classList.add('wpaicg-recording');
wpaicgstartChatRecording();
}
}
}
if (wpaicgChatTyping && wpaicgChatTyping.length) {
for (let i = 0; i < wpaicgChatTyping.length; i++) {
wpaicgChatTyping[i].addEventListener('keyup', function (event) {
if ((event.which === 13 || event.keyCode === 13) && !event.shiftKey) {
let parentChat = wpaicgChatTyping[i].closest('.wpaicg-chatbox');
let chatTyping = parentChat.querySelectorAll('.wpaicg-chatbox-typing')[0];
wpaicgSendChatMessage(parentChat, chatTyping, 'widget');
}
})
}
}
if (wpaicgShortcodeTyping && wpaicgShortcodeTyping.length) {
for (let i = 0; i < wpaicgShortcodeTyping.length; i++) {
wpaicgShortcodeTyping[i].addEventListener('keyup', function (event) {
if ((event.which === 13 || event.keyCode === 13) && !event.shiftKey) {
let parentChat = wpaicgShortcodeTyping[i].closest('.wpaicg-chat-shortcode');
let chatTyping = parentChat.querySelectorAll('.wpaicg-chat-shortcode-typing')[0];
wpaicgSendChatMessage(parentChat, chatTyping, 'shortcode');
}
})
}
}
if (wpaicgChatSend && wpaicgChatSend.length) {
for (let i = 0; i < wpaicgChatSend.length; i++) {
wpaicgChatSend[i].addEventListener('click', function (event) {
let parentChat = wpaicgChatSend[i].closest('.wpaicg-chatbox');
let chatTyping = parentChat.querySelectorAll('.wpaicg-chatbox-typing')[0];
wpaicgSendChatMessage(parentChat, chatTyping, 'widget');
})
}
}
if (wpaicgShortcodeSend && wpaicgShortcodeSend.length) {
for (let i = 0; i < wpaicgShortcodeSend.length; i++) {
wpaicgShortcodeSend[i].addEventListener('click', function (event) {
let parentChat = wpaicgShortcodeSend[i].closest('.wpaicg-chat-shortcode');
let chatTyping = parentChat.querySelectorAll('.wpaicg-chat-shortcode-typing')[0];
wpaicgSendChatMessage(parentChat, chatTyping, 'shortcode');
})
}
}
if (wpaicgMicBtns && wpaicgMicBtns.length) {
for (let i = 0; i < wpaicgMicBtns.length; i++) {
wpaicgMicBtns[i].addEventListener('click', function () {
wpaicgMicEvent(wpaicgMicBtns[i]);
});
}
}
}
// Initialize Sidebar Toggle
function initSidebarToggle() {
const toggleButtons = document.querySelectorAll('.wpaicg-sidebar-toggle');
const savedSidebarState = localStorage.getItem('wpaicg_sidebar_state') || 'closed';
// Set initial state for all sidebars based on the saved preference.
document.querySelectorAll('.wpaicg-sidebar').forEach(sidebar => {
if (savedSidebarState === 'open') {
sidebar.classList.add('open');
} else {
sidebar.classList.remove('open');
}
});
toggleButtons.forEach(toggle => {
toggle.addEventListener('click', function () {
const chatContainer = this.closest('.wpaicg-chat-shortcode, .wpaicg-chatbox');
const sidebar = chatContainer.querySelector('.wpaicg-sidebar');
sidebar.classList.toggle('open');
const newState = sidebar.classList.contains('open') ? 'open' : 'closed';
localStorage.setItem('wpaicg_sidebar_state', newState);
});
// Allow toggle via keyboard (Enter key)
toggle.addEventListener('keypress', function (e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.click();
}
});
});
}
/**
* Builds the list of all conversations in the sidebar for each chat container,
* sorted by their "last updated" timestamp in descending order.
* We look for the first user message (prefixed "Human:") to name the conversation.
*/
function loadConversationList() {
const containers = ['.wpaicg-chat-shortcode', '.wpaicg-chatbox'];
containers.forEach(containerSelector => {
const chatContainers = document.querySelectorAll(containerSelector);
chatContainers.forEach(chatContainer => {
// Find the sidebar within this container
const sidebar = chatContainer.querySelector('.wpaicg-sidebar');
if (!sidebar) return;
// Conversation list UL inside the sidebar
const conversationList = sidebar.querySelector('.wpaicg-conversation-list');
if (!conversationList) return;
// Clear out any old items
conversationList.innerHTML = '';
// Identify which bot
const botId = chatContainer.getAttribute('data-bot-id') || '0';
const chatType = chatContainer.getAttribute('data-type') || 'shortcode';
// Need the clientID from localStorage
let clientID = localStorage.getItem('wpaicg_chat_client_id');
if (!clientID) return;
// Base prefix for conversation keys
let conversationKeyBase = '';
if (botId !== '0') {
// custom bot
conversationKeyBase = `wpaicg_chat_history_custom_bot_${botId}_${clientID}`;
} else {
// default shortcodes or widgets
conversationKeyBase = `wpaicg_chat_history_${chatType}_${clientID}`;
}
// We'll collect all conversations, then sort
let conversationData = [];
// Loop through localStorage to find all conversation keys
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
// Must match our base prefix plus the underscore for the index
if (key.startsWith(conversationKeyBase + '_')) {
// Ensure we're looking specifically at the conversation array, not the _timestamp
if (key.endsWith('_timestamp')) continue;
const rawData = localStorage.getItem(key);
if (!rawData) continue;
let chatHistory;
try {
chatHistory = JSON.parse(rawData);
} catch (e) {
chatHistory = [];
}
// Default name
let title = "Untitled Conversation";
// We look specifically for the FIRST user message ("Human:")
if (Array.isArray(chatHistory) && chatHistory.length > 0) {
for (let j = 0; j < chatHistory.length; j++) {
let msg = chatHistory[j].text;
if (typeof msg === 'string' && msg.startsWith('Human:')) {
// Remove "Human:"
const userMsg = msg.replace(/^Human:\s*/, '').trim();
title = userMsg.substring(0, 20) + (userMsg.length > 20 ? '…' : '');
break; // we found our user message
}
}
}
// Check if there's a timestamp
const timestampKey = key + '_timestamp';
const rawTimestamp = localStorage.getItem(timestampKey);
const lastUpdated = rawTimestamp ? parseInt(rawTimestamp, 10) : 0;
// Collect
conversationData.push({ key, lastUpdated, title });
}
}
// Sort conversationData by lastUpdated descending
conversationData.sort((a, b) => b.lastUpdated - a.lastUpdated);
// Now build <li> for each
conversationData.forEach(item => {
const li = document.createElement('li');
li.textContent = item.title;
li.dataset.conversationKey = item.key;
// --- NEW CODE: Add a "trash" icon on hover for deletion ---
const trashIcon = document.createElement('span');
// Dashicons classes for a trash icon + our custom class
trashIcon.classList.add('dashicons', 'dashicons-trash', 'wpaicg-delete-icon');
// On click, remove from local storage & UI
trashIcon.addEventListener('click', e => {
e.stopPropagation(); // Prevent loading the conversation
localStorage.removeItem(item.key);
localStorage.removeItem(item.key + '_timestamp');
li.remove();
});
li.appendChild(trashIcon);
// Clicking the li itself loads the conversation
li.addEventListener('click', function () {
loadSelectedConversation(item.key, chatContainer);
});
conversationList.appendChild(li);
});
});
});
}
/**
* Loads the selected conversation key from localStorage
* into the chat UI, and sets it as active for subsequent messages.
* Also handles assistant thread alignment if assistant is enabled.
*
* @param {string} conversationKey - The exact localStorage key for this conversation.
* @param {HTMLElement} chatContainer - The .wpaicg-chat-shortcode or .wpaicg-chatbox container.
*/
function loadSelectedConversation(conversationKey, chatContainer) {
// Clear the display
var chatBox = chatContainer.querySelector('.wpaicg-chatbox-messages, .wpaicg-chat-shortcode-messages');
if (!chatBox) return;
chatBox.innerHTML = '';
// Retrieve the conversation
var rawHistory = localStorage.getItem(conversationKey);
if (!rawHistory) return;
var chatHistory = JSON.parse(rawHistory);
if (!Array.isArray(chatHistory)) chatHistory = [];
// Reconstruct the conversation in the UI
chatHistory.forEach(message => {
reconstructMessage(chatBox, message, chatContainer);
});
// Scroll to bottom
chatBox.scrollTop = chatBox.scrollHeight;
// Mark this conversationKey as the current "active" conversation
const botId = chatContainer.getAttribute('data-bot-id') || '0';
const clientID = localStorage.getItem('wpaicg_chat_client_id') || '';
const activeConversationKey = `wpaicg_current_conversation_${botId}_${clientID}`;
localStorage.setItem(activeConversationKey, conversationKey);
// If assistant is enabled, we want to align the active thread similarly
let assistantEnabled = chatContainer.getAttribute('data-assistant-enabled') === 'true';
if (assistantEnabled) {
// Example conversationKey:
// wpaicg_chat_history_custom_bot_4377_dc8XxpH90T_2
// we want to map the portion after "custom_bot_4377_dc8XxpH90T_" => "2"
// so the thread key is "custom_bot_4377_dc8XxpH90T_2"
// or if it's the default short/widget fallback
let suffixParts = conversationKey.split('_');
if (suffixParts.length >= 2) {
// For custom bot conversation keys => wpaicg_chat_history_custom_bot_${botId}_${clientID}_${index}
// suffixParts might be: [ 'wpaicg','chat','history','custom','bot','4377','dc8XxpH90T','2' ]
// We'll find the last item => index
let lastIndex = suffixParts[suffixParts.length - 1];
// Rebuild the base to "custom_bot_{botId}_{clientID}_{lastIndex}" or for short/widget => "shortcode_{clientID}_{lastIndex}"
// Actually we can slice from the third piece onward
// e.g. conversationKeyBase = "custom_bot_4377_dc8XxpH90T_2"
let conversationKeyBase = suffixParts.slice(3).join('_');
// e.g. "custom_bot_4377_dc8XxpH90T_2"
// Create the "activeThreadKey"
let activeThreadKeyName = `wpaicg_current_thread_${botId}_${clientID}`;
localStorage.setItem(activeThreadKeyName, conversationKeyBase);
}
}
}
// Call the init function when the document is ready
document.addEventListener('DOMContentLoaded', function () {
wpaicgChatInit();
loadConversations();
initSidebarToggle();
loadConversationList();
initNewChatButtons();
});