PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.4.0
MxChat – AI Chatbot & Content Generation for WordPress v2.4.0
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | js/chat-script.js +2312 -1078 2.0.62.4.0 View file →
@@ -1,482 +1,965 @@
1 1 jQuery(document).ready(function($) {
2 -//console.log('mxchatChat object:', mxchatChat);
3 -//console.log('Link Target Toggle Value:', mxchatChat.link_target_toggle);
4 -// Add these variables at the top of your chat-script.js file
2 +
3 + // ====================================
4 + // GLOBAL VARIABLES & CONFIGURATION
5 + // ====================================
5 6 const toolbarIconColor = mxchatChat.toolbar_icon_color || '#212121';
6 -
7 +
7 8 // Initialize color settings
8 9 var userMessageBgColor = mxchatChat.user_message_bg_color;
9 10 var userMessageFontColor = mxchatChat.user_message_font_color;
10 11 var botMessageBgColor = mxchatChat.bot_message_bg_color;
11 12 var botMessageFontColor = mxchatChat.bot_message_font_color;
12 - // Add live agent message colors
13 13 var liveAgentMessageBgColor = mxchatChat.live_agent_message_bg_color;
14 14 var liveAgentMessageFontColor = mxchatChat.live_agent_message_font_color;
15 -
16 -
15 +
17 16 var linkTarget = mxchatChat.link_target_toggle === 'on' ? '_blank' : '_self';
18 17 let lastSeenMessageId = '';
19 18 let notificationCheckInterval;
20 19 let notificationBadge;
21 - // Initialize session ID
22 20 var sessionId = getChatSession();
23 -
24 - let pollingInterval; // Variable to store the interval ID
25 - let processedMessageIds = new Set(); // Add this at the top with your other variables
26 -//console.log('Live Agent BG Color:', liveAgentMessageBgColor);
27 -//console.log('Live Agent Font Color:', liveAgentMessageFontColor);
21 + let pollingInterval;
22 + let processedMessageIds = new Set();
28 23 let activePdfFile = null;
29 24 let activeWordFile = null;
30 25
31 26
32 -// Function to create and append notification badge
33 -// Function to create and append notification badge
34 -function createNotificationBadge() {
35 - console.log("Creating notification badge...");
36 - const chatButton = document.getElementById('floating-chatbot-button');
37 - console.log("Chat button found:", !!chatButton);
27 + // ====================================
28 + // SESSION MANAGEMENT
29 + // ====================================
38 30
39 - if (!chatButton) return;
31 + function getChatSession() {
32 + var sessionId = getCookie('mxchat_session_id');
33 + //console.log("Session ID retrieved from cookie: ", sessionId);
34 +
35 + if (!sessionId) {
36 + sessionId = generateSessionId();
37 + //console.log("Generated new session ID: ", sessionId);
38 + setChatSession(sessionId);
39 + }
40 +
41 + //console.log("Final session ID: ", sessionId);
42 + return sessionId;
43 + }
44 +
45 + function setChatSession(sessionId) {
46 + // Set the cookie with a 24-hour expiration (86400 seconds)
47 + document.cookie = "mxchat_session_id=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
48 + }
49 +
50 + function getCookie(name) {
51 + let value = "; " + document.cookie;
52 + let parts = value.split("; " + name + "=");
53 + if (parts.length == 2) return parts.pop().split(";").shift();
54 + }
55 +
56 + function generateSessionId() {
57 + return 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
58 + }
40 59
41 - // Remove any existing badge first
42 - const existingBadge = chatButton.querySelector('.chat-notification-badge');
43 - if (existingBadge) {
44 - console.log("Removing existing badge");
45 - existingBadge.remove();
60 +// ====================================
61 +// CONTEXTUAL AWARENESS FUNCTIONALITY
62 +// ====================================
63 +
64 +function getPageContext() {
65 + // Check if contextual awareness is enabled
66 + if (mxchatChat.contextual_awareness_toggle !== 'on') {
67 + return null;
46 68 }
47 -
48 - notificationBadge = document.createElement('div');
49 - notificationBadge.className = 'chat-notification-badge';
50 - notificationBadge.style.cssText = `
51 - display: none;
52 - position: absolute;
53 - top: -5px;
54 - right: -5px;
55 - background-color: red;
56 - color: white;
57 - border-radius: 50%;
58 - padding: 4px 8px;
59 - font-size: 12px;
60 - font-weight: bold;
61 - z-index: 10001;
62 - `;
63 - chatButton.style.position = 'relative';
64 - chatButton.appendChild(notificationBadge);
65 69
66 - console.log("Notification badge created and appended:", {
67 - exists: !!notificationBadge,
68 - parent: notificationBadge?.parentNode?.id,
69 - display: notificationBadge?.style?.display
70 - });
70 + // Get page URL
71 + const pageUrl = window.location.href;
72 +
73 + // Get page title
74 + const pageTitle = document.title || '';
75 +
76 + // Get main content from the page
77 + let pageContent = '';
78 +
79 + // Try to get content from common content areas
80 + const contentSelectors = [
81 + 'main',
82 + '[role="main"]',
83 + '.content',
84 + '.main-content',
85 + '.post-content',
86 + '.entry-content',
87 + '.page-content',
88 + 'article',
89 + '#content',
90 + '#main'
91 + ];
92 +
93 + let contentElement = null;
94 + for (const selector of contentSelectors) {
95 + contentElement = document.querySelector(selector);
96 + if (contentElement) {
97 + break;
98 + }
99 + }
100 +
101 + // If no specific content area found, use body but exclude header, footer, nav, sidebar
102 + if (!contentElement) {
103 + contentElement = document.body;
104 + }
105 +
106 + if (contentElement) {
107 + // Clone the element to avoid modifying the original
108 + const clone = contentElement.cloneNode(true);
109 +
110 + // Remove unwanted elements
111 + const unwantedSelectors = [
112 + 'header',
113 + 'footer',
114 + 'nav',
115 + '.navigation',
116 + '.sidebar',
117 + '.widget',
118 + '.menu',
119 + 'script',
120 + 'style',
121 + '.comments',
122 + '#comments',
123 + '.breadcrumb',
124 + '.breadcrumbs',
125 + '#floating-chatbot',
126 + '#floating-chatbot-button',
127 + '.mxchat',
128 + '[class*="chat"]',
129 + '[id*="chat"]'
130 + ];
131 +
132 + unwantedSelectors.forEach(selector => {
133 + const elements = clone.querySelectorAll(selector);
134 + elements.forEach(el => el.remove());
135 + });
136 +
137 + // NEW: Extract MxChat context data attributes before getting text content
138 + const contextData = [];
139 + clone.querySelectorAll('[data-mxchat-context]').forEach(el => {
140 + const contextValue = el.dataset.mxchatContext;
141 + if (contextValue && contextValue.trim()) {
142 + contextData.push(contextValue);
143 + }
144 + });
145 +
146 + // Get text content and clean it up
147 + pageContent = clone.textContent || clone.innerText || '';
148 +
149 + // Add context data to page content if any were found
150 + if (contextData.length > 0) {
151 + pageContent += '\n\nAdditional Context:\n' + contextData.join('\n');
152 + }
153 +
154 + // Clean up whitespace and limit length
155 + pageContent = pageContent
156 + .replace(/\s+/g, ' ')
157 + .trim()
158 + .substring(0, 3000); // Limit to 3000 characters to avoid token limits
159 + }
160 +
161 + // Only return context if we have meaningful content
162 + if (!pageContent || pageContent.length < 50) {
163 + return null;
164 + }
165 +
166 + return {
167 + url: pageUrl,
168 + title: pageTitle,
169 + content: pageContent
170 + };
71 171 }
72 172
73 -// Function to check for new messages
74 -function checkForNewMessages() {
173 +// NEW: Track originating page when chat starts
174 +function trackOriginatingPage() {
75 175 const sessionId = getChatSession();
76 - const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
176 + const pageUrl = window.location.href;
177 + const pageTitle = document.title || 'Untitled Page';
77 178
78 - if (!chatPersistenceEnabled) return;
79 -
179 + // Only track once per session
180 + const trackingKey = 'mxchat_originating_tracked_' + sessionId;
181 + if (sessionStorage.getItem(trackingKey)) {
182 + return;
183 + }
184 +
80 185 $.ajax({
81 186 url: mxchatChat.ajax_url,
82 187 type: 'POST',
83 188 data: {
84 - action: 'mxchat_check_new_messages',
189 + action: 'mxchat_track_originating_page',
85 190 session_id: sessionId,
86 - last_seen_id: lastSeenMessageId,
191 + page_url: pageUrl,
192 + page_title: pageTitle,
87 193 nonce: mxchatChat.nonce
88 194 },
89 195 success: function(response) {
90 - if (response.success && response.data.hasNewMessages) {
91 - showNotification();
196 + if (response.success) {
197 + sessionStorage.setItem(trackingKey, 'true');
92 198 }
93 199 }
94 200 });
95 201 }
96 202
97 -// Function to show notification
98 -function showNotification() {
99 - const badge = document.getElementById('chat-notification-badge');
100 - if (badge && $('#floating-chatbot').hasClass('hidden')) {
101 - badge.style.display = 'block';
102 - badge.textContent = '1';
203 +// ====================================
204 +// CORE CHAT FUNCTIONALITY
205 +// ====================================
206 +// Update your existing sendMessage function
207 +function sendMessage() {
208 + var message = $('#chat-input').val();
209 +
210 + // ADD PROMPT HOOK HERE
211 + if (typeof customMxChatFilter === 'function') {
212 + message = customMxChatFilter(message, "prompt");
103 213 }
104 -}
214 +
215 + if (message) {
216 + appendMessage("user", message);
217 + $('#chat-input').val('');
218 + $('#chat-input').css('height', 'auto');
105 219
106 -function hideNotification() {
107 - const badge = document.getElementById('chat-notification-badge');
108 - if (badge) {
109 - badge.style.display = 'none';
110 - }
111 -}
112 -// Function to start notification checking
113 -function startNotificationChecking() {
114 - const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
115 - if (!chatPersistenceEnabled) return;
220 + if (hasQuickQuestions()) {
221 + collapseQuickQuestions();
222 + }
223 + appendThinkingMessage();
224 + scrollToBottom();
116 225
117 - createNotificationBadge();
118 - notificationCheckInterval = setInterval(checkForNewMessages, 30000); // Check every 30 seconds
119 -}
226 + const currentModel = mxchatChat.model || 'gpt-4o';
120 227
121 -// Function to stop notification checking
122 -function stopNotificationChecking() {
123 - if (notificationCheckInterval) {
124 - clearInterval(notificationCheckInterval);
228 + // Check if streaming is enabled AND supported for this model
229 + if (shouldUseStreaming(currentModel)) {
230 + callMxChatStream(message, function(response) {
231 + $('.bot-message.temporary-message').removeClass('temporary-message');
232 + });
233 + } else {
234 + callMxChat(message, function(response) {
235 + replaceLastMessage("bot", response);
236 + });
237 + }
125 238 }
126 239 }
127 240
241 +// Update your existing sendMessageToChatbot function
242 +function sendMessageToChatbot(message) {
243 + // ADD PROMPT HOOK HERE
244 + if (typeof customMxChatFilter === 'function') {
245 + message = customMxChatFilter(message, "prompt");
246 + }
247 +
248 + var sessionId = getChatSession();
128 249
250 + if (hasQuickQuestions()) {
251 + collapseQuickQuestions();
252 + }
253 + appendThinkingMessage();
254 + scrollToBottom();
129 255
256 + const currentModel = mxchatChat.model || 'gpt-4o';
130 257
131 -function getChatSession() {
132 - var sessionId = getCookie('mxchat_session_id');
133 - //console.log("Session ID retrieved from cookie: ", sessionId);
134 -
135 - if (!sessionId) {
136 - sessionId = generateSessionId();
137 - //console.log("Generated new session ID: ", sessionId);
138 - setChatSession(sessionId);
258 + // Check if streaming is enabled AND supported for this model
259 + if (shouldUseStreaming(currentModel)) {
260 + callMxChatStream(message, function(response) {
261 + $('.bot-message.temporary-message').removeClass('temporary-message');
262 + });
263 + } else {
264 + callMxChat(message, function(response) {
265 + $('.temporary-message').remove();
266 + replaceLastMessage("bot", response);
267 + });
139 268 }
140 -
141 - //console.log("Final session ID: ", sessionId);
142 - return sessionId;
143 269 }
144 270
145 -function setChatSession(sessionId) {
146 - // Set the cookie with a 24-hour expiration (86400 seconds)
147 - document.cookie = "mxchat_session_id=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
271 +// Updated shouldUseStreaming function with debugging
272 +function shouldUseStreaming(model) {
273 + // Check if streaming is enabled in settings (using your toggle naming pattern)
274 + const streamingEnabled = mxchatChat.enable_streaming_toggle === 'on';
275 +
276 + // Check if model supports streaming
277 + const streamingSupported = isStreamingSupported(model);
278 +
279 +
280 + // Only use streaming if both enabled and supported
281 + return streamingEnabled && streamingSupported;
148 282 }
149 283
150 -// Get cookie value by name
151 -function getCookie(name) {
152 - let value = "; " + document.cookie;
153 - let parts = value.split("; " + name + "=");
154 - if (parts.length == 2) return parts.pop().split(";").shift();
284 +// Helper function to handle chat mode updates
285 +function handleChatModeUpdates(response, responseText) {
286 + // Check for explicit chat mode in response (THIS IS THE KEY FIX)
287 + if (response.chat_mode) {
288 + updateChatModeIndicator(response.chat_mode);
289 + return; // Return early since we found explicit mode
290 + }
291 + // Check for fallback response chat mode
292 + else if (response.fallbackResponse && response.fallbackResponse.chat_mode) {
293 + updateChatModeIndicator(response.fallbackResponse.chat_mode);
294 + return; // Return early since we found explicit mode
295 + }
296 +
297 + // Only do text-based detection if no explicit mode was provided
298 + // Check for specific AI chatbot response text
299 + if (responseText === 'You are now chatting with the AI chatbot.' ||
300 + responseText.includes('now chatting with the AI') ||
301 + responseText.includes('switched to AI mode') ||
302 + responseText.includes('AI chatbot is now')) {
303 + updateChatModeIndicator('ai');
304 + }
305 + // Check for agent transfer messages
306 + else if (responseText.includes('agent') &&
307 + (responseText.includes('transfer') || responseText.includes('connected'))) {
308 + updateChatModeIndicator('agent');
309 + }
155 310 }
156 311
157 -// Generate a new session ID
158 -function generateSessionId() {
159 - return 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
160 -}
312 +function callMxChat(message, callback) {
313 + // Get page context if contextual awareness is enabled
314 + const pageContext = getPageContext();
315 +
316 + // Prepare AJAX data
317 + const ajaxData = {
318 + action: 'mxchat_handle_chat_request',
319 + message: message,
320 + session_id: getChatSession(),
321 + nonce: mxchatChat.nonce,
322 + current_page_url: window.location.href,
323 + current_page_title: document.title
324 + };
325 +
326 + // Add page context if available
327 + if (pageContext) {
328 + ajaxData.page_context = JSON.stringify(pageContext);
329 + }
330 +
331 + // CHECK FOR VISION FLAGS AND ADD THEM
332 + if (window.mxchatVisionProcessed) {
333 + ajaxData.vision_processed = true;
334 + ajaxData.original_user_message = window.mxchatOriginalMessage || message;
335 + ajaxData.vision_images_count = window.mxchatVisionImagesCount || 0;
336 + // Clear the flags after use
337 + window.mxchatVisionProcessed = false;
338 + window.mxchatOriginalMessage = null;
339 + window.mxchatVisionImagesCount = 0;
340 + }
341 +
342 + $.ajax({
343 + url: mxchatChat.ajax_url,
344 + type: 'POST',
345 + dataType: 'json',
346 + data: ajaxData,
347 + success: function(response) {
348 +
349 + // IMMEDIATE CHAT MODE UPDATE - This should be FIRST
350 + if (response.chat_mode) {
351 + //console.log("Updating chat mode to:", response.chat_mode);
352 + updateChatModeIndicator(response.chat_mode);
353 + }
354 +
355 + // Also check in data property if response is wrapped
356 + if (response.data && response.data.chat_mode) {
357 + //console.log("Updating chat mode from data to:", response.data.chat_mode);
358 + updateChatModeIndicator(response.data.chat_mode);
359 + }
360 + // Log the full response for debugging
361 + //console.log("API Response:", response);
161 362
162 -// Function to send the message to the chatbot (backend)
163 -function sendMessageToChatbot(message) {
164 - var sessionId = getChatSession(); // Reuse the session ID logic
363 + // IMMEDIATE CHAT MODE UPDATE - Handle this first, before any other processing
364 + if (response.chat_mode) {
365 + //console.log("Updating chat mode immediately to:", response.chat_mode);
366 + updateChatModeIndicator(response.chat_mode);
367 + }
165 368
166 - // Hide the popular questions section
167 - $('#mxchat-popular-questions').hide();
369 + // First check if this is a successful response by looking for text, html, or message fields
370 + // This preserves compatibility with your server response format
371 + if (response.text !== undefined || response.html !== undefined || response.message !== undefined ||
372 + (response.success === true && response.data && response.data.status === 'waiting_for_agent')) {
168 373
169 - // Show thinking indicator (no need to append the user's message again)
170 - appendThinkingMessage();
171 - scrollToBottom();
374 + // Handle successful response - this is your original success handling code
172 375
173 - //console.log("Sending message to chatbot:", message); // Log the message
174 - //console.log("Session ID:", sessionId); // Log the session ID
376 + // Handle other responses
377 + let responseText = response.text || '';
378 + let responseHtml = response.html || '';
379 + let responseMessage = response.message || '';
175 380
176 - // Call the chatbot using the same call logic as sendMessage
177 - callMxChat(message, function(response) {
178 - // ** Ensure temporary thinking message is removed before adding new response **
179 - $('.temporary-message').remove();
381 + // Add PDF filename handling
382 + if (response.data && response.data.filename) {
383 + showActivePdf(response.data.filename);
384 + activePdfFile = response.data.filename;
385 + }
180 386
181 - // Replace thinking indicator with actual response
182 - replaceLastMessage("bot", response);
183 - });
184 -}
387 + // Add redirect check here
388 + if (response.redirect_url) {
389 + if (responseText) {
390 + replaceLastMessage("bot", responseText);
391 + }
392 + setTimeout(() => {
393 + window.location.href = response.redirect_url;
394 + }, 1500);
395 + return;
396 + }
185 397
398 + // Check for live agent response
399 + if (response.success && response.data && response.data.status === 'waiting_for_agent') {
400 + updateChatModeIndicator('agent');
401 + return;
402 + }
186 403
404 + // Handle the message and show notification if chat is hidden
405 + if (responseText || responseHtml || responseMessage) {
406 +
407 + // ADD RESPONSE HOOKS HERE - BEFORE DISPLAYING
408 + if (responseText && typeof customMxChatFilter === 'function') {
409 + responseText = customMxChatFilter(responseText, "response");
410 + }
411 + if (responseMessage && typeof customMxChatFilter === 'function') {
412 + responseMessage = customMxChatFilter(responseMessage, "response");
413 + }
414 +
415 + // Update the messages as before
416 + if (responseText && responseHtml) {
417 + replaceLastMessage("bot", responseText, responseHtml);
418 + } else if (responseText) {
419 + replaceLastMessage("bot", responseText);
420 + } else if (responseHtml) {
421 + replaceLastMessage("bot", "", responseHtml);
422 + } else if (responseMessage) {
423 + replaceLastMessage("bot", responseMessage);
424 + }
187 425
426 + // Check if chat is hidden and show notification
427 + if ($('#floating-chatbot').hasClass('hidden')) {
428 + const badge = $('#chat-notification-badge');
429 + if (badge.length) {
430 + badge.show();
431 + }
432 + }
433 + } else {
434 + ////console.error("Unexpected response format:", response);
435 + replaceLastMessage("bot", "I received an empty response. Please try again or contact support if this persists.");
436 + }
188 437
189 -function sendMessage() {
190 - var message = $('#chat-input').val(); // Get value from textarea
191 - if (message) {
192 - appendMessage("user", message); // Append user's message
193 - $('#chat-input').val(''); // Clear the textarea
194 - $('#chat-input').css('height', 'auto'); // Reset height after clearing content
438 + if (response.message_id) {
439 + lastSeenMessageId = response.message_id;
440 + }
195 441
196 - // Hide the popular questions section
197 - $('#mxchat-popular-questions').hide();
442 + return;
443 + }
198 444
199 - // Show typing indicator
200 - appendThinkingMessage();
201 - scrollToBottom();
445 + // If we got here, it's likely an error response
446 + // Now we can check for error conditions with our robust error handling
202 447
203 - callMxChat(message, function(response) {
204 - // Replace typing indicator with actual response
205 - replaceLastMessage("bot", response);
206 - });
207 - }
208 -}
448 + let errorMessage = "";
449 + let errorCode = "";
209 450
451 + // Check various possible error locations in the response
452 + if (response.data && response.data.error_message) {
453 + errorMessage = response.data.error_message;
454 + errorCode = response.data.error_code || "";
455 + } else if (response.error_message) {
456 + errorMessage = response.error_message;
457 + errorCode = response.error_code || "";
458 + } else if (response.message) {
459 + errorMessage = response.message;
460 + } else if (typeof response.data === 'string') {
461 + errorMessage = response.data;
462 + } else if (!response.success) {
463 + // Explicit check for success: false without other error info
464 + errorMessage = "An error occurred. Please try again or contact support.";
465 + } else {
466 + // Fallback for any other unexpected response format
467 + errorMessage = "Unexpected response received. Please try again or contact support.";
468 + }
210 469
470 + // Log the error with code for debugging
471 + //console.log("Response data:", response.data);
472 + ////console.error("API Error:", errorMessage, "Code:", errorCode);
211 473
212 - // Function to append a thinking message with animation
213 - function appendThinkingMessage() {
214 - // Remove any existing thinking dots first
215 - $('.thinking-dots').remove();
474 + // Format user-friendly error message
475 + let displayMessage = errorMessage;
216 476
217 - // Retrieve the bot message font color and background color
218 - var botMessageFontColor = mxchatChat.bot_message_font_color;
219 - var botMessageBgColor = mxchatChat.bot_message_bg_color;
477 + // Customize message for admin users
478 + if (mxchatChat.is_admin) {
479 + // For admin users, show more technical details including error code
480 + displayMessage = errorMessage + (errorCode ? " (Error code: " + errorCode + ")" : "");
481 + }
220 482
483 + replaceLastMessage("bot", displayMessage);
484 + },
485 + error: function(xhr, status, error) {
486 + //console.error("AJAX Error:", status, error);
487 + //console.log("Response Text:", xhr.responseText);
221 488
222 - var thinkingHtml = '<div class="thinking-dots-container">' +
223 - '<div class="thinking-dots">' +
224 - '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
225 - '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
226 - '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
227 - '</div>' +
228 - '</div>';
489 + let errorMessage = "An unexpected error occurred.";
229 490
230 - // Append the thinking dots to the chat container (or within the temporary message div)
231 - $("#chat-box").append('<div class="bot-message temporary-message" style="background-color: ' + botMessageBgColor + ';">' + thinkingHtml + '</div>');
232 - scrollToBottom();
233 - }
491 + // Try to parse the response if it's JSON
492 + try {
493 + const responseJson = JSON.parse(xhr.responseText);
494 + //console.log("Parsed error response:", responseJson);
234 495
235 - // Trigger send button click when "Enter" key is pressed in the textarea
236 - $('#chat-input').keypress(function(e) {
237 - if (e.which == 13 && !e.shiftKey) { // Check if "Enter" is pressed without Shift
238 - e.preventDefault(); // Prevent default "Enter" behavior
239 - $('#send-button').click(); // Trigger send button click
496 + if (responseJson.data && responseJson.data.error_message) {
497 + errorMessage = responseJson.data.error_message;
498 + } else if (responseJson.message) {
499 + errorMessage = responseJson.message;
500 + }
501 + } catch (e) {
502 + // Not JSON or parsing failed, use HTTP status based messages
503 + if (xhr.status === 0) {
504 + errorMessage = "Network error: Please check your internet connection.";
505 + } else if (xhr.status === 403) {
506 + errorMessage = "Access denied: Your session may have expired. Please refresh the page.";
507 + } else if (xhr.status === 404) {
508 + errorMessage = "API endpoint not found. Please contact support.";
509 + } else if (xhr.status === 429) {
510 + errorMessage = "Too many requests. Please try again in a moment.";
511 + } else if (xhr.status >= 500) {
512 + errorMessage = "Server error: The server encountered an issue. Please try again later.";
513 + }
240 514 }
241 - });
242 515
243 - // Handle send button click
244 - $('#send-button').click(function() {
245 - sendMessage();
516 + replaceLastMessage("bot", errorMessage);
517 + }
246 518 });
519 +}
247 520
248 - // Handle click on popular questions
249 - $('.mxchat-popular-question').on('click', function () {
250 - var question = $(this).text(); // Get the text of the clicked question
521 +function callMxChatStream(message, callback) {
522 + //console.log("Using streaming for message:", message);
523 +
524 + const currentModel = mxchatChat.model || 'gpt-4o';
525 + if (!isStreamingSupported(currentModel)) {
526 + //console.log("Streaming not supported, falling back to regular call");
527 + callMxChat(message, callback);
528 + return;
529 + }
251 530
252 - // Append the question as if the user typed it
253 - appendMessage("user", question);
531 + // Get page context if contextual awareness is enabled
532 + const pageContext = getPageContext();
254 533
255 - // Send the question to the server (backend)
256 - sendMessageToChatbot(question);
257 - });
534 + const formData = new FormData();
535 + formData.append('action', 'mxchat_stream_chat');
536 + formData.append('message', message);
537 + formData.append('session_id', getChatSession());
538 + formData.append('nonce', mxchatChat.nonce);
539 + formData.append('current_page_url', window.location.href);
540 + formData.append('current_page_title', document.title);
541 +
542 + // Add page context if available
543 + if (pageContext) {
544 + formData.append('page_context', JSON.stringify(pageContext));
545 + }
258 546
547 + // CHECK FOR VISION FLAGS AND ADD THEM
548 + if (window.mxchatVisionProcessed) {
549 + formData.append('vision_processed', 'true');
550 + formData.append('original_user_message', window.mxchatOriginalMessage || message);
551 + formData.append('vision_images_count', window.mxchatVisionImagesCount || '0');
552 + // Clear the flags after use
553 + window.mxchatVisionProcessed = false;
554 + window.mxchatOriginalMessage = null;
555 + window.mxchatVisionImagesCount = 0;
556 + }
259 557
260 -// Add this new function to handle markdown headers
261 -function formatMarkdownHeaders(text) {
262 - // Handle h1 to h6 headers
263 - return text.replace(/^(#{1,6})\s(.+)$/gm, function(match, hashes, content) {
264 - const level = hashes.length;
265 - return `<h${level} class="chat-heading">${content}</h${level}>`;
266 - });
267 -}
558 + let accumulatedContent = '';
559 + let testingDataReceived = false;
560 + let streamingStarted = false;
268 561
269 -// Update the linkify function to handle both URLs and markdown
270 -// Modified linkify function that only processes URLs in user content
271 -function linkify(inputText) {
272 - if (!inputText) return '';
273 -
274 - // Process markdown headers
275 - let processedText = formatMarkdownHeaders(inputText);
276 -
277 - // Process markdown links
278 - const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
279 - processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
280 - const safeUrl = encodeURI(url);
281 - const safeText = sanitizeUserInput(text);
282 - return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
283 - });
562 + fetch(mxchatChat.ajax_url, {
563 + method: 'POST',
564 + body: formData,
565 + credentials: 'same-origin'
566 + })
567 + .then(response => {
568 + //console.log("Streaming response received:", response);
569 +
570 + // Store the response for potential fallback handling
571 + const responseClone = response.clone();
572 +
573 + if (!response.ok) {
574 + // Try to get error details from response
575 + return responseClone.json().then(errorData => {
576 + //console.log("Server returned error response:", errorData);
577 + throw { isServerError: true, data: errorData };
578 + }).catch(() => {
579 + throw new Error('Network response was not ok');
580 + });
581 + }
284 582
285 - // Process standalone URLs
286 - const urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
287 - processedText = processedText.replace(urlPattern, (match, prefix, url) => {
288 - const safeUrl = encodeURI(url);
289 - return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
583 +// Check if response is JSON instead of streaming
584 +const contentType = response.headers.get('content-type');
585 +if (contentType && contentType.includes('application/json')) {
586 + //console.log("Received JSON response instead of stream");
587 + return responseClone.json().then(data => {
588 + //console.log("Processing JSON response:", data);
589 +
590 + // IMMEDIATE CHAT MODE UPDATE for JSON response
591 + if (data.chat_mode) {
592 + //console.log("JSON Response: Updating chat mode to:", data.chat_mode);
593 + updateChatModeIndicator(data.chat_mode);
594 + }
595 +
596 + // Check for testing panel
597 + if (window.mxchatTestPanelInstance && data.testing_data) {
598 + //console.log('Testing data found in streaming JSON response:', data.testing_data);
599 + window.mxchatTestPanelInstance.handleTestingData(data.testing_data);
600 + }
601 +
602 + // Handle the JSON response directly
603 + handleNonStreamResponse(data, callback);
604 + return Promise.resolve(); // Prevent further processing
290 605 });
606 +}
291 607
292 - // Process www. URLs
293 - const wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
294 - processedText = processedText.replace(wwwPattern, (match, prefix, url) => {
295 - const safeUrl = encodeURI(`http://${url}`);
296 - return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
297 - });
608 + // Continue with streaming processing
609 + const reader = response.body.getReader();
610 + const decoder = new TextDecoder();
611 + let buffer = '';
298 612
299 - return processedText;
300 -}
613 + function processStream() {
614 + reader.read().then(({ done, value }) => {
615 + if (done) {
616 + //console.log("Streaming completed, final content:", accumulatedContent);
617 +
618 + // If streaming completed but no content was received, try to get response as fallback
619 + if (!streamingStarted || !accumulatedContent) {
620 + //console.log("Stream completed with no content, checking for fallback data");
621 +
622 + // Try to read the response as JSON
623 + responseClone.text().then(text => {
624 + try {
625 + const data = JSON.parse(text);
626 + if (data.text || data.message) {
627 + handleNonStreamResponse(data, callback);
628 + } else {
629 + // No valid data, fall back to regular call
630 + $('.bot-message.temporary-message').remove();
631 + callMxChat(message, callback);
632 + }
633 + } catch (e) {
634 + // Could not parse, fall back to regular call
635 + $('.bot-message.temporary-message').remove();
636 + callMxChat(message, callback);
637 + }
638 + }).catch(() => {
639 + $('.bot-message.temporary-message').remove();
640 + callMxChat(message, callback);
641 + });
642 + return;
643 + }
644 +
645 + if (callback) {
646 + callback(accumulatedContent);
647 + }
648 + return;
649 + }
301 650
651 + buffer += decoder.decode(value, { stream: true });
652 + const lines = buffer.split('\n');
653 + buffer = lines.pop() || '';
302 654
303 -function scrollElementToTop(element) {
304 - var chatBox = $('#chat-box');
305 - var elementTop = element.position().top + chatBox.scrollTop();
306 - chatBox.animate({ scrollTop: elementTop }, 500);
307 -}
655 + for (const line of lines) {
656 + if (line.startsWith('data: ')) {
657 + const data = line.substring(6);
308 658
659 + if (data === '[DONE]') {
660 + //console.log("Received [DONE] signal");
661 +
662 + if (!accumulatedContent) {
663 + //console.log("No content received before [DONE]");
664 + $('.bot-message.temporary-message').remove();
665 + callMxChat(message, callback);
666 + return;
667 + }
668 +
669 + if (callback) {
670 + callback(accumulatedContent);
671 + }
672 + return;
673 + }
309 674
310 -// Optimized scrollToBottom function for instant scrolling
311 -function scrollToBottom(instant = false) {
312 - var chatBox = $('#chat-box');
313 - if (instant) {
314 - // Instantly set the scroll position to the bottom
315 - chatBox.scrollTop(chatBox.prop("scrollHeight"));
316 - } else {
317 - // Use requestAnimationFrame for smoother scrolling if needed
318 - let start = null;
319 - const scrollHeight = chatBox.prop("scrollHeight");
320 - const initialScroll = chatBox.scrollTop();
321 - const distance = scrollHeight - initialScroll;
322 - const duration = 500; // Duration in ms
675 + try {
676 + const json = JSON.parse(data);
677 +
678 + // IMMEDIATE CHAT MODE UPDATE FOR STREAMING
679 + if (json.chat_mode) {
680 + //console.log("Stream: Updating chat mode immediately to:", json.chat_mode);
681 + updateChatModeIndicator(json.chat_mode);
682 + }
683 +
684 + // Handle testing data
685 + if (json.testing_data && !testingDataReceived) {
686 + //console.log('Testing data received in stream:', json.testing_data);
687 + if (window.mxchatTestPanelInstance) {
688 + window.mxchatTestPanelInstance.handleTestingData(json.testing_data);
689 + testingDataReceived = true;
690 + }
691 + }
692 + // Handle content streaming
693 + else if (json.content) {
694 + streamingStarted = true;
695 + accumulatedContent += json.content;
696 + updateStreamingMessage(accumulatedContent);
697 + }
698 + // Handle complete response in stream (fallback response)
699 + else if (json.text || json.message) {
700 + //console.log("Received complete response in stream:", json);
701 + handleNonStreamResponse(json, callback);
702 + return;
703 + }
704 + // Handle errors
705 + else if (json.error) {
706 + console.error("Streaming error:", json.error);
707 +
708 + // Check if we have a fallback response
709 + if (json.fallback || json.text || json.message) {
710 + handleNonStreamResponse(json, callback);
711 + } else {
712 + $('.bot-message.temporary-message').remove();
713 + callMxChat(message, callback);
714 + }
715 + return;
716 + }
717 + } catch (e) {
718 + console.error('Error parsing SSE data:', e, 'Data:', data);
719 + }
720 + }
721 + }
323 722
324 - function smoothScroll(timestamp) {
325 - if (!start) start = timestamp;
326 - const progress = timestamp - start;
327 - const currentScroll = initialScroll + (distance * (progress / duration));
328 - chatBox.scrollTop(currentScroll);
723 + processStream();
724 + }).catch(streamError => {
725 + console.error('Error reading stream:', streamError);
726 + $('.bot-message.temporary-message').remove();
727 + callMxChat(message, callback);
728 + });
729 + }
329 730
330 - if (progress < duration) {
331 - requestAnimationFrame(smoothScroll);
731 + processStream();
732 + })
733 + .catch(error => {
734 + //console.log('Streaming failed:', error);
735 +
736 + // Check if we have server error data with chat mode
737 + if (error && error.isServerError && error.data) {
738 + //console.log('Using server error response data');
739 +
740 + // Check for chat mode in error data
741 + if (error.data.chat_mode) {
742 + //console.log("Error Response: Updating chat mode to:", error.data.chat_mode);
743 + updateChatModeIndicator(error.data.chat_mode);
744 + }
745 +
746 + handleNonStreamResponse(error.data, callback);
332 747 } else {
333 - chatBox.scrollTop(scrollHeight); // Ensure it's exactly at the bottom
748 + // Only fall back to regular call if we don't have any response data
749 + //console.log('No response data available, falling back to regular call');
750 + $('.bot-message.temporary-message').remove();
751 + callMxChat(message, callback);
334 752 }
753 + });
754 +}
755 +
756 +// Helper function to handle non-streaming responses (updated to include immediate chat mode handling)
757 +function handleNonStreamResponse(data, callback) {
758 + //console.log("Handling non-stream response:", data);
759 +
760 + // IMMEDIATE CHAT MODE UPDATE FOR NON-STREAMING RESPONSES
761 + if (data.chat_mode) {
762 + //console.log("Non-Stream: Updating chat mode immediately to:", data.chat_mode);
763 + updateChatModeIndicator(data.chat_mode);
764 + }
765 +
766 + // Also check in data property if response is wrapped
767 + if (data.data && data.data.chat_mode) {
768 + //console.log("Non-Stream: Updating chat mode from data to:", data.data.chat_mode);
769 + updateChatModeIndicator(data.data.chat_mode);
770 + }
771 +
772 + // Remove temporary message
773 + $('.bot-message.temporary-message').remove();
774 +
775 + // Handle different response formats
776 + if (data.text || data.html || data.message) {
777 +
778 + // Apply response hooks
779 + if (data.text && typeof customMxChatFilter === 'function') {
780 + data.text = customMxChatFilter(data.text, "response");
335 781 }
336 -
337 - requestAnimationFrame(smoothScroll);
782 + if (data.message && typeof customMxChatFilter === 'function') {
783 + data.message = customMxChatFilter(data.message, "response");
784 + }
785 +
786 + // Display the response
787 + if (data.text && data.html) {
788 + replaceLastMessage("bot", data.text, data.html);
789 + } else if (data.text) {
790 + replaceLastMessage("bot", data.text);
791 + } else if (data.html) {
792 + replaceLastMessage("bot", "", data.html);
793 + } else if (data.message) {
794 + replaceLastMessage("bot", data.message);
795 + }
338 796 }
339 -}
340 -
341 -
342 - // Function to format text with **bold** inside double asterisks
343 - function formatBoldText(text) {
344 - return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
797 +
798 + // Handle other response properties
799 + if (data.data && data.data.filename) {
800 + showActivePdf(data.data.filename);
801 + activePdfFile = data.data.filename;
345 802 }
346 -
347 - // Function to convert newline characters to HTML line breaks and handle paragraph spacing
348 -function convertNewlinesToBreaks(text) {
349 - // Split the text into paragraphs (marked by double newlines or multiple <br> tags)
350 - const paragraphs = text.split(/(?:\n\n|\<br\>\s*\<br\>)/g);
351 803
352 - // Wrap each paragraph in <p> tags
353 - return paragraphs
354 - .map(para => `<p>${para.trim()}</p>`)
355 - .join('');
804 + if (data.redirect_url) {
805 + setTimeout(() => {
806 + window.location.href = data.redirect_url;
807 + }, 1500);
808 + }
809 +
810 + if (callback) {
811 + callback(data.text || data.message || '');
812 + }
356 813 }
357 - // Copy to clipboard function
358 - // Function to copy text to clipboard
359 - function copyToClipboard(text) {
360 - var tempInput = $('<input>');
361 - $('body').append(tempInput);
362 - tempInput.val(text).select();
363 - document.execCommand('copy');
364 - tempInput.remove();
365 - }
366 -
367 -
814 +// Enhanced updateChatModeIndicator function for immediate DOM updates
368 815 function updateChatModeIndicator(mode) {
816 + //console.log("updateChatModeIndicator called with mode:", mode);
369 817 const indicator = document.getElementById('chat-mode-indicator');
370 818 if (indicator) {
371 - indicator.textContent = mode === 'agent' ? 'Live Agent' : 'AI Agent';
819 + const oldText = indicator.textContent;
820 +
821 + if (mode === 'agent') {
822 + indicator.textContent = 'Live Agent';
823 + startPolling();
824 + //console.log("Set indicator to Live Agent");
825 + } else {
826 + // Everything else is AI mode
827 + const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
828 + indicator.textContent = customAiText;
829 + stopPolling();
830 + //console.log("Set indicator to AI Agent:", customAiText);
831 + }
832 +
833 + // Force immediate DOM update and reflow
834 + if (oldText !== indicator.textContent) {
835 + // Force a reflow to ensure the change is visible immediately
836 + indicator.style.display = 'none';
837 + indicator.offsetHeight; // Trigger reflow
838 + indicator.style.display = '';
839 +
840 + // Double-check after a brief moment to ensure the change stuck
841 + setTimeout(() => {
842 + //console.log("Final indicator text:", indicator.textContent);
843 + if (mode === 'agent' && indicator.textContent !== 'Live Agent') {
844 + console.warn("Mode indicator update failed, forcing update");
845 + indicator.textContent = 'Live Agent';
846 + } else if (mode !== 'agent' && indicator.textContent === 'Live Agent') {
847 + console.warn("Mode indicator stuck on Live Agent, forcing AI update");
848 + const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
849 + indicator.textContent = customAiText;
850 + }
851 + }, 50);
852 + }
853 + } else {
854 + console.error("Chat mode indicator element not found!");
372 855 }
856 +}
373 857
374 - // Start or stop polling based on mode
375 - if (mode === 'agent') {
376 - startPolling();
377 - } else {
378 - stopPolling();
858 +// Helper function to handle non-streaming responses (updated to include chat mode handling)
859 +function handleNonStreamResponse(data, callback) {
860 + //console.log("Handling non-stream response:", data);
861 +
862 + // Remove temporary message
863 + $('.bot-message.temporary-message').remove();
864 +
865 + // Handle different response formats
866 + if (data.text || data.html || data.message) {
867 +
868 + // Apply response hooks
869 + if (data.text && typeof customMxChatFilter === 'function') {
870 + data.text = customMxChatFilter(data.text, "response");
871 + }
872 + if (data.message && typeof customMxChatFilter === 'function') {
873 + data.message = customMxChatFilter(data.message, "response");
874 + }
875 +
876 + // NEW: Handle chat mode updates for streaming responses too
877 + handleChatModeUpdates(data, data.text || data.message);
878 +
879 + // Display the response
880 + if (data.text && data.html) {
881 + replaceLastMessage("bot", data.text, data.html);
882 + } else if (data.text) {
883 + replaceLastMessage("bot", data.text);
884 + } else if (data.html) {
885 + replaceLastMessage("bot", "", data.html);
886 + } else if (data.message) {
887 + replaceLastMessage("bot", data.message);
888 + }
379 889 }
890 +
891 + // Handle other response properties
892 + if (data.data && data.data.filename) {
893 + showActivePdf(data.data.filename);
894 + activePdfFile = data.data.filename;
895 + }
896 +
897 + if (data.redirect_url) {
898 + setTimeout(() => {
899 + window.location.href = data.redirect_url;
900 + }, 1500);
901 + }
902 +
903 + if (callback) {
904 + callback(data.text || data.message || '');
905 + }
380 906 }
381 907
382 -function callMxChat(message, callback) {
383 - $.ajax({
384 - url: mxchatChat.ajax_url,
385 - type: 'POST',
386 - dataType: 'json',
387 - data: {
388 - action: 'mxchat_handle_chat_request',
389 - message: message,
390 - session_id: getChatSession(),
391 - nonce: mxchatChat.nonce
392 - },
393 - success: function(response) {
394 - // Existing chat mode check
395 - if (response.chat_mode) {
396 - updateChatModeIndicator(response.chat_mode);
397 - }
398 - else if (response.fallbackResponse && response.fallbackResponse.chat_mode) {
399 - updateChatModeIndicator(response.fallbackResponse.chat_mode);
400 - }
908 +// Function to update message during streaming
909 +function updateStreamingMessage(content) {
910 + // ADD RESPONSE HOOK FOR REAL-TIME STREAMING
911 + if (typeof customMxChatFilter === 'function') {
912 + content = customMxChatFilter(content, "response");
913 + }
914 +
915 + const formattedContent = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(content))));
401 916
402 - // Add PDF filename handling
403 - if (response.data && response.data.filename) {
404 - showActivePdf(response.data.filename);
405 - activePdfFile = response.data.filename;
406 - }
407 -
408 - // Add redirect check here
409 - if (response.redirect_url) {
410 - let responseText = response.text || '';
411 - if (responseText) {
412 - replaceLastMessage("bot", responseText);
413 - }
414 - setTimeout(() => {
415 - window.location.href = response.redirect_url;
416 - }, 1500);
417 - return;
418 - }
917 + // Find the temporary message
918 + const tempMessage = $('.bot-message.temporary-message').last();
419 919
920 + if (tempMessage.length) {
921 + // Update existing message
922 + tempMessage.html(formattedContent);
923 + } else {
924 + // Create new temporary message if it doesn't exist
925 + appendMessage("bot", content, '', [], true);
926 + }
927 +}
420 928
421 - // Check for live agent response
422 - if (response.success && response.data && response.data.status === 'waiting_for_agent') {
423 - updateChatModeIndicator('agent');
424 - return;
425 - }
929 +//Function to check if streaming is supported for the current model
930 +function isStreamingSupported(model) {
931 + if (!model) return false;
426 932
427 - // Handle other responses
428 - let responseText = response.text || '';
429 - let responseHtml = response.html || '';
430 - let responseMessage = response.message || '';
933 + //console.log("Checking streaming support for model:", model); // Debug log
431 934
432 - if (responseText === 'You are now chatting with the AI chatbot.') {
433 - updateChatModeIndicator('ai');
434 - }
935 + // Get the model prefix
936 + const modelPrefix = model.split('-')[0].toLowerCase();
937 +
938 + //console.log("Model prefix:", modelPrefix); // Debug log
435 939
436 - // Handle the message and show notification if chat is hidden
437 - if (responseText || responseHtml || responseMessage) {
438 - // Update the messages as before
439 - if (responseText && responseHtml) {
440 - replaceLastMessage("bot", responseText, responseHtml);
441 - } else if (responseText) {
442 - replaceLastMessage("bot", responseText);
443 - } else if (responseHtml) {
444 - replaceLastMessage("bot", "", responseHtml);
445 - } else if (responseMessage) {
446 - replaceLastMessage("bot", responseMessage);
447 - }
940 + // Support streaming for OpenAI, Claude, Grok, and DeepSeek models
941 + const isSupported = modelPrefix === 'gpt' || modelPrefix === 'o1' || modelPrefix === 'claude' || modelPrefix === 'grok' || modelPrefix === 'deepseek';
942 +
943 + //console.log("Streaming supported:", isSupported); // Debug log
944 +
945 + return isSupported;
946 +}
448 947
449 - // Check if chat is hidden and show notification
450 - if ($('#floating-chatbot').hasClass('hidden')) {
451 - const badge = $('#chat-notification-badge');
452 - if (badge.length) {
453 - badge.show();
454 - }
455 - }
456 - } else {
457 - console.error("Unexpected response format:", response);
458 - replaceLastMessage("bot", "I'm sorry, something went wrong.");
459 - }
948 +// Update the event handlers to use the correct function names
949 +$('#send-button').off('click').on('click', function() {
950 + sendMessage(); // Use the updated sendMessage function
951 +});
460 952
461 - if (response.message_id) {
462 - lastSeenMessageId = response.message_id;
463 - }
464 - },
465 - error: function(xhr, status, error) {
466 - replaceLastMessage("bot", "An unexpected error occurred.");
467 - }
468 - });
469 -}
953 +// Override enter key handler
954 +$('#chat-input').off('keypress').on('keypress', function(e) {
955 + if (e.which == 13 && !e.shiftKey) {
956 + e.preventDefault();
957 + sendMessage(); // Use the updated sendMessage function
958 + }
959 +});
470 960
471 -// Sanitize only user input
472 -function sanitizeUserInput(text) {
473 - const div = document.createElement('div');
474 - div.textContent = text;
475 - return div.innerHTML;
476 -}
477 -
478 -// Modified appendMessage function that only sanitizes user content
961 +
479 962 function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) {
480 963 try {
481 964 // Determine styles based on sender type
482 965 let messageClass, bgColor, fontColor;
@@ -498,8 +981,9 @@
498 981 }
499 982
500 983 const messageDiv = $('<div>')
501 984 .addClass(messageClass)
985 + .attr('dir', 'auto')
502 986 .css({
503 987 'background': bgColor,
504 988 'color': fontColor,
505 989 'margin-bottom': '1em'
@@ -516,11 +1000,10 @@
516 1000 }
517 1001
518 1002 // Add images if provided
519 1003 if (images && images.length > 0) {
520 - fullMessage += '<div class="image-gallery">';
1004 + fullMessage += '<div class="image-gallery" dir="auto">';
521 1005 images.forEach(img => {
522 - // Ensure image URLs and titles are properly escaped
523 1006 const safeTitle = sanitizeUserInput(img.title);
524 1007 const safeUrl = encodeURI(img.image_url);
525 1008 const safeThumbnail = encodeURI(img.thumbnail_url);
526 1009
@@ -546,8 +1029,13 @@
546 1029 messageDiv.addClass('temporary-message');
547 1030 }
548 1031
549 1032 messageDiv.hide().appendTo('#chat-box').fadeIn(300, function() {
1033 + // FIXED: Use event delegation for link tracking
1034 + if (sender === "bot" || sender === "agent") {
1035 + attachLinkTracking(messageDiv, messageText);
1036 + }
1037 +
550 1038 if (sender === "bot") {
551 1039 const lastUserMessage = $('#chat-box').find('.user-message').last();
552 1040 if (lastUserMessage.length) {
553 1041 scrollElementToTop(lastUserMessage);
@@ -563,8 +1051,62 @@
563 1051 console.error("Error rendering message:", error);
564 1052 }
565 1053 }
566 1054
1055 +// NEW: Helper function to attach link tracking with proper event handling
1056 +function attachLinkTracking(messageDiv, messageText) {
1057 + // Use a slight delay to ensure DOM is ready
1058 + setTimeout(function() {
1059 + const links = messageDiv.find('a[href]').not('[data-tracked]');
1060 +
1061 + links.each(function() {
1062 + const $link = $(this);
1063 + const originalHref = $link.attr('href');
1064 +
1065 + // Mark as tracked to avoid duplicate handlers
1066 + $link.attr('data-tracked', 'true');
1067 +
1068 + // Only track external URLs
1069 + if (originalHref && (originalHref.startsWith('http://') || originalHref.startsWith('https://'))) {
1070 + // Remove any existing click handlers first
1071 + $link.off('click.tracking');
1072 +
1073 + // Add new click handler with namespace
1074 + $link.on('click.tracking', function(e) {
1075 + e.preventDefault();
1076 + e.stopPropagation();
1077 +
1078 + const messageContext = typeof messageText === 'string'
1079 + ? messageText.substring(0, 200)
1080 + : '';
1081 +
1082 + // Track the click
1083 + $.ajax({
1084 + url: mxchatChat.ajax_url,
1085 + type: 'POST',
1086 + data: {
1087 + action: 'mxchat_track_url_click',
1088 + session_id: getChatSession(),
1089 + url: originalHref,
1090 + message_context: messageContext,
1091 + nonce: mxchatChat.nonce
1092 + },
1093 + complete: function() {
1094 + // Always redirect, even if tracking fails
1095 + if ($link.attr('target') === '_blank' || linkTarget === '_blank') {
1096 + window.open(originalHref, '_blank');
1097 + } else {
1098 + window.location.href = originalHref;
1099 + }
1100 + }
1101 + });
1102 +
1103 + return false; // Extra insurance to prevent default
1104 + });
1105 + }
1106 + });
1107 + }, 100); // Small delay to ensure DOM is ready
1108 +}
567 1109
568 1110 function replaceLastMessage(sender, responseText, responseHtml = '', images = []) {
569 1111 var messageClass = sender === "user" ? "user-message" : sender === "agent" ? "agent-message" : "bot-message";
570 1112 var lastMessageDiv = $('#chat-box').find('.bot-message.temporary-message, .agent-message.temporary-message').last();
@@ -587,9 +1129,9 @@
587 1129 fullMessage += '<br><br>' + responseHtml;
588 1130 }
589 1131
590 1132 if (images.length > 0) {
591 - fullMessage += '<div class="image-gallery">';
1133 + fullMessage += '<div class="image-gallery" dir="auto">';
592 1134 images.forEach(img => {
593 1135 fullMessage += `
594 1136 <div style="margin-bottom: 10px;">
595 1137 <strong>${img.title}</strong><br>
@@ -606,8 +1148,9 @@
606 1148 $(this)
607 1149 .html(fullMessage)
608 1150 .removeClass('bot-message user-message')
609 1151 .addClass(messageClass)
1152 + .attr('dir', 'auto')
610 1153 .css({
611 1154 'background-color': bgColor,
612 1155 'color': fontColor,
613 1156 })
@@ -612,9 +1155,14 @@
612 1155 'color': fontColor,
613 1156 })
614 1157 .removeClass('temporary-message')
615 1158 .fadeIn(200, function() {
1159 + // FIXED: Use the helper function for link tracking
616 1160 if (sender === "bot" || sender === "agent") {
1161 + attachLinkTracking($(this), responseText);
1162 + }
1163 +
1164 + if (sender === "bot" || sender === "agent") {
617 1165 const lastUserMessage = $('#chat-box').find('.user-message').last();
618 1166 if (lastUserMessage.length) {
619 1167 scrollElementToTop(lastUserMessage);
620 1168 }
@@ -630,26 +1178,374 @@
630 1178 }
631 1179 }
632 1180
633 1181
634 -function startPolling() {
635 - // Clear any existing interval first
636 - stopPolling();
637 - // Start new polling interval
638 - pollingInterval = setInterval(checkForAgentMessages, 5000);
639 - //console.log("Started agent message polling");
640 -}
1182 + function appendThinkingMessage() {
1183 + // Remove any existing thinking dots first
1184 + $('.thinking-dots').remove();
641 1185
642 -function stopPolling() {
643 - if (pollingInterval) {
644 - clearInterval(pollingInterval);
645 - pollingInterval = null;
646 - //console.log("Stopped agent message polling");
1186 + // Retrieve the bot message font color and background color
1187 + var botMessageFontColor = mxchatChat.bot_message_font_color;
1188 + var botMessageBgColor = mxchatChat.bot_message_bg_color;
1189 +
1190 +
1191 + var thinkingHtml = '<div class="thinking-dots-container">' +
1192 + '<div class="thinking-dots">' +
1193 + '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
1194 + '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
1195 + '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
1196 + '</div>' +
1197 + '</div>';
1198 +
1199 + // Append the thinking dots to the chat container (or within the temporary message div)
1200 + $("#chat-box").append('<div class="bot-message temporary-message" style="background-color: ' + botMessageBgColor + ';">' + thinkingHtml + '</div>');
1201 + scrollToBottom();
647 1202 }
1203 +
1204 + function removeThinkingDots() {
1205 + $('.thinking-dots').closest('.temporary-message').remove();
1206 + }
1207 +
1208 +
1209 + // ====================================
1210 + // TEXT FORMATTING & PROCESSING
1211 + // ====================================
1212 +
1213 +
1214 + function linkify(inputText) {
1215 + if (!inputText) return '';
1216 +
1217 + // Process markdown headers
1218 + let processedText = formatMarkdownHeaders(inputText);
1219 +
1220 + // Process markdown links
1221 + const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
1222 + processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
1223 + const safeUrl = encodeURI(url);
1224 + const safeText = sanitizeUserInput(text);
1225 + return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
1226 + });
1227 +
1228 + // Process phone numbers (tel:)
1229 + const phonePattern = /\[([^\]]+)\]\((tel:[\d+]+)\)/g;
1230 + processedText = processedText.replace(phonePattern, (match, text, phone) => {
1231 + const safePhone = encodeURI(phone);
1232 + const safeText = sanitizeUserInput(text);
1233 + return `<a href="${safePhone}">${safeText}</a>`;
1234 + });
1235 +
1236 + // Process standalone URLs
1237 + const urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
1238 + processedText = processedText.replace(urlPattern, (match, prefix, url) => {
1239 + const safeUrl = encodeURI(url);
1240 + return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
1241 + });
1242 +
1243 + // Process www. URLs
1244 + const wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
1245 + processedText = processedText.replace(wwwPattern, (match, prefix, url) => {
1246 + const safeUrl = encodeURI(`http://${url}`);
1247 + return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
1248 + });
1249 +
1250 + // Add this after your phone pattern
1251 + const mailtoPattern = /\[([^\]]+)\]\((mailto:[^\)]+)\)/g;
1252 + processedText = processedText.replace(mailtoPattern, (match, text, mailto) => {
1253 + const safeMailto = encodeURI(mailto);
1254 + const safeText = sanitizeUserInput(text);
1255 + return `<a href="${safeMailto}">${safeText}</a>`;
1256 + });
1257 +
1258 + return processedText;
1259 + }
1260 +
1261 + function formatMarkdownHeaders(text) {
1262 + // Handle h1 to h6 headers
1263 + return text.replace(/^(#{1,6})\s(.+)$/gm, function(match, hashes, content) {
1264 + const level = hashes.length;
1265 + return `<h${level} class="chat-heading">${content}</h${level}>`;
1266 + });
1267 + }
1268 +
1269 + function formatBoldText(text) {
1270 + return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
1271 + }
1272 +
1273 + function convertNewlinesToBreaks(text) {
1274 + // Split the text into paragraphs (marked by double newlines or multiple <br> tags)
1275 + const paragraphs = text.split(/(?:\n\n|\<br\>\s*\<br\>)/g);
1276 +
1277 + // Wrap each paragraph in <p> tags
1278 + return paragraphs
1279 + .map(para => `<p>${para.trim()}</p>`)
1280 + .join('');
1281 + }
1282 +
1283 + function formatCodeBlocks(text) {
1284 + // First handle raw PHP tags
1285 + text = text.replace(/(<\?php[\s\S]*?\?>)/g, (match) => {
1286 + return `<pre><code class="language-php">${escapeHtml(match)}</code></pre>`;
1287 + });
1288 +
1289 + // Then handle code blocks with backticks
1290 + text = text.replace(/```php5?\n([\s\S]+?)```/gi, (match, code) => {
1291 + return `<pre><code class="language-php">${escapeHtml(code)}</code></pre>`;
1292 + });
1293 +
1294 + return text;
1295 + }
1296 +
1297 + function sanitizeUserInput(text) {
1298 + const div = document.createElement('div');
1299 + div.textContent = text;
1300 + return div.innerHTML;
1301 + }
1302 +
1303 +
1304 + function escapeHtml(unsafe) {
1305 + // First check if it's already a code block
1306 + if (unsafe.includes('<pre><code') || unsafe.includes('</code></pre>')) {
1307 + return unsafe;
1308 + }
1309 +
1310 + return unsafe
1311 + .replace(/&/g, "&amp;")
1312 + .replace(/</g, "&lt;")
1313 + .replace(/>/g, "&gt;")
1314 + .replace(/"/g, "&quot;")
1315 + .replace(/'/g, "&#039;");
1316 + }
1317 +
1318 + function decodeHTMLEntities(text) {
1319 + var textArea = document.createElement('textarea');
1320 + textArea.innerHTML = text;
1321 + return textArea.value;
1322 + }
1323 +
1324 +
1325 + // ====================================
1326 + // UI & SCROLLING CONTROLS
1327 + // ====================================
1328 +
1329 + function scrollToBottom(instant = false) {
1330 + var chatBox = $('#chat-box');
1331 + if (instant) {
1332 + // Instantly set the scroll position to the bottom
1333 + chatBox.scrollTop(chatBox.prop("scrollHeight"));
1334 + } else {
1335 + // Use requestAnimationFrame for smoother scrolling if needed
1336 + let start = null;
1337 + const scrollHeight = chatBox.prop("scrollHeight");
1338 + const initialScroll = chatBox.scrollTop();
1339 + const distance = scrollHeight - initialScroll;
1340 + const duration = 500; // Duration in ms
1341 +
1342 + function smoothScroll(timestamp) {
1343 + if (!start) start = timestamp;
1344 + const progress = timestamp - start;
1345 + const currentScroll = initialScroll + (distance * (progress / duration));
1346 + chatBox.scrollTop(currentScroll);
1347 +
1348 + if (progress < duration) {
1349 + requestAnimationFrame(smoothScroll);
1350 + } else {
1351 + chatBox.scrollTop(scrollHeight); // Ensure it's exactly at the bottom
1352 + }
1353 + }
1354 +
1355 + requestAnimationFrame(smoothScroll);
1356 + }
1357 + }
1358 +
1359 + function scrollElementToTop(element) {
1360 + var chatBox = $('#chat-box');
1361 + var elementTop = element.position().top + chatBox.scrollTop();
1362 + chatBox.animate({ scrollTop: elementTop }, 500);
1363 + }
1364 +
1365 + function showChatWidget() {
1366 + // First ensure display is set
1367 + $('#floating-chatbot-button').css('display', 'flex');
1368 + // Then handle the fade
1369 + $('#floating-chatbot-button').fadeTo(500, 1);
1370 + // Force visibility
1371 + $('#floating-chatbot-button').removeClass('hidden');
1372 + //console.log('Showing widget');
1373 + }
1374 +
1375 + function hideChatWidget() {
1376 + $('#floating-chatbot-button').css('display', 'none');
1377 + $('#floating-chatbot-button').addClass('hidden');
1378 + //console.log('Hiding widget');
1379 + }
1380 +
1381 + function disableScroll() {
1382 + if (isMobile()) {
1383 + $('body').css('overflow', 'hidden');
1384 + }
1385 + }
1386 +
1387 + function enableScroll() {
1388 + if (isMobile()) {
1389 + $('body').css('overflow', '');
1390 + }
1391 + }
1392 +
1393 + function isMobile() {
1394 + // This can be a simple check, or more sophisticated detection of mobile devices
1395 + return window.innerWidth <= 768; // Example threshold for mobile devices
1396 + }
1397 +
1398 + function setFullHeight() {
1399 + var vh = $(window).innerHeight() * 0.01;
1400 + $(':root').css('--vh', vh + 'px');
1401 + }
1402 +
1403 +
1404 + // ====================================
1405 + // NOTIFICATION SYSTEM
1406 + // ====================================
1407 +
1408 + function createNotificationBadge() {
1409 + //console.log("Creating notification badge...");
1410 + const chatButton = document.getElementById('floating-chatbot-button');
1411 + //console.log("Chat button found:", !!chatButton);
1412 +
1413 + if (!chatButton) return;
1414 +
1415 + // Remove any existing badge first
1416 + const existingBadge = chatButton.querySelector('.chat-notification-badge');
1417 + if (existingBadge) {
1418 + //console.log("Removing existing badge");
1419 + existingBadge.remove();
1420 + }
1421 +
1422 + notificationBadge = document.createElement('div');
1423 + notificationBadge.className = 'chat-notification-badge';
1424 + notificationBadge.style.cssText = `
1425 + display: none;
1426 + position: absolute;
1427 + top: -5px;
1428 + right: -5px;
1429 + background-color: red;
1430 + color: white;
1431 + border-radius: 50%;
1432 + padding: 4px 8px;
1433 + font-size: 12px;
1434 + font-weight: bold;
1435 + z-index: 10001;
1436 + `;
1437 + chatButton.style.position = 'relative';
1438 + chatButton.appendChild(notificationBadge);
1439 +
1440 + }
1441 +
1442 + function showNotification() {
1443 + const badge = document.getElementById('chat-notification-badge');
1444 + if (badge && $('#floating-chatbot').hasClass('hidden')) {
1445 + badge.style.display = 'block';
1446 + badge.textContent = '1';
1447 + }
1448 + }
1449 +
1450 + function hideNotification() {
1451 + const badge = document.getElementById('chat-notification-badge');
1452 + if (badge) {
1453 + badge.style.display = 'none';
1454 + }
1455 + }
1456 +
1457 + function startNotificationChecking() {
1458 + const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
1459 + if (!chatPersistenceEnabled) return;
1460 +
1461 + createNotificationBadge();
1462 + notificationCheckInterval = setInterval(checkForNewMessages, 30000); // Check every 30 seconds
1463 + }
1464 +
1465 + function stopNotificationChecking() {
1466 + if (notificationCheckInterval) {
1467 + clearInterval(notificationCheckInterval);
1468 + }
1469 + }
1470 +
1471 + function checkForNewMessages() {
1472 + const sessionId = getChatSession();
1473 + const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
1474 +
1475 + if (!chatPersistenceEnabled) return;
1476 +
1477 + $.ajax({
1478 + url: mxchatChat.ajax_url,
1479 + type: 'POST',
1480 + data: {
1481 + action: 'mxchat_check_new_messages',
1482 + session_id: sessionId,
1483 + last_seen_id: lastSeenMessageId,
1484 + nonce: mxchatChat.nonce
1485 + },
1486 + success: function(response) {
1487 + if (response.success && response.data.hasNewMessages) {
1488 + showNotification();
1489 + }
1490 + }
1491 + });
1492 + }
1493 +
1494 +
1495 + // ====================================
1496 + // LIVE AGENT FUNCTIONALITY
1497 + // ====================================
1498 +
1499 +function updateChatModeIndicator(mode) {
1500 + //console.log("updateChatModeIndicator called with mode:", mode);
1501 + const indicator = document.getElementById('chat-mode-indicator');
1502 + if (indicator) {
1503 + const oldText = indicator.textContent;
1504 +
1505 + if (mode === 'agent') {
1506 + indicator.textContent = 'Live Agent';
1507 + startPolling();
1508 + //console.log("Set indicator to Live Agent");
1509 + } else {
1510 + // Everything else is AI mode
1511 + const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
1512 + indicator.textContent = customAiText;
1513 + stopPolling();
1514 + //console.log("Set indicator to AI Agent:", customAiText);
1515 + }
1516 +
1517 + // Force immediate DOM update
1518 + if (oldText !== indicator.textContent) {
1519 + // Force a reflow to ensure the change is visible immediately
1520 + indicator.offsetHeight;
1521 +
1522 + // Double-check after a brief moment
1523 + setTimeout(() => {
1524 + //console.log("Final indicator text:", indicator.textContent);
1525 + }, 50);
1526 + }
1527 + } else {
1528 + console.error("Chat mode indicator element not found!");
1529 + }
648 1530 }
649 1531
650 -
651 -// Update your checkForAgentMessages function
1532 + function startPolling() {
1533 + // Clear any existing interval first
1534 + stopPolling();
1535 + // Start new polling interval
1536 + pollingInterval = setInterval(checkForAgentMessages, 5000);
1537 + //console.log("Started agent message polling");
1538 + }
1539 +
1540 + function stopPolling() {
1541 + if (pollingInterval) {
1542 + clearInterval(pollingInterval);
1543 + pollingInterval = null;
1544 + //console.log("Stopped agent message polling");
1545 + }
1546 + }
1547 +
652 1548 function checkForAgentMessages() {
653 1549 const sessionId = getChatSession();
654 1550 $.ajax({
655 1551 url: mxchatChat.ajax_url,
@@ -658,8 +1554,9 @@
658 1554 data: {
659 1555 action: 'mxchat_fetch_new_messages',
660 1556 session_id: sessionId,
661 1557 last_seen_id: lastSeenMessageId,
1558 + persistence_enabled: 'true', // Add this too
662 1559 nonce: mxchatChat.nonce
663 1560 },
664 1561 success: function (response) {
665 1562 if (response.success && response.data?.new_messages) {
@@ -667,9 +1564,10 @@
667 1564
668 1565 response.data.new_messages.forEach(function (message) {
669 1566 if (message.role === "agent" && !processedMessageIds.has(message.id)) {
670 1567 hasNewMessage = true;
671 - replaceLastMessage("agent", message.content);
1568 + // CHANGE THIS LINE:
1569 + appendMessage("agent", message.content); // Instead of replaceLastMessage
672 1570 lastSeenMessageId = message.id;
673 1571 processedMessageIds.add(message.id);
674 1572 }
675 1573 });
@@ -681,13 +1579,18 @@
681 1579 scrollToBottom(true);
682 1580 }
683 1581 },
684 1582 error: function (xhr, status, error) {
685 - console.error("Polling error:", xhr, status, error);
1583 + //console.error("Polling error:", xhr, status, error);
686 1584 }
687 1585 });
688 1586 }
689 -function loadChatHistory() {
1587 +
1588 + // ====================================
1589 + // CHAT HISTORY & PERSISTENCE
1590 + // ====================================
1591 +
1592 + function loadChatHistory() {
690 1593 var sessionId = getChatSession();
691 1594 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
692 1595
693 1596 if (chatPersistenceEnabled && sessionId) {
@@ -699,196 +1602,298 @@
699 1602 action: 'mxchat_fetch_conversation_history',
700 1603 session_id: sessionId
701 1604 },
702 1605 success: function(response) {
703 - if (response.success && response.data && Array.isArray(response.data.conversation)) {
1606 + // Check if the response indicates success
1607 + if (response.success) {
1608 + // Handle case where conversation data exists and is an array
1609 + if (response.data && Array.isArray(response.data.conversation)) {
1610 + var $chatBox = $('#chat-box');
1611 + var $fragment = $(document.createDocumentFragment());
1612 + let highestMessageId = lastSeenMessageId;
704 1613
1614 + // Update chat mode if provided
1615 + if (response.data.chat_mode) {
1616 + updateChatModeIndicator(response.data.chat_mode);
1617 + }
705 1618
706 - var $chatBox = $('#chat-box');
707 - var $fragment = $(document.createDocumentFragment());
708 - let highestMessageId = lastSeenMessageId;
1619 + // Only process if there are actual messages
1620 + if (response.data.conversation.length > 0) {
1621 + $.each(response.data.conversation, function(index, message) {
1622 + // Skip agent messages if persistence is off
1623 + if (!chatPersistenceEnabled && message.role === 'agent') {
1624 + return;
1625 + }
709 1626
710 - if (response.data.chat_mode) {
711 - updateChatModeIndicator(response.data.chat_mode);
712 - }
1627 + var messageClass, messageBgColor, messageFontColor;
713 1628
714 - $.each(response.data.conversation, function(index, message) {
715 - // Skip agent messages if persistence is off
716 - if (!chatPersistenceEnabled && message.role === 'agent') {
717 - return;
718 - }
1629 + switch (message.role) {
1630 + case 'user':
1631 + messageClass = 'user-message';
1632 + messageBgColor = userMessageBgColor;
1633 + messageFontColor = userMessageFontColor;
1634 + break;
1635 + case 'agent':
1636 + messageClass = 'agent-message';
1637 + messageBgColor = liveAgentMessageBgColor;
1638 + messageFontColor = liveAgentMessageFontColor;
1639 + break;
1640 + default:
1641 + messageClass = 'bot-message';
1642 + messageBgColor = botMessageBgColor;
1643 + messageFontColor = botMessageFontColor;
1644 + break;
1645 + }
719 1646
720 - var messageClass, messageBgColor, messageFontColor;
1647 + var messageElement = $('<div>').addClass(messageClass)
1648 + .css({
1649 + 'background': messageBgColor,
1650 + 'color': messageFontColor
1651 + });
721 1652
722 - switch (message.role) {
723 - case 'user':
724 - messageClass = 'user-message';
725 - messageBgColor = userMessageBgColor;
726 - messageFontColor = userMessageFontColor;
727 - break;
728 - case 'agent':
729 - messageClass = 'agent-message';
730 - messageBgColor = liveAgentMessageBgColor;
731 - messageFontColor = liveAgentMessageFontColor;
732 - break;
733 - default:
734 - messageClass = 'bot-message';
735 - messageBgColor = botMessageBgColor;
736 - messageFontColor = botMessageFontColor;
737 - break;
738 - }
1653 + var content = message.content;
1654 + content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
1655 + content = decodeHTMLEntities(content);
739 1656
740 - var messageElement = $('<div>').addClass(messageClass)
741 - .css({
742 - 'background': messageBgColor,
743 - 'color': messageFontColor
744 - });
1657 + if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
1658 + messageElement.html(content);
1659 + } else {
1660 + var formattedContent = linkify(
1661 + formatBoldText(
1662 + convertNewlinesToBreaks(formatCodeBlocks(content))
1663 + )
1664 + );
1665 + messageElement.html(formattedContent);
1666 + }
745 1667
746 - var content = message.content;
747 - content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
748 - content = decodeHTMLEntities(content);
1668 + $fragment.append(messageElement);
749 1669
750 - if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
751 - messageElement.html(content);
752 - } else {
753 - var formattedContent = linkify(
754 - formatBoldText(
755 - convertNewlinesToBreaks(formatCodeBlocks(content))
756 - )
757 - );
758 - messageElement.html(formattedContent);
759 - }
1670 + // Track message IDs
1671 + if (message.id) {
1672 + highestMessageId = Math.max(highestMessageId, message.id);
1673 + processedMessageIds.add(message.id);
1674 + }
1675 + });
760 1676
761 - $fragment.append(messageElement);
1677 + // Only append messages and scroll if we have content
1678 + $chatBox.append($fragment);
1679 + scrollToBottom(true);
762 1680
763 - // In loadChatHistory, change this part:
764 - if (message.id) {
765 - highestMessageId = Math.max(highestMessageId, message.id);
766 - processedMessageIds.add(message.id); // Add all message IDs to processed set
1681 + // Collapse quick questions if we have conversation history
1682 + if (hasQuickQuestions()) {
1683 + collapseQuickQuestions();
767 1684 }
768 - });
769 1685
770 - $chatBox.append($fragment);
771 - scrollToBottom(true);
1686 + // Update lastSeenMessageId after history loads
1687 + lastSeenMessageId = highestMessageId;
772 1688
773 - if (response.data.conversation.length > 0) {
774 - $('#mxchat-popular-questions').hide();
775 - }
776 -
777 - // Update lastSeenMessageId after history loads
778 - lastSeenMessageId = highestMessageId;
779 -
780 - // Only update chat mode if persistence is enabled
781 - if (chatPersistenceEnabled && response.data.conversation.length > 0) {
782 - var lastMessage = response.data.conversation[response.data.conversation.length - 1];
783 - if (lastMessage.role === 'agent') {
784 - updateChatModeIndicator('agent');
1689 + // Only update chat mode if persistence is enabled and we have messages
1690 + if (chatPersistenceEnabled) {
1691 + var lastMessage = response.data.conversation[response.data.conversation.length - 1];
1692 + if (lastMessage.role === 'agent') {
1693 + updateChatModeIndicator('agent');
1694 + }
1695 + }
1696 + } else {
1697 + // No conversation history - this is normal for new chats
1698 + //console.log("No conversation history found for this session - starting fresh chat.");
785 1699 }
1700 + } else {
1701 + // Response successful but no conversation data - this is also normal
1702 + //console.log("No conversation data in response - starting fresh chat.");
786 1703 }
787 1704 } else {
788 - console.warn("No conversation history found.");
1705 + // Only show error if the response explicitly indicates an error
1706 + console.warn("Failed to load chat history:", response.message || "Unknown error");
1707 + // Don't show error message to user for failed history loads
1708 + // Just start with a fresh chat instead
789 1709 }
790 1710 },
791 1711 error: function(xhr, status, error) {
792 - console.error("Error loading chat history:", status, error);
793 - appendMessage("bot", "Unable to load chat history.");
1712 + // Only log AJAX errors, don't show them to the user
1713 + console.error("AJAX error loading chat history:", status, error);
1714 + // Start with fresh chat - don't show error to user
794 1715 }
795 1716 });
796 1717 } else {
797 - console.warn("Chat persistence is disabled or no session ID found. Not loading history.");
1718 + //console.log("Chat persistence is disabled or no session ID found. Starting fresh chat.");
798 1719 }
799 1720 }
800 1721
801 -// Function to decode HTML entities
802 -function decodeHTMLEntities(text) {
803 - var textArea = document.createElement('textarea');
804 - textArea.innerHTML = text;
805 - return textArea.value;
806 -}
807 1722
808 -
809 -// Update formatCodeBlocks function
810 -function formatCodeBlocks(text) {
811 - // First handle raw PHP tags
812 - text = text.replace(/(<\?php[\s\S]*?\?>)/g, (match) => {
813 - return `<pre><code class="language-php">${escapeHtml(match)}</code></pre>`;
814 - });
815 -
816 - // Then handle code blocks with backticks
817 - text = text.replace(/```php5?\n([\s\S]+?)```/gi, (match, code) => {
818 - return `<pre><code class="language-php">${escapeHtml(code)}</code></pre>`;
819 - });
820 -
821 - return text;
822 -}
823 -
824 -// Update escapeHtml function to preserve existing code blocks
825 -function escapeHtml(unsafe) {
826 - // First check if it's already a code block
827 - if (unsafe.includes('<pre><code') || unsafe.includes('</code></pre>')) {
828 - return unsafe;
1723 + // ====================================
1724 + // FILE UPLOAD FUNCTIONALITY
1725 + // ====================================
1726 +
1727 + function addSafeEventListener(elementId, eventType, handler) {
1728 + const element = document.getElementById(elementId);
1729 + if (element) {
1730 + element.addEventListener(eventType, handler);
1731 + }
829 1732 }
830 1733
831 - return unsafe
832 - .replace(/&/g, "&amp;")
833 - .replace(/</g, "&lt;")
834 - .replace(/>/g, "&gt;")
835 - .replace(/"/g, "&quot;")
836 - .replace(/'/g, "&#039;");
837 -}
838 -// Utility function to escape HTML
839 -function escapeHtml(unsafe) {
840 - return unsafe
841 - .replace(/&/g, "&amp;")
842 - .replace(/</g, "&lt;")
843 - .replace(/>/g, "&gt;")
844 - .replace(/"/g, "&quot;")
845 - .replace(/'/g, "&#039;");
846 -}
847 -
848 -
849 -
850 -
851 -// Function to convert newlines, skipping preformatted text
852 -function convertNewlinesToBreaks(text) {
853 - // Split while preserving code blocks
854 - return text.split(/(<pre\b[^>]*>[\s\S]*?<\/pre>)/g).map(part => {
855 - if (part.startsWith('<pre')) return part;
856 - return part.replace(/(^|[^>])\n/g, '$1<br>');
857 - }).join('');
858 -}
859 -
860 -
861 -
862 -
863 - // Helper function to check if a string is an image HTML
864 - function isImageHtml(str) {
865 - return str.startsWith('<img') && str.endsWith('>');
1734 + function showActivePdf(filename) {
1735 + const container = document.getElementById('active-pdf-container');
1736 + const nameElement = document.getElementById('active-pdf-name');
1737 +
1738 + if (!container || !nameElement) {
1739 + //console.error('PDF container elements not found');
1740 + return;
1741 + }
1742 +
1743 + nameElement.textContent = filename;
1744 + container.style.display = 'flex';
866 1745 }
867 -
868 - // Function to remove thinking dots
869 - function removeThinkingDots() {
870 - $('.thinking-dots').closest('.temporary-message').remove();
1746 +
1747 + function showActiveWord(filename) {
1748 + const container = document.getElementById('active-word-container');
1749 + const nameElement = document.getElementById('active-word-name');
1750 +
1751 + if (!container || !nameElement) {
1752 + //console.error('Word document container elements not found');
1753 + return;
1754 + }
1755 +
1756 + nameElement.textContent = filename;
1757 + container.style.display = 'flex';
871 1758 }
872 -
873 - function isMobile() {
874 - // This can be a simple check, or more sophisticated detection of mobile devices
875 - return window.innerWidth <= 768; // Example threshold for mobile devices
1759 +
1760 + function removeActivePdf() {
1761 + const container = document.getElementById('active-pdf-container');
1762 + const nameElement = document.getElementById('active-pdf-name');
1763 +
1764 + if (!container || !nameElement || !activePdfFile) return;
1765 +
1766 + fetch(mxchatChat.ajax_url, {
1767 + method: 'POST',
1768 + headers: {
1769 + 'Content-Type': 'application/x-www-form-urlencoded',
1770 + },
1771 + body: new URLSearchParams({
1772 + 'action': 'mxchat_remove_pdf',
1773 + 'session_id': sessionId,
1774 + 'nonce': mxchatChat.nonce
1775 + })
1776 + })
1777 + .then(response => response.json())
1778 + .then(data => {
1779 + if (data.success) {
1780 + container.style.display = 'none';
1781 + nameElement.textContent = '';
1782 + activePdfFile = null;
1783 + appendMessage('bot', 'PDF removed.');
1784 + }
1785 + })
1786 + .catch(error => {
1787 + //console.error('Error removing PDF:', error);
1788 + });
876 1789 }
877 -
878 - function disableScroll() {
879 - if (isMobile()) {
880 - $('body').css('overflow', 'hidden');
1790 +
1791 + function removeActiveWord() {
1792 + const container = document.getElementById('active-word-container');
1793 + const nameElement = document.getElementById('active-word-name');
1794 +
1795 + if (!container || !nameElement || !activeWordFile) return;
1796 +
1797 + fetch(mxchatChat.ajax_url, {
1798 + method: 'POST',
1799 + headers: {
1800 + 'Content-Type': 'application/x-www-form-urlencoded',
1801 + },
1802 + body: new URLSearchParams({
1803 + 'action': 'mxchat_remove_word',
1804 + 'session_id': sessionId,
1805 + 'nonce': mxchatChat.nonce
1806 + })
1807 + })
1808 + .then(response => response.json())
1809 + .then(data => {
1810 + if (data.success) {
1811 + container.style.display = 'none';
1812 + nameElement.textContent = '';
1813 + activeWordFile = null;
1814 + appendMessage('bot', 'Word document removed.');
1815 + }
1816 + })
1817 + .catch(error => {
1818 + //console.error('Error removing Word document:', error);
1819 + });
1820 + }
1821 +
1822 + // ====================================
1823 + // CONSENT & COMPLIANCE (GDPR)
1824 + // ====================================
1825 +
1826 + function initializeChatVisibility() {
1827 + //console.log('Initializing chat visibility');
1828 + const complianzEnabled = mxchatChat.complianz_toggle === 'on' ||
1829 + mxchatChat.complianz_toggle === '1' ||
1830 + mxchatChat.complianz_toggle === 1;
1831 +
1832 + if (complianzEnabled && typeof cmplz_has_consent === "function" && typeof complianz !== 'undefined') {
1833 + // Initial check
1834 + checkConsentAndShowChat();
1835 +
1836 + // Listen for consent changes
1837 + $(document).on('cmplz_status_change', function(event) {
1838 + //console.log('Status change detected');
1839 + checkConsentAndShowChat();
1840 + });
1841 + } else {
1842 + // If Complianz is not enabled, always show
1843 + $('#floating-chatbot-button')
1844 + .css('display', 'flex')
1845 + .removeClass('hidden no-consent')
1846 + .fadeTo(500, 1);
1847 +
1848 + // Also check pre-chat message when Complianz is not enabled
1849 + checkPreChatDismissal();
881 1850 }
882 1851 }
883 1852
884 - function enableScroll() {
885 - if (isMobile()) {
886 - $('body').css('overflow', '');
1853 +
1854 + function checkConsentAndShowChat() {
1855 + var consentStatus = cmplz_has_consent('marketing');
1856 + var consentType = complianz.consenttype;
1857 +
1858 + //console.log('Checking consent:', {status: consentStatus,type: consentType});
1859 +
1860 + let $widget = $('#floating-chatbot-button');
1861 + let $chatbot = $('#floating-chatbot');
1862 + let $preChat = $('#pre-chat-message');
1863 +
1864 + if (consentStatus === true) {
1865 + //console.log('Consent granted - showing widget');
1866 + $widget
1867 + .removeClass('no-consent')
1868 + .css('display', 'flex')
1869 + .removeClass('hidden')
1870 + .fadeTo(500, 1);
1871 + $chatbot.removeClass('no-consent');
1872 +
1873 + // Show pre-chat message if not dismissed
1874 + checkPreChatDismissal();
1875 + } else {
1876 + //console.log('No consent - hiding widget');
1877 + $widget
1878 + .addClass('no-consent')
1879 + .fadeTo(500, 0, function() {
1880 + $(this)
1881 + .css('display', 'none')
1882 + .addClass('hidden');
1883 + });
1884 + $chatbot.addClass('no-consent');
1885 +
1886 + // Hide pre-chat message when no consent
1887 + $preChat.hide();
887 1888 }
888 1889 }
889 1890
890 -// Pre-chat dismissal check function (wrapped in a function for reuse)
1891 +
1892 + // ====================================
1893 + // PRE-CHAT MESSAGE HANDLING
1894 + // ====================================
1895 +
891 1896 function checkPreChatDismissal() {
892 1897 $.ajax({
893 1898 url: mxchatChat.ajax_url,
894 1899 type: 'POST',
@@ -903,98 +1908,13 @@
903 1908 $('#pre-chat-message').hide();
904 1909 }
905 1910 },
906 1911 error: function() {
907 - console.error('Failed to check pre-chat message dismissal status.');
1912 + //console.error('Failed to check pre-chat message dismissal status.');
908 1913 }
909 1914 });
910 1915 }
911 1916
912 - // Function to show the chatbot widget
913 -function showChatWidget() {
914 - // First ensure display is set
915 - $('#floating-chatbot-button').css('display', 'flex');
916 - // Then handle the fade
917 - $('#floating-chatbot-button').fadeTo(500, 1);
918 - // Force visibility
919 - $('#floating-chatbot-button').removeClass('hidden');
920 - //console.log('Showing widget');
921 -}
922 -
923 -// Function to hide the chatbot widget
924 -function hideChatWidget() {
925 - $('#floating-chatbot-button').css('display', 'none');
926 - $('#floating-chatbot-button').addClass('hidden');
927 - //console.log('Hiding widget');
928 -}
929 -
930 -function initializeChatVisibility() {
931 - //console.log('Initializing chat visibility');
932 - const complianzEnabled = mxchatChat.complianz_toggle === 'on' ||
933 - mxchatChat.complianz_toggle === '1' ||
934 - mxchatChat.complianz_toggle === 1;
935 -
936 - if (complianzEnabled && typeof cmplz_has_consent === "function" && typeof complianz !== 'undefined') {
937 - // Initial check
938 - checkConsentAndShowChat();
939 -
940 - // Listen for consent changes
941 - $(document).on('cmplz_status_change', function(event) {
942 - //console.log('Status change detected');
943 - checkConsentAndShowChat();
944 - });
945 - } else {
946 - // If Complianz is not enabled, always show
947 - $('#floating-chatbot-button')
948 - .css('display', 'flex')
949 - .removeClass('hidden no-consent')
950 - .fadeTo(500, 1);
951 -
952 - // Also check pre-chat message when Complianz is not enabled
953 - checkPreChatDismissal();
954 - }
955 -}
956 -
957 -
958 -
959 -function checkConsentAndShowChat() {
960 - var consentStatus = cmplz_has_consent('marketing');
961 - var consentType = complianz.consenttype;
962 -
963 - //console.log('Checking consent:', {status: consentStatus,type: consentType});
964 -
965 - let $widget = $('#floating-chatbot-button');
966 - let $chatbot = $('#floating-chatbot');
967 - let $preChat = $('#pre-chat-message');
968 -
969 - if (consentStatus === true) {
970 - //console.log('Consent granted - showing widget');
971 - $widget
972 - .removeClass('no-consent')
973 - .css('display', 'flex')
974 - .removeClass('hidden')
975 - .fadeTo(500, 1);
976 - $chatbot.removeClass('no-consent');
977 -
978 - // Show pre-chat message if not dismissed
979 - checkPreChatDismissal();
980 - } else {
981 - //console.log('No consent - hiding widget');
982 - $widget
983 - .addClass('no-consent')
984 - .fadeTo(500, 0, function() {
985 - $(this)
986 - .css('display', 'none')
987 - .addClass('hidden');
988 - });
989 - $chatbot.addClass('no-consent');
990 -
991 - // Hide pre-chat message when no consent
992 - $preChat.hide();
993 - }
994 -}
995 -
996 - // Function to dismiss pre-chat message for 24 hours
997 1917 function handlePreChatDismissal() {
998 1918 $('#pre-chat-message').fadeOut(200);
999 1919 $.ajax({
1000 1920 url: mxchatChat.ajax_url,
@@ -1006,20 +1926,64 @@
1006 1926 success: function() {
1007 1927 $('#pre-chat-message').hide();
1008 1928 },
1009 1929 error: function() {
1010 - console.error('Failed to dismiss pre-chat message.');
1930 + //console.error('Failed to dismiss pre-chat message.');
1011 1931 }
1012 1932 });
1013 1933 }
1014 1934
1015 - // Handle pre-chat message dismissal on button click
1016 - $(document).on('click', '.close-pre-chat-message', function(e) {
1017 - e.stopPropagation();
1018 - handlePreChatDismissal();
1019 - });
1020 1935
1021 - // Toggle chatbot visibility on floating button click
1936 + // ====================================
1937 + // UTILITY FUNCTIONS
1938 + // ====================================
1939 +
1940 + function copyToClipboard(text) {
1941 + var tempInput = $('<input>');
1942 + $('body').append(tempInput);
1943 + tempInput.val(text).select();
1944 + document.execCommand('copy');
1945 + tempInput.remove();
1946 + }
1947 +
1948 +
1949 + function isImageHtml(str) {
1950 + return str.startsWith('<img') && str.endsWith('>');
1951 + }
1952 +
1953 +
1954 + // ====================================
1955 + // EVENT HANDLERS & INITIALIZATION
1956 + // ====================================
1957 +
1958 +$(document).on('click', '.mxchat-popular-question', function () {
1959 + var question = $(this).text();
1960 +
1961 + // Append the question as if the user typed it
1962 + appendMessage("user", question);
1963 +
1964 + // Only collapse if there are questions
1965 + if (hasQuickQuestions()) {
1966 + collapseQuickQuestions();
1967 + }
1968 +
1969 + // Send the question to the server
1970 + sendMessageToChatbot(question);
1971 +});
1972 +
1973 +$(document).on('click', '.questions-toggle-btn', function(e) {
1974 + e.preventDefault();
1975 + e.stopPropagation();
1976 + expandQuickQuestions();
1977 +});
1978 +
1979 +$(document).on('click', '.questions-collapse-btn', function(e) {
1980 + e.preventDefault();
1981 + e.stopPropagation();
1982 + collapseQuickQuestions();
1983 +});
1984 +
1985 + // Chatbot visibility toggle handlers
1022 1986 $(document).on('click', '#floating-chatbot-button', function() {
1023 1987 var chatbot = $('#floating-chatbot');
1024 1988 if (chatbot.hasClass('hidden')) {
1025 1989 chatbot.removeClass('hidden').addClass('visible');
@@ -1033,16 +1997,15 @@
1033 1997 enableScroll();
1034 1998 checkPreChatDismissal();
1035 1999 }
1036 2000 });
1037 -
2001 +
1038 2002 $(document).on('click', '#exit-chat-button', function() {
1039 2003 $('#floating-chatbot').addClass('hidden').removeClass('visible');
1040 2004 $('#floating-chatbot-button').removeClass('hidden');
1041 2005 enableScroll();
1042 2006 });
1043 -
1044 - // Close pre-chat message on click
2007 +
1045 2008 $(document).on('click', '.close-pre-chat-message', function(e) {
1046 2009 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
1047 2010 $('#pre-chat-message').fadeOut(200, function() {
1048 2011 $(this).remove();
@@ -1047,530 +2010,803 @@
1047 2010 $('#pre-chat-message').fadeOut(200, function() {
1048 2011 $(this).remove();
1049 2012 });
1050 2013 });
2014 +
1051 2015
1052 - // Open chatbot when pre-chat message is clicked
1053 - $(document).on('click', '#pre-chat-message', function() {
1054 - var chatbot = $('#floating-chatbot');
1055 - if (chatbot.hasClass('hidden')) {
1056 - chatbot.removeClass('hidden').addClass('visible');
1057 - $('#floating-chatbot-button').addClass('hidden');
1058 - $('#pre-chat-message').fadeOut(250); // Hide pre-chat message
1059 - disableScroll(); // Disable scroll when chatbot opens
2016 + // PDF upload button handlers
2017 + if (document.getElementById('pdf-upload-btn')) {
2018 + document.getElementById('pdf-upload-btn').addEventListener('click', function() {
2019 + document.getElementById('pdf-upload').click();
2020 + });
2021 + }
2022 +
2023 + // Word upload button handlers
2024 + if (document.getElementById('word-upload-btn')) {
2025 + document.getElementById('word-upload-btn').addEventListener('click', function() {
2026 + document.getElementById('word-upload').click();
2027 + });
2028 + }
2029 +
2030 + // PDF file input change handler
2031 + addSafeEventListener('pdf-upload', 'change', async function(e) {
2032 + const file = e.target.files[0];
2033 +
2034 + if (!file || file.type !== 'application/pdf') {
2035 + alert('Please select a valid PDF file.');
2036 + return;
1060 2037 }
2038 +
2039 + if (!sessionId) {
2040 + //console.error('No session ID found');
2041 + alert('Error: No session ID found');
2042 + return;
2043 + }
2044 +
2045 + if (!mxchatChat || !mxchatChat.ajax_url || !mxchatChat.nonce) {
2046 + //console.error('mxchatChat not properly configured:', mxchatChat);
2047 + alert('Error: Ajax configuration missing');
2048 + return;
2049 + }
2050 +
2051 + // Disable buttons and show loading state
2052 + const uploadBtn = document.getElementById('pdf-upload-btn');
2053 + const sendBtn = document.getElementById('send-button');
2054 + const originalBtnContent = uploadBtn.innerHTML;
2055 +
2056 + try {
2057 + const formData = new FormData();
2058 + formData.append('action', 'mxchat_upload_pdf');
2059 + formData.append('pdf_file', file);
2060 + formData.append('session_id', sessionId);
2061 + formData.append('nonce', mxchatChat.nonce);
2062 +
2063 + uploadBtn.disabled = true;
2064 + sendBtn.disabled = true;
2065 + uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
2066 + <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
2067 + </svg>`;
2068 +
2069 + const response = await fetch(mxchatChat.ajax_url, {
2070 + method: 'POST',
2071 + body: formData
2072 + });
2073 +
2074 + const data = await response.json();
2075 +
2076 + if (data.success) {
2077 + // Hide popular questions if they exist
2078 + const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
2079 + if (hasQuickQuestions()) {
2080 + collapseQuickQuestions();
2081 + }
2082 +
2083 + // Show the active PDF name
2084 + showActivePdf(data.data.filename);
2085 +
2086 + appendMessage('bot', data.data.message);
2087 + scrollToBottom();
2088 + activePdfFile = data.data.filename;
2089 + } else {
2090 + //console.error('Upload failed:', data.data);
2091 + alert('Failed to upload PDF. Please try again.');
2092 + }
2093 + } catch (error) {
2094 + //console.error('Upload error:', error);
2095 + alert('Error uploading file. Please try again.');
2096 + } finally {
2097 + uploadBtn.disabled = false;
2098 + sendBtn.disabled = false;
2099 + uploadBtn.innerHTML = originalBtnContent;
2100 + this.value = ''; // Reset file input
2101 + }
1061 2102 });
2103 +
2104 + // Word file input change handler
2105 + addSafeEventListener('word-upload', 'change', async function(e) {
2106 + const file = e.target.files[0];
2107 +
2108 + if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
2109 + alert('Please select a valid Word document (.docx).');
2110 + return;
2111 + }
2112 +
2113 + if (!sessionId) {
2114 + //console.error('No session ID found');
2115 + alert('Error: No session ID found');
2116 + return;
2117 + }
2118 +
2119 + // Disable buttons and show loading state
2120 + const uploadBtn = document.getElementById('word-upload-btn');
2121 + const sendBtn = document.getElementById('send-button');
2122 + const originalBtnContent = uploadBtn.innerHTML;
2123 +
2124 + try {
2125 + const formData = new FormData();
2126 + formData.append('action', 'mxchat_upload_word');
2127 + formData.append('word_file', file);
2128 + formData.append('session_id', sessionId);
2129 + formData.append('nonce', mxchatChat.nonce);
2130 +
2131 + uploadBtn.disabled = true;
2132 + sendBtn.disabled = true;
2133 + uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
2134 + <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
2135 + </svg>`;
2136 +
2137 + const response = await fetch(mxchatChat.ajax_url, {
2138 + method: 'POST',
2139 + body: formData
2140 + });
2141 +
2142 + const data = await response.json();
2143 +
2144 + if (data.success) {
2145 + // Hide popular questions if they exist
2146 + const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
2147 + if (hasQuickQuestions()) {
2148 + collapseQuickQuestions();
2149 + }
2150 +
2151 + // Show the active Word document name
2152 + showActiveWord(data.data.filename);
2153 +
2154 + appendMessage('bot', data.data.message);
2155 + scrollToBottom();
2156 + activeWordFile = data.data.filename;
2157 + } else {
2158 + //console.error('Upload failed:', data.data);
2159 + alert('Failed to upload Word document. Please try again.');
2160 + }
2161 + } catch (error) {
2162 + //console.error('Upload error:', error);
2163 + alert('Error uploading file. Please try again.');
2164 + } finally {
2165 + uploadBtn.disabled = false;
2166 + sendBtn.disabled = false;
2167 + uploadBtn.innerHTML = originalBtnContent;
2168 + this.value = ''; // Reset file input
2169 + }
2170 + });
2171 +
2172 + // Remove button click handlers
2173 + document.getElementById('remove-pdf-btn')?.addEventListener('click', function(e) {
2174 + e.preventDefault();
2175 + e.stopPropagation();
2176 + removeActivePdf();
2177 + });
2178 +
2179 + document.getElementById('remove-word-btn')?.addEventListener('click', function(e) {
2180 + e.preventDefault();
2181 + e.stopPropagation();
2182 + removeActiveWord();
2183 + });
2184 +
2185 + // Window resize handlers
2186 + $(window).on('resize orientationchange', function() {
2187 + setFullHeight();
2188 + });
1062 2189
1063 - // If the chatbot is initially hidden, ensure the button is visible
1064 - if ($('#floating-chatbot').hasClass('hidden')) {
1065 - $('#floating-chatbot-button').removeClass('hidden');
1066 - }
1067 2190
1068 - function setFullHeight() {
1069 - var vh = $(window).innerHeight() * 0.01;
1070 - $(':root').css('--vh', vh + 'px');
2191 + // ====================================
2192 + // TOOLBAR & STYLING SETUP
2193 + // ====================================
2194 +
2195 + // Apply toolbar settings
2196 + if (mxchatChat.chat_toolbar_toggle === 'on') {
2197 + $('.chat-toolbar').show();
2198 + } else {
2199 + $('.chat-toolbar').hide();
1071 2200 }
1072 -
1073 - // Set the height when the page loads
1074 -
1075 -
1076 - // Set the height on resize and orientation change events
1077 - $(window).on('resize orientationchange', function() {
1078 - setFullHeight();
2201 +
2202 + // Apply toolbar icon colors
2203 + const toolbarElements = [
2204 + '#mxchat-chatbot .toolbar-btn svg',
2205 + '#mxchat-chatbot .active-pdf-name',
2206 + '#mxchat-chatbot .active-word-name',
2207 + '#mxchat-chatbot .remove-pdf-btn svg',
2208 + '#mxchat-chatbot .remove-word-btn svg',
2209 + '#mxchat-chatbot .toolbar-perplexity svg'
2210 + ];
2211 +
2212 + toolbarElements.forEach(selector => {
2213 + $(selector).css({
2214 + 'fill': toolbarIconColor,
2215 + 'stroke': toolbarIconColor,
2216 + 'color': toolbarIconColor
2217 + });
1079 2218 });
1080 2219
1081 2220
1082 - // Now handle the close button to dismiss the pre-chat message for 24 hours
1083 - var closeButton = document.querySelector('.close-pre-chat-message');
1084 - if (closeButton) {
1085 - closeButton.addEventListener('click', function() {
1086 - $('#pre-chat-message').fadeOut(200); // Hide the message
2221 +// ====================================
2222 +// EMAIL COLLECTION SETUP
2223 +// ====================================
2224 +// Only run email collection setup if it's enabled
2225 +if (mxchatChat && mxchatChat.enable_email_block === 'on') {
2226 + // Email collection form setup and handlers
2227 + const emailForm = document.getElementById('email-collection-form');
2228 + const emailBlocker = document.getElementById('email-blocker');
2229 + const chatbotWrapper = document.getElementById('chat-container');
1087 2230
1088 - // Send an AJAX request to set the transient flag for 24 hours
1089 - $.ajax({
1090 - url: mxchatChat.ajax_url,
1091 - type: 'POST',
1092 - data: {
1093 - action: 'mxchat_dismiss_pre_chat_message',
1094 - _ajax_nonce: mxchatChat.nonce
1095 - },
1096 - success: function() {
1097 - //console.log('Pre-chat message dismissed for 24 hours.');
2231 + if (emailForm && emailBlocker && chatbotWrapper) {
2232 +
2233 + // NEW: Check if server already provided the email state
2234 + if (mxchatChat.skip_email_check && mxchatChat.initial_email_state) {
2235 + //console.log('Using server-provided email state:', mxchatChat.initial_email_state);
2236 +
2237 + // Use server-provided state instead of making AJAX call
2238 + const emailState = mxchatChat.initial_email_state;
2239 + if (emailState.show_email_form) {
2240 + showEmailForm();
2241 + } else {
2242 + showChatContainer();
2243 + }
2244 +
2245 + // Continue to set up form handlers but skip the AJAX check
2246 + setupFormHandlers();
2247 +
2248 + } else {
2249 + // FALLBACK: Use existing AJAX approach if server state not available
2250 + //console.log('Server state not available, using AJAX fallback');
2251 + setupFormHandlers();
2252 + // Initialize email check with delay to prevent race conditions
2253 + setTimeout(checkSessionAndEmail, 100);
2254 + }
1098 2255
1099 - // Ensure the message is hidden after dismissal
1100 - $('#pre-chat-message').hide();
2256 + // Add loading state management
2257 + let isSubmitting = false;
2258 +
2259 + // Check if email exists for the current session
2260 + function checkSessionAndEmail() {
2261 + const sessionId = getChatSession();
2262 +
2263 + // Add timeout to prevent hanging
2264 + const controller = new AbortController();
2265 + const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
2266 +
2267 + fetch(mxchatChat.ajax_url, {
2268 + method: 'POST',
2269 + headers: {
2270 + 'Content-Type': 'application/x-www-form-urlencoded',
1101 2271 },
1102 - error: function() {
1103 - //console.error('Failed to dismiss pre-chat message.');
2272 + body: new URLSearchParams({
2273 + action: 'mxchat_check_email_provided',
2274 + session_id: sessionId,
2275 + nonce: mxchatChat.nonce,
2276 + }),
2277 + signal: controller.signal
2278 + })
2279 + .then((response) => {
2280 + clearTimeout(timeoutId);
2281 + if (!response.ok) {
2282 + throw new Error(`HTTP error! status: ${response.status}`);
1104 2283 }
2284 + return response.json();
2285 + })
2286 + .then((data) => {
2287 + if (data.success) {
2288 + if (data.data.logged_in || data.data.email) {
2289 + showChatContainer();
2290 + } else {
2291 + showEmailForm();
2292 + }
2293 + } else {
2294 + // On error, default to showing email form
2295 + showEmailForm();
2296 + }
2297 + })
2298 + .catch((error) => {
2299 + clearTimeout(timeoutId);
2300 + console.warn('Email check failed, defaulting to email form:', error);
2301 + showEmailForm();
1105 2302 });
1106 - });
1107 - }
2303 + }
1108 2304
2305 + // Optimized UI transition functions
2306 + function showEmailForm() {
2307 + emailBlocker.style.display = 'flex';
2308 + chatbotWrapper.style.display = 'none';
2309 + }
1109 2310
2311 + function showChatContainer() {
2312 + // Show chat immediately without delay
2313 + emailBlocker.style.display = 'none';
2314 + chatbotWrapper.style.display = 'flex';
2315 +
2316 + // Load chat history only after showing chat container
2317 + if (typeof loadChatHistory === 'function') {
2318 + loadChatHistory();
2319 + }
2320 + }
1110 2321
2322 + // Enhanced email validation
2323 + function isValidEmail(email) {
2324 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2325 + return emailRegex.test(email.trim()) && email.length <= 254; // RFC 5321 limit
2326 + }
1111 2327
1112 -// Event listener for Add to Cart button
1113 -$(document).on('click', '.mxchat-add-to-cart-button', function() {
1114 - var productId = $(this).data('product-id');
1115 - // Add a special prefix to indicate this is from button
1116 - appendMessage("user", "add to cart");
1117 - sendMessageToChatbot("!addtocart"); // Special command to indicate button click
1118 -});
2328 + // Enhanced name validation
2329 + function isValidName(name) {
2330 + return name && name.trim().length >= 2 && name.trim().length <= 100;
2331 + }
1119 2332
2333 + // Show loading state with spinner
2334 + function setSubmissionState(loading) {
2335 + const submitButton = document.getElementById('email-submit-button');
2336 + const emailInput = document.getElementById('user-email');
2337 + const nameInput = document.getElementById('user-name');
2338 +
2339 + if (loading) {
2340 + isSubmitting = true;
2341 + submitButton.disabled = true;
2342 + emailInput.disabled = true;
2343 + if (nameInput) nameInput.disabled = true;
2344 +
2345 + // Store original content and add spinner
2346 + if (!submitButton.getAttribute('data-original-html')) {
2347 + submitButton.setAttribute('data-original-html', submitButton.innerHTML);
2348 + }
2349 +
2350 + // Add loading spinner while keeping original text
2351 + const originalText = submitButton.textContent;
2352 + submitButton.innerHTML = `
2353 + <svg class="email-spinner" style="width: 16px; height: 16px; margin-right: 8px; animation: spin 1s linear infinite;" viewBox="0 0 24 24">
2354 + <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" stroke-dasharray="31.416" stroke-dashoffset="31.416">
2355 + <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite"/>
2356 + <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite"/>
2357 + </circle>
2358 + </svg>
2359 + ${originalText}
2360 + `;
2361 +
2362 + submitButton.style.opacity = '0.8';
2363 + } else {
2364 + isSubmitting = false;
2365 + submitButton.disabled = false;
2366 + emailInput.disabled = false;
2367 + if (nameInput) nameInput.disabled = false;
2368 +
2369 + // Restore original content
2370 + const originalHtml = submitButton.getAttribute('data-original-html');
2371 + if (originalHtml) {
2372 + submitButton.innerHTML = originalHtml;
2373 + }
2374 +
2375 + submitButton.style.opacity = '1';
2376 + }
2377 + }
1120 2378
1121 -if (document.getElementById('pdf-upload-btn')) {
1122 - document.getElementById('pdf-upload-btn').addEventListener('click', function() {
1123 - document.getElementById('pdf-upload').click();
1124 - });
1125 -}
2379 + // Error display functions
2380 + function showEmailError(message) {
2381 + clearEmailError();
2382 +
2383 + const errorDiv = document.createElement('div');
2384 + errorDiv.className = 'email-error';
2385 + errorDiv.style.cssText = `
2386 + color: #e74c3c;
2387 + font-size: 12px;
2388 + margin-top: 8px;
2389 + padding: 4px 0;
2390 + animation: fadeInError 0.3s ease;
2391 + `;
2392 + errorDiv.textContent = message;
2393 +
2394 + // Add CSS animation if not already present
2395 + if (!document.getElementById('email-error-styles')) {
2396 + const style = document.createElement('style');
2397 + style.id = 'email-error-styles';
2398 + style.textContent = `
2399 + @keyframes fadeInError {
2400 + from { opacity: 0; transform: translateY(-5px); }
2401 + to { opacity: 1; transform: translateY(0); }
2402 + }
2403 + .email-input-shake {
2404 + animation: shake 0.5s ease-in-out;
2405 + }
2406 + @keyframes shake {
2407 + 0%, 100% { transform: translateX(0); }
2408 + 25% { transform: translateX(-5px); }
2409 + 75% { transform: translateX(5px); }
2410 + }
2411 + `;
2412 + document.head.appendChild(style);
2413 + }
2414 +
2415 + emailForm.appendChild(errorDiv);
2416 +
2417 + // Add shake animation to inputs
2418 + const emailInput = document.getElementById('user-email');
2419 + const nameInput = document.getElementById('user-name');
2420 +
2421 + if (emailInput) {
2422 + emailInput.classList.add('email-input-shake');
2423 + setTimeout(() => {
2424 + emailInput.classList.remove('email-input-shake');
2425 + }, 500);
2426 + }
2427 +
2428 + if (nameInput) {
2429 + nameInput.classList.add('email-input-shake');
2430 + setTimeout(() => {
2431 + nameInput.classList.remove('email-input-shake');
2432 + }, 500);
2433 + }
2434 + }
1126 2435
1127 -if (document.getElementById('word-upload-btn')) {
1128 - document.getElementById('word-upload-btn').addEventListener('click', function() {
1129 - document.getElementById('word-upload').click();
1130 - });
1131 -}
2436 + function clearEmailError() {
2437 + const existingErrors = emailForm.querySelectorAll('.email-error');
2438 + existingErrors.forEach(error => error.remove());
2439 + }
1132 2440
1133 -// PDF file input change handler
1134 -document.getElementById('pdf-upload').addEventListener('change', async function(e) {
1135 - const file = e.target.files[0];
2441 + // NEW: Separate function to set up all form handlers
2442 + function setupFormHandlers() {
2443 + // Add CSS for spinner animation if not already present
2444 + if (!document.getElementById('email-spinner-styles')) {
2445 + const style = document.createElement('style');
2446 + style.id = 'email-spinner-styles';
2447 + style.textContent = `
2448 + @keyframes spin {
2449 + from { transform: rotate(0deg); }
2450 + to { transform: rotate(360deg); }
2451 + }
2452 + .email-spinner {
2453 + display: inline-block;
2454 + vertical-align: middle;
2455 + }
2456 + `;
2457 + document.head.appendChild(style);
2458 + }
1136 2459
1137 - if (!file || file.type !== 'application/pdf') {
1138 - alert('Please select a valid PDF file.');
1139 - return;
1140 - }
2460 + // Handle email form submission with improved error handling
2461 + emailForm.addEventListener('submit', function (event) {
2462 + event.preventDefault();
2463 +
2464 + // Prevent double submission
2465 + if (isSubmitting) {
2466 + return;
2467 + }
1141 2468
1142 - if (!sessionId) {
1143 - console.error('No session ID found');
1144 - alert('Error: No session ID found');
1145 - return;
1146 - }
2469 + const userEmail = document.getElementById('user-email').value.trim();
2470 + const nameInput = document.getElementById('user-name');
2471 + const userName = nameInput ? nameInput.value.trim() : '';
2472 + const sessionId = getChatSession();
1147 2473
1148 - if (!mxchatChat || !mxchatChat.ajax_url || !mxchatChat.nonce) {
1149 - console.error('mxchatChat not properly configured:', mxchatChat);
1150 - alert('Error: Ajax configuration missing');
1151 - return;
1152 - }
2474 + // Validate email before submission
2475 + if (!userEmail) {
2476 + showEmailError('Please enter your email address.');
2477 + return;
2478 + }
1153 2479
1154 - // Disable buttons and show loading state
1155 - const uploadBtn = document.getElementById('pdf-upload-btn');
1156 - const sendBtn = document.getElementById('send-button');
1157 - const originalBtnContent = uploadBtn.innerHTML;
2480 + if (!isValidEmail(userEmail)) {
2481 + showEmailError('Please enter a valid email address.');
2482 + return;
2483 + }
1158 2484
1159 - try {
1160 - const formData = new FormData();
1161 - formData.append('action', 'mxchat_upload_pdf');
1162 - formData.append('pdf_file', file);
1163 - formData.append('session_id', sessionId);
1164 - formData.append('nonce', mxchatChat.nonce);
2485 + // Validate name if field exists
2486 + if (nameInput && !isValidName(userName)) {
2487 + showEmailError('Please enter a valid name (2-100 characters).');
2488 + return;
2489 + }
1165 2490
1166 - uploadBtn.disabled = true;
1167 - sendBtn.disabled = true;
1168 - uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1169 - <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1170 - </svg>`;
2491 + // Clear any existing errors
2492 + clearEmailError();
2493 + setSubmissionState(true);
1171 2494
1172 - const response = await fetch(mxchatChat.ajax_url, {
1173 - method: 'POST',
1174 - body: formData
1175 - });
2495 + // Add timeout for submission
2496 + const controller = new AbortController();
2497 + const timeoutId = setTimeout(() => {
2498 + controller.abort();
2499 + setSubmissionState(false);
2500 + showEmailError('Request timed out. Please try again.');
2501 + }, 15000); // 15 second timeout
1176 2502
1177 - const data = await response.json();
2503 + // Prepare form data with optional name
2504 + const formData = new URLSearchParams({
2505 + action: 'mxchat_handle_save_email_and_response',
2506 + email: userEmail,
2507 + session_id: sessionId,
2508 + nonce: mxchatChat.nonce,
2509 + });
1178 2510
1179 - if (data.success) {
1180 - // Hide popular questions if they exist
1181 - const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1182 - if (popularQuestionsContainer) {
1183 - popularQuestionsContainer.style.display = 'none';
1184 - }
2511 + // Add name to form data if provided
2512 + if (userName) {
2513 + formData.append('name', userName);
2514 + }
1185 2515
1186 - // Show the active PDF name
1187 - showActivePdf(data.data.filename);
1188 -
1189 - appendMessage('bot', data.data.message);
1190 - scrollToBottom();
1191 - activePdfFile = data.data.filename;
1192 - } else {
1193 - console.error('Upload failed:', data.data);
1194 - alert('Failed to upload PDF. Please try again.');
1195 - }
1196 - } catch (error) {
1197 - console.error('Upload error:', error);
1198 - alert('Error uploading file. Please try again.');
1199 - } finally {
1200 - uploadBtn.disabled = false;
1201 - sendBtn.disabled = false;
1202 - uploadBtn.innerHTML = originalBtnContent;
1203 - this.value = ''; // Reset file input
1204 - }
1205 -});
2516 + fetch(mxchatChat.ajax_url, {
2517 + method: 'POST',
2518 + headers: {
2519 + 'Content-Type': 'application/x-www-form-urlencoded',
2520 + },
2521 + body: formData,
2522 + signal: controller.signal
2523 + })
2524 + .then((response) => {
2525 + clearTimeout(timeoutId);
2526 + if (!response.ok) {
2527 + throw new Error(`HTTP error! status: ${response.status}`);
2528 + }
2529 + return response.json();
2530 + })
2531 + .then((data) => {
2532 + setSubmissionState(false);
2533 +
2534 + if (data.success) {
2535 + // Show chat immediately
2536 + showChatContainer();
1206 2537
1207 -// Word file input change handler
1208 -document.getElementById('word-upload').addEventListener('change', async function(e) {
1209 - const file = e.target.files[0];
2538 + // Handle bot response if provided
2539 + if (data.message && typeof appendMessage === 'function') {
2540 + setTimeout(() => {
2541 + appendMessage('bot', data.message);
2542 + if (typeof scrollToBottom === 'function') {
2543 + scrollToBottom();
2544 + }
2545 + }, 100);
2546 + }
2547 + } else {
2548 + showEmailError(data.message || 'Failed to save email. Please try again.');
2549 + }
2550 + })
2551 + .catch((error) => {
2552 + clearTimeout(timeoutId);
2553 + setSubmissionState(false);
2554 +
2555 + if (error.name === 'AbortError') {
2556 + showEmailError('Request timed out. Please try again.');
2557 + } else {
2558 + console.error('Email submission error:', error);
2559 + showEmailError('An error occurred. Please try again.');
2560 + }
2561 + });
2562 + });
1210 2563
1211 - if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
1212 - alert('Please select a valid Word document (.docx).');
1213 - return;
1214 - }
2564 + // Real-time email validation
2565 + const emailInput = document.getElementById('user-email');
2566 + if (emailInput) {
2567 + let validationTimeout;
2568 +
2569 + emailInput.addEventListener('input', function() {
2570 + // Clear previous validation timeout
2571 + if (validationTimeout) {
2572 + clearTimeout(validationTimeout);
2573 + }
2574 +
2575 + // Debounce validation
2576 + validationTimeout = setTimeout(() => {
2577 + const email = this.value.trim();
2578 + clearEmailError();
2579 +
2580 + if (email && !isValidEmail(email)) {
2581 + showEmailError('Please enter a valid email address.');
2582 + }
2583 + }, 500);
2584 + });
1215 2585
1216 - if (!sessionId) {
1217 - console.error('No session ID found');
1218 - alert('Error: No session ID found');
1219 - return;
1220 - }
2586 + // Handle Enter key
2587 + emailInput.addEventListener('keypress', function(e) {
2588 + if (e.key === 'Enter' && !isSubmitting) {
2589 + emailForm.dispatchEvent(new Event('submit'));
2590 + }
2591 + });
2592 + }
1221 2593
1222 - // Disable buttons and show loading state
1223 - const uploadBtn = document.getElementById('word-upload-btn');
1224 - const sendBtn = document.getElementById('send-button');
1225 - const originalBtnContent = uploadBtn.innerHTML;
2594 + // Real-time name validation
2595 + const nameInput = document.getElementById('user-name');
2596 + if (nameInput) {
2597 + let nameValidationTimeout;
2598 +
2599 + nameInput.addEventListener('input', function() {
2600 + // Clear previous validation timeout
2601 + if (nameValidationTimeout) {
2602 + clearTimeout(nameValidationTimeout);
2603 + }
2604 +
2605 + // Debounce validation
2606 + nameValidationTimeout = setTimeout(() => {
2607 + const name = this.value.trim();
2608 + clearEmailError();
2609 +
2610 + if (name && !isValidName(name)) {
2611 + showEmailError('Name must be between 2 and 100 characters.');
2612 + }
2613 + }, 500);
2614 + });
1226 2615
1227 - try {
1228 - const formData = new FormData();
1229 - formData.append('action', 'mxchat_upload_word');
1230 - formData.append('word_file', file);
1231 - formData.append('session_id', sessionId);
1232 - formData.append('nonce', mxchatChat.nonce);
1233 -
1234 - uploadBtn.disabled = true;
1235 - sendBtn.disabled = true;
1236 - uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1237 - <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1238 - </svg>`;
1239 -
1240 - const response = await fetch(mxchatChat.ajax_url, {
1241 - method: 'POST',
1242 - body: formData
2616 + // Handle Enter key
2617 + nameInput.addEventListener('keypress', function(e) {
2618 + if (e.key === 'Enter' && !isSubmitting) {
2619 + emailForm.dispatchEvent(new Event('submit'));
2620 + }
2621 + });
2622 + }
2623 + }
2624 +
2625 + } else {
2626 + console.error('Email collection is enabled but essential elements are missing:', {
2627 + emailForm: !!emailForm,
2628 + emailBlocker: !!emailBlocker,
2629 + chatbotWrapper: !!chatbotWrapper
1243 2630 });
2631 + }
2632 +}
1244 2633
1245 - const data = await response.json();
1246 2634
1247 - if (data.success) {
1248 - // Hide popular questions if they exist
1249 - const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1250 - if (popularQuestionsContainer) {
1251 - popularQuestionsContainer.style.display = 'none';
1252 - }
1253 -
1254 - // Show the active Word document name
1255 - showActiveWord(data.data.filename);
1256 -
1257 - appendMessage('bot', data.data.message);
1258 - scrollToBottom();
1259 - activeWordFile = data.data.filename;
1260 - } else {
1261 - console.error('Upload failed:', data.data);
1262 - alert('Failed to upload Word document. Please try again.');
2635 + // Open chatbot when pre-chat message is clicked
2636 + $(document).on('click', '#pre-chat-message', function() {
2637 + var chatbot = $('#floating-chatbot');
2638 + if (chatbot.hasClass('hidden')) {
2639 + chatbot.removeClass('hidden').addClass('visible');
2640 + $('#floating-chatbot-button').addClass('hidden');
2641 + $('#pre-chat-message').fadeOut(250); // Hide pre-chat message
2642 + disableScroll(); // Disable scroll when chatbot opens
1263 2643 }
1264 - } catch (error) {
1265 - console.error('Upload error:', error);
1266 - alert('Error uploading file. Please try again.');
1267 - } finally {
1268 - uploadBtn.disabled = false;
1269 - sendBtn.disabled = false;
1270 - uploadBtn.innerHTML = originalBtnContent;
1271 - this.value = ''; // Reset file input
1272 - }
1273 -});
2644 + });
1274 2645
1275 -// Function to show active PDF name in toolbar
1276 -function showActivePdf(filename) {
1277 - const container = document.getElementById('active-pdf-container');
1278 - const nameElement = document.getElementById('active-pdf-name');
1279 -
1280 - if (!container || !nameElement) {
1281 - console.error('PDF container elements not found');
1282 - return;
1283 - }
2646 + var closeButton = document.querySelector('.close-pre-chat-message');
2647 + if (closeButton) {
2648 + closeButton.addEventListener('click', function() {
2649 + $('#pre-chat-message').fadeOut(200); // Hide the message
1284 2650
1285 - nameElement.textContent = filename;
1286 - container.style.display = 'flex';
1287 -}
2651 + // Send an AJAX request to set the transient flag for 24 hours
2652 + $.ajax({
2653 + url: mxchatChat.ajax_url,
2654 + type: 'POST',
2655 + data: {
2656 + action: 'mxchat_dismiss_pre_chat_message',
2657 + _ajax_nonce: mxchatChat.nonce
2658 + },
2659 + success: function() {
2660 + //console.log('Pre-chat message dismissed for 24 hours.');
1288 2661
1289 -// Function to show active Word document name in toolbar
1290 -function showActiveWord(filename) {
1291 - const container = document.getElementById('active-word-container');
1292 - const nameElement = document.getElementById('active-word-name');
2662 + // Ensure the message is hidden after dismissal
2663 + $('#pre-chat-message').hide();
2664 + },
2665 + error: function() {
2666 + ////console.error('Failed to dismiss pre-chat message.');
2667 + }
2668 + });
2669 + });
2670 + }
1293 2671
1294 - if (!container || !nameElement) {
1295 - console.error('Word document container elements not found');
1296 - return;
1297 - }
1298 -
1299 - nameElement.textContent = filename;
1300 - container.style.display = 'flex';
1301 -}
1302 -
1303 -// Function to remove active PDF
1304 -function removeActivePdf() {
1305 - const container = document.getElementById('active-pdf-container');
1306 - const nameElement = document.getElementById('active-pdf-name');
1307 2672
1308 - if (!container || !nameElement || !activePdfFile) return;
1309 -
1310 - fetch(mxchatChat.ajax_url, {
1311 - method: 'POST',
1312 - headers: {
1313 - 'Content-Type': 'application/x-www-form-urlencoded',
1314 - },
1315 - body: new URLSearchParams({
1316 - 'action': 'mxchat_remove_pdf',
1317 - 'session_id': sessionId,
1318 - 'nonce': mxchatChat.nonce
1319 - })
1320 - })
1321 - .then(response => response.json())
1322 - .then(data => {
1323 - if (data.success) {
1324 - container.style.display = 'none';
1325 - nameElement.textContent = '';
1326 - activePdfFile = null;
1327 - appendMessage('bot', 'PDF removed.');
1328 - }
1329 - })
1330 - .catch(error => {
1331 - console.error('Error removing PDF:', error);
1332 - });
2673 +function hasQuickQuestions() {
2674 + const questionButtons = document.querySelectorAll('#mxchat-popular-questions .mxchat-popular-question');
2675 + return questionButtons.length > 0;
1333 2676 }
1334 2677
1335 -// Function to remove active Word document
1336 -function removeActiveWord() {
1337 - const container = document.getElementById('active-word-container');
1338 - const nameElement = document.getElementById('active-word-name');
1339 -
1340 - if (!container || !nameElement || !activeWordFile) return;
1341 -
1342 - fetch(mxchatChat.ajax_url, {
1343 - method: 'POST',
1344 - headers: {
1345 - 'Content-Type': 'application/x-www-form-urlencoded',
1346 - },
1347 - body: new URLSearchParams({
1348 - 'action': 'mxchat_remove_word',
1349 - 'session_id': sessionId,
1350 - 'nonce': mxchatChat.nonce
1351 - })
1352 - })
1353 - .then(response => response.json())
1354 - .then(data => {
1355 - if (data.success) {
1356 - container.style.display = 'none';
1357 - nameElement.textContent = '';
1358 - activeWordFile = null;
1359 - appendMessage('bot', 'Word document removed.');
2678 +function collapseQuickQuestions() {
2679 + const questionsContainer = document.getElementById('mxchat-popular-questions');
2680 + if (questionsContainer && hasQuickQuestions()) {
2681 + questionsContainer.classList.add('collapsed');
2682 + questionsContainer.classList.add('has-been-collapsed');
2683 + try {
2684 + sessionStorage.setItem('mxchat_questions_collapsed', 'true');
2685 + sessionStorage.setItem('mxchat_questions_has_been_collapsed', 'true');
2686 + } catch (e) {
2687 + // Ignore if sessionStorage is not available
1360 2688 }
1361 - })
1362 - .catch(error => {
1363 - console.error('Error removing Word document:', error);
1364 - });
2689 + }
1365 2690 }
1366 2691
1367 -// Add remove button click handlers
1368 -document.getElementById('remove-pdf-btn')?.addEventListener('click', function(e) {
1369 - e.preventDefault();
1370 - e.stopPropagation();
1371 - removeActivePdf();
1372 -});
1373 -
1374 -document.getElementById('remove-word-btn')?.addEventListener('click', function(e) {
1375 - e.preventDefault();
1376 - e.stopPropagation();
1377 - removeActiveWord();
1378 -});
1379 -
1380 -// Check initial document status
1381 -function checkInitialDocumentStatus() {
1382 - if (!sessionId) return;
1383 -
1384 - // Check PDF status
1385 - fetch(mxchatChat.ajax_url, {
1386 - method: 'POST',
1387 - headers: {
1388 - 'Content-Type': 'application/x-www-form-urlencoded',
1389 - },
1390 - body: new URLSearchParams({
1391 - 'action': 'mxchat_check_pdf_status',
1392 - 'session_id': sessionId,
1393 - 'nonce': mxchatChat.nonce
1394 - })
1395 - })
1396 - .then(response => response.json())
1397 - .then(data => {
1398 - if (data.success && data.data.filename) {
1399 - showActivePdf(data.data.filename);
1400 - activePdfFile = data.data.filename;
2692 +function expandQuickQuestions() {
2693 + const questionsContainer = document.getElementById('mxchat-popular-questions');
2694 + if (questionsContainer && hasQuickQuestions()) {
2695 + questionsContainer.classList.remove('collapsed');
2696 + try {
2697 + sessionStorage.setItem('mxchat_questions_collapsed', 'false');
2698 + } catch (e) {
2699 + // Ignore if sessionStorage is not available
1401 2700 }
1402 - })
1403 - .catch(error => {
1404 - console.error('Error checking PDF status:', error);
1405 - });
1406 -
1407 - // Check Word document status
1408 - fetch(mxchatChat.ajax_url, {
1409 - method: 'POST',
1410 - headers: {
1411 - 'Content-Type': 'application/x-www-form-urlencoded',
1412 - },
1413 - body: new URLSearchParams({
1414 - 'action': 'mxchat_check_word_status',
1415 - 'session_id': sessionId,
1416 - 'nonce': mxchatChat.nonce
1417 - })
1418 - })
1419 - .then(response => response.json())
1420 - .then(data => {
1421 - if (data.success && data.data.filename) {
1422 - showActiveWord(data.data.filename);
1423 - activeWordFile = data.data.filename;
1424 - }
1425 - })
1426 - .catch(error => {
1427 - console.error('Error checking Word document status:', error);
1428 - });
2701 + }
1429 2702 }
1430 2703
1431 -// Apply toolbar settings
1432 -if (mxchatChat.chat_toolbar_toggle === 'on') {
1433 - $('.chat-toolbar').show();
1434 -} else {
1435 - $('.chat-toolbar').hide();
1436 -}
1437 -
1438 -// Initialize on page load
1439 -document.addEventListener('DOMContentLoaded', function() {
1440 - checkInitialDocumentStatus();
1441 -});
1442 -
1443 -// Style all toolbar elements
1444 -const toolbarElements = [
1445 - '#mxchat-chatbot .toolbar-btn svg',
1446 - '#mxchat-chatbot .active-pdf-name',
1447 - '#mxchat-chatbot .active-word-name',
1448 - '#mxchat-chatbot .remove-pdf-btn svg',
1449 - '#mxchat-chatbot .remove-word-btn svg'
1450 -];
1451 -$(toolbarElements.join(', ')).css({
1452 - 'fill': toolbarIconColor,
1453 - 'color': toolbarIconColor
1454 -});
1455 -
1456 -
1457 -
1458 -// Ensure essential elements are defined
1459 -const emailForm = document.getElementById('email-collection-form');
1460 -const emailBlocker = document.getElementById('email-blocker');
1461 -const chatbotWrapper = document.getElementById('chat-container');
1462 -
1463 -if (emailForm && emailBlocker && chatbotWrapper) {
1464 - // Check if email exists for the current session
1465 - function checkSessionAndEmail() {
1466 - const sessionId = getChatSession();
1467 - //console.log("[DEBUG JS] checkSessionAndEmail -> sessionId:", sessionId);
1468 -
1469 - fetch(mxchatChat.ajax_url, {
1470 - method: 'POST',
1471 - headers: {
1472 - 'Content-Type': 'application/x-www-form-urlencoded',
1473 - },
1474 - body: new URLSearchParams({
1475 - action: 'mxchat_check_email_provided',
1476 - session_id: sessionId,
1477 - nonce: mxchatChat.nonce,
1478 - }),
1479 - })
1480 - .then((response) => response.json())
1481 - .then((data) => {
1482 - //console.log("[DEBUG JS] mxchat_check_email_provided response:", data);
2704 +function checkQuickQuestionsState() {
2705 + if (!hasQuickQuestions()) {
2706 + return; // Don't do anything if no questions exist
2707 + }
2708 +
2709 + try {
2710 + const isCollapsed = sessionStorage.getItem('mxchat_questions_collapsed');
2711 + const hasBeenCollapsed = sessionStorage.getItem('mxchat_questions_has_been_collapsed');
1483 2712
1484 - if (data.success) {
1485 - if (data.data.logged_in) {
1486 - //console.log("[DEBUG JS] User is logged in. Hiding email form.");
1487 - emailBlocker.style.display = 'none';
1488 - chatbotWrapper.style.display = 'flex';
1489 - } else if (data.data.email) {
1490 - //console.log("[DEBUG JS] Email found for session. Hiding email form.");
1491 - emailBlocker.style.display = 'none';
1492 - chatbotWrapper.style.display = 'flex';
1493 - } else {
1494 - //console.log("[DEBUG JS] No email provided. Showing email form.");
1495 - emailBlocker.style.display = 'flex';
1496 - chatbotWrapper.style.display = 'none';
2713 + const questionsContainer = document.getElementById('mxchat-popular-questions');
2714 + if (questionsContainer) {
2715 + if (hasBeenCollapsed === 'true') {
2716 + questionsContainer.classList.add('has-been-collapsed');
1497 2717 }
1498 - } else {
1499 - //console.log("[DEBUG JS] Error or no data received. Showing email form.");
1500 - emailBlocker.style.display = 'flex';
1501 - chatbotWrapper.style.display = 'none';
2718 + if (isCollapsed === 'true') {
2719 + questionsContainer.classList.add('collapsed');
2720 + }
1502 2721 }
1503 - })
1504 - .catch((error) => {
1505 - // console.error("[DEBUG JS] Fetch error -> forcing email form visible:", error);
1506 - emailBlocker.style.display = 'flex';
1507 - chatbotWrapper.style.display = 'none';
1508 - });
2722 + } catch (e) {
2723 + // Ignore if sessionStorage is not available
2724 + }
1509 2725 }
1510 2726
1511 -
1512 -
1513 - // Handle email form submission
1514 - emailForm.addEventListener('submit', function (event) {
1515 - event.preventDefault();
1516 - const userEmail = document.getElementById('user-email').value;
1517 - const sessionId = getChatSession();
1518 -
1519 - if (userEmail) {
1520 - fetch(mxchatChat.ajax_url, {
1521 - method: 'POST',
1522 - headers: {
1523 - 'Content-Type': 'application/x-www-form-urlencoded',
2727 +// NEW: Global delegation for dynamically added links as fallback
2728 +$(document).on('click', '#chat-box a[href]:not([data-tracked])', function(e) {
2729 + const $link = $(this);
2730 + const messageDiv = $link.closest('.bot-message, .agent-message');
2731 +
2732 + // Only process bot/agent message links
2733 + if (messageDiv.length > 0) {
2734 + const originalHref = $link.attr('href');
2735 +
2736 + if (originalHref && (originalHref.startsWith('http://') || originalHref.startsWith('https://'))) {
2737 + e.preventDefault();
2738 + e.stopPropagation();
2739 +
2740 + // Mark as tracked
2741 + $link.attr('data-tracked', 'true');
2742 +
2743 + // Get message context from the message div
2744 + const messageText = messageDiv.text().substring(0, 200);
2745 +
2746 + $.ajax({
2747 + url: mxchatChat.ajax_url,
2748 + type: 'POST',
2749 + data: {
2750 + action: 'mxchat_track_url_click',
2751 + session_id: getChatSession(),
2752 + url: originalHref,
2753 + message_context: messageText,
2754 + nonce: mxchatChat.nonce
1524 2755 },
1525 - body: new URLSearchParams({
1526 - action: 'mxchat_handle_save_email_and_response',
1527 - email: userEmail,
1528 - session_id: sessionId,
1529 - nonce: mxchatChat.nonce,
1530 - }),
1531 - })
1532 - .then((response) => response.json())
1533 - .then((data) => {
1534 - //console.log('Backend response:', data);
1535 - if (data.success) {
1536 - //console.log('Email saved successfully:', userEmail);
1537 - emailBlocker.style.display = 'none';
1538 - chatbotWrapper.style.display = 'flex';
1539 -
1540 - // Optionally handle bot response
1541 - if (data.message) {
1542 - appendMessage('bot', data.message);
1543 - scrollToBottom();
1544 - }
2756 + complete: function() {
2757 + if ($link.attr('target') === '_blank' || linkTarget === '_blank') {
2758 + window.open(originalHref, '_blank');
1545 2759 } else {
1546 - console.error('Error saving email:', data.message || 'Unknown error');
2760 + window.location.href = originalHref;
1547 2761 }
1548 - })
1549 - .catch((error) => {
1550 - console.error('AJAX error:', error);
1551 - });
2762 + }
2763 + });
2764 +
2765 + return false;
1552 2766 }
1553 - });
2767 + }
2768 +});
1554 2769
1555 - // Check session and email status on page load
1556 - checkSessionAndEmail();
1557 -} else {
1558 - console.error('Essential elements for email handling are missing.');
2770 +
2771 +
2772 +
2773 +// ====================================
2774 +// MAIN INITIALIZATION
2775 +// ====================================
2776 +
2777 +if ($('#floating-chatbot').hasClass('hidden')) {
2778 + $('#floating-chatbot-button').removeClass('hidden');
1559 2779 }
2780 +// Initialize when document is ready
2781 +setFullHeight();
2782 +initializeChatVisibility();
2783 +loadChatHistory();
2784 +trackOriginatingPage();
1560 2785
2786 +// Make functions globally available for add-ons
2787 +window.hasQuickQuestions = hasQuickQuestions;
2788 +window.collapseQuickQuestions = collapseQuickQuestions;
2789 +window.appendMessage = appendMessage;
2790 +window.appendThinkingMessage = appendThinkingMessage;
2791 +window.scrollToBottom = scrollToBottom;
2792 +window.scrollElementToTop = scrollElementToTop;
2793 +window.replaceLastMessage = replaceLastMessage;
2794 +window.callMxChat = callMxChat;
2795 +window.callMxChatStream = callMxChatStream;
2796 +window.shouldUseStreaming = shouldUseStreaming;
2797 +window.getChatSession = getChatSession;
2798 +window.getPageContext = getPageContext;
2799 +window.updateStreamingMessage = updateStreamingMessage;
1561 2800
1562 -// Initialize when document is ready
1563 -$(document).ready(function() {
1564 - setFullHeight();
1565 - initializeChatVisibility();
1566 - loadChatHistory();
2801 +}); // End of jQuery ready
1567 2802
1568 -});
1569 2803
1570 -});
2804 +// ====================================
2805 +// GLOBAL EVENT LISTENERS (Outside jQuery)
2806 +// ====================================
1571 2807
1572 -// Event listener for copy button
2808 +// Event listener for copy button (code blocks)
1573 2809 document.addEventListener("click", (e) => {
1574 2810 if (e.target.classList.contains("mxchat-copy-button")) {
1575 2811 const copyButton = e.target;
1576 2812 const codeBlock = copyButton
@@ -1590,7 +2826,5 @@
1590 2826 });
1591 2827 }
1592 2828 }
1593 2829 });
1594 -
1595 -
1596 2830