PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.1
MxChat – AI Chatbot & Content Generation for WordPress v2.2.1
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/mxchat-admin.js +2278 -155 2.0.32.2.1 View file →
@@ -10,9 +10,9 @@
10 10 timeout = setTimeout(later, wait);
11 11 };
12 12 }
13 13
14 -// Helper function to open edit modal
14 +// Helper function to open edit modal for intents/actions
15 15 function mxchatOpenEditModal(intentId, phrases) {
16 16 const modal = document.getElementById('mxchat-edit-modal');
17 17 if (!modal) return;
18 18
@@ -60,11 +60,122 @@
60 60 // Focus the textarea
61 61 phrasesField.focus();
62 62 }
63 63
64 +// Updated mxchatOpenActionModal function to integrate with the new selector
65 +function mxchatOpenActionModal(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '') {
66 + const modal = document.getElementById('mxchat-action-modal');
67 + if (!modal) return;
68 +
69 + // Get form fields
70 + const actionIdField = document.getElementById('edit_action_id');
71 + const labelField = document.getElementById('intent_label');
72 + const phrasesField = document.getElementById('action_phrases');
73 + const formActionType = document.getElementById('form_action_type');
74 + const callbackGroup = document.getElementById('callback_selection_group');
75 + const callbackSelect = document.getElementById('callback_function');
76 + const saveButton = document.getElementById('mxchat-save-action-btn');
77 + const nonceContainer = document.getElementById('action-nonce-container');
78 + const thresholdSlider = document.getElementById('similarity_threshold');
79 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
80 +
81 + // Set up modal for edit or create
82 + if (isEdit) {
83 + saveButton.textContent = 'Update Action';
84 + formActionType.value = 'mxchat_edit_intent';
85 + actionIdField.value = actionId;
86 + labelField.value = label;
87 + phrasesField.value = phrases;
88 + callbackGroup.style.display = 'none'; // Hide callback selection when editing
89 + thresholdSlider.value = threshold; // Set the current threshold value
90 + thresholdDisplay.textContent = threshold + '%'; // Update display
91 +
92 + // Remove the required attribute when editing
93 + callbackSelect.removeAttribute('required');
94 +
95 + // Update the nonce field for editing
96 + nonceContainer.innerHTML = ''; // Clear existing nonce
97 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) {
98 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.edit_intent_nonce}">`;
99 + }
100 + } else {
101 + saveButton.textContent = 'Save Action';
102 + formActionType.value = 'mxchat_add_intent';
103 + actionIdField.value = '';
104 + labelField.value = '';
105 + phrasesField.value = '';
106 + callbackGroup.style.display = 'block'; // Show callback selection when creating
107 + thresholdSlider.value = 85; // Default value for new actions
108 + thresholdDisplay.textContent = '85%'; // Default display
109 +
110 + // Ensure the required attribute is present when adding
111 + callbackSelect.setAttribute('required', 'required');
112 +
113 + // Update the nonce field for adding
114 + nonceContainer.innerHTML = ''; // Clear existing nonce
115 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) {
116 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.add_intent_nonce}">`;
117 + }
118 + }
119 +
120 + // Show modal with animation
121 + modal.style.display = 'flex';
122 + requestAnimationFrame(() => {
123 + modal.classList.add('active');
124 + });
125 +
126 + // Set up close handlers
127 + const closeModal = () => {
128 + modal.classList.remove('active');
129 + setTimeout(() => {
130 + modal.style.display = 'none';
131 + }, 300); // Match the CSS transition time
132 + };
133 +
134 + // Close button handler
135 + const closeBtn = modal.querySelector('.mxchat-modal-close');
136 + if (closeBtn) {
137 + closeBtn.onclick = closeModal;
138 + }
139 +
140 + // Cancel button handler
141 + const cancelBtn = modal.querySelector('.mxchat-modal-cancel');
142 + if (cancelBtn) {
143 + cancelBtn.onclick = closeModal;
144 + }
145 +
146 + // Click outside modal to close
147 + modal.onclick = (e) => {
148 + if (e.target === modal) {
149 + closeModal();
150 + }
151 + };
152 +
153 + // Escape key to close modal
154 + document.addEventListener('keydown', function(e) {
155 + if (e.key === 'Escape' && modal.classList.contains('active')) {
156 + closeModal();
157 + }
158 + }, { once: true });
159 +
160 + // Focus the first field
161 + labelField.focus();
162 +
163 + // Dispatch an event for the action type selector to catch
164 + const event = new CustomEvent('mxchatModalOpened', {
165 + detail: {
166 + isEdit: isEdit,
167 + callbackFunction: callbackFunction || (isEdit ? callbackSelect.value : '')
168 + }
169 + });
170 + document.dispatchEvent(event);
171 +
172 + return closeModal; // Return close function for external use
173 +}
174 +
64 175 // Initialize event listeners
65 176 document.addEventListener('DOMContentLoaded', () => {
66 - // Set up edit button handlers
177 + // Set up edit button handlers for intents
67 178 document.querySelectorAll('.mxchat-edit-button').forEach(button => {
68 179 button.onclick = () => {
69 180 const intentId = button.dataset.intentId;
70 181 const phrases = button.dataset.phrases;
@@ -70,8 +181,158 @@
70 181 const phrases = button.dataset.phrases;
71 182 mxchatOpenEditModal(intentId, phrases);
72 183 };
73 184 });
185 +
186 + // Set up edit button handlers for actions (new functionality)
187 + document.querySelectorAll('.mxchat-action-card .mxchat-edit-button').forEach(button => {
188 + button.onclick = () => {
189 + const actionId = button.dataset.actionId;
190 + const phrases = button.dataset.phrases;
191 + const label = button.dataset.label;
192 + const threshold = button.dataset.threshold || 85;
193 + const callbackFunction = button.dataset.callbackFunction; // Add this data attribute
194 + mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction);
195 + };
196 + });
197 +
198 + // Set up add new action buttons (new functionality)
199 + const addActionBtn = document.getElementById('mxchat-add-action-btn');
200 + if (addActionBtn) {
201 + addActionBtn.onclick = () => mxchatOpenActionModal();
202 + }
203 +
204 + const createFirstAction = document.getElementById('mxchat-create-first-action');
205 + if (createFirstAction) {
206 + createFirstAction.onclick = () => mxchatOpenActionModal();
207 + }
208 +
209 + // Setup category-specific new action buttons (new functionality)
210 + document.querySelectorAll('.mxchat-new-action-button').forEach(button => {
211 + button.onclick = () => {
212 + const category = button.closest('.mxchat-new-action-card').dataset.category;
213 + const closeModal = mxchatOpenActionModal();
214 +
215 + // Pre-select the appropriate callback based on category
216 + if (category) {
217 + const callbackSelect = document.getElementById('callback_function');
218 + if (callbackSelect) {
219 + setTimeout(() => {
220 + // Map categories to default callbacks
221 + const categoryToCallback = {
222 + 'data_collection': 'mxchat_handle_form_collection',
223 + 'integrations': 'mxchat_handle_slack_message',
224 + 'custom_actions': 'mxchat_handle_custom_action',
225 + 'recommendations': 'mxchat_handle_product_recommendations'
226 + // Add more mappings as needed
227 + };
228 +
229 + if (categoryToCallback[category]) {
230 + callbackSelect.value = categoryToCallback[category];
231 + }
232 + }, 100);
233 + }
234 + }
235 + };
236 + });
237 +
238 + // Handle action toggle switches (new functionality)
239 + document.querySelectorAll('.mxchat-action-toggle').forEach(toggle => {
240 + toggle.onchange = function() {
241 + const actionId = this.dataset.actionId;
242 + const isEnabled = this.checked;
243 +
244 + // Show loading indicator
245 + const loadingEl = document.getElementById('mxchat-action-loading');
246 + if (loadingEl) loadingEl.style.display = 'flex';
247 +
248 + // Send AJAX request to update status
249 + fetch(ajaxurl, {
250 + method: 'POST',
251 + headers: {
252 + 'Content-Type': 'application/x-www-form-urlencoded',
253 + },
254 + body: new URLSearchParams({
255 + action: 'mxchat_toggle_action',
256 + intent_id: actionId,
257 + enabled: isEnabled ? 1 : 0,
258 + nonce: mxchatAdmin.toggle_action_nonce // Use the correct nonce
259 + })
260 + })
261 + .then(response => response.json())
262 + .then(data => {
263 + if (!data.success) {
264 + alert('Failed to update action status: ' + (data.data?.message || 'Unknown error'));
265 + this.checked = !isEnabled; // Revert the toggle
266 + }
267 + })
268 + .catch(error => {
269 + //console.error('Error:', error);
270 + alert('Server error. Please try again.');
271 + this.checked = !isEnabled; // Revert the toggle
272 + })
273 + .finally(() => {
274 + if (loadingEl) loadingEl.style.display = 'none';
275 + });
276 + };
277 + });
278 +
279 + // Handle threshold sliders in action cards (new functionality)
280 + document.querySelectorAll('.mxchat-threshold-slider').forEach(slider => {
281 + slider.oninput = function() {
282 + const actionId = this.id.replace('intent_threshold_', '');
283 + document.getElementById('threshold_output_' + actionId).textContent = this.value + '%';
284 + };
285 + });
286 +
287 + // Handle threshold save buttons in action cards (new functionality)
288 + document.querySelectorAll('.mxchat-threshold-save').forEach(button => {
289 + button.onclick = function(e) {
290 + e.preventDefault();
291 + const form = this.closest('form');
292 + const intentId = form.querySelector('input[name="intent_id"]').value;
293 + const threshold = form.querySelector('input[name="intent_threshold"]').value;
294 + const nonce = form.querySelector('input[name="_wpnonce"]').value;
295 +
296 + // Show loading indicator
297 + const loadingEl = document.getElementById('mxchat-action-loading');
298 + if (loadingEl) loadingEl.style.display = 'flex';
299 +
300 + // Send AJAX request
301 + fetch(ajaxurl, {
302 + method: 'POST',
303 + headers: {
304 + 'Content-Type': 'application/x-www-form-urlencoded',
305 + },
306 + body: new URLSearchParams({
307 + action: 'mxchat_update_intent_threshold',
308 + intent_id: intentId,
309 + intent_threshold: threshold,
310 + _wpnonce: nonce
311 + })
312 + })
313 + .then(response => response.json())
314 + .then(data => {
315 + if (data.success) {
316 + // Visual feedback of success
317 + const card = this.closest('.mxchat-action-card');
318 + card.style.background = 'rgba(120, 115, 245, 0.1)';
319 + setTimeout(() => {
320 + card.style.background = 'white';
321 + }, 300);
322 + } else {
323 + alert('Failed to update threshold: ' + (data.data?.message || 'Unknown error'));
324 + }
325 + })
326 + .catch(error => {
327 + //console.error('Error:', error);
328 + alert('Server error. Please try again.');
329 + })
330 + .finally(() => {
331 + if (loadingEl) loadingEl.style.display = 'none';
332 + });
333 + };
334 + });
74 335 });
75 336
76 337 jQuery(document).ready(function($) {
77 338 // Ensure we have a debounce function (use lodash if available, otherwise use our implementation)
@@ -77,11 +338,29 @@
77 338 // Ensure we have a debounce function (use lodash if available, otherwise use our implementation)
78 339 const useDebounce = (window._ && window._.debounce) ? window._.debounce : debounce;
79 340
80 341 // --- AJAX Auto-Save ---
81 - const $autosaveSections = $('.mxchat-autosave-section');
342 + let $autosaveSections = $('.mxchat-autosave-section');
343 +
344 + // *** ADD THIS: Extend auto-save sections to include Pinecone settings ***
345 + const $pineconeAutosaveSection = $('#mxchat-kb-tab-pinecone');
346 + if ($pineconeAutosaveSection.length) {
347 + $autosaveSections = $autosaveSections.add($pineconeAutosaveSection);
348 + console.log('Added Pinecone section to auto-save monitoring');
349 + }
350 +
351 + // Track whether fields have been modified by user
352 + const userModifiedFields = new Set();
82 353
83 354 if ($autosaveSections.length) {
355 + // Track user interactions with input fields to determine if changes are user-initiated
356 + $autosaveSections.find('input, textarea, select').on('focus keydown paste', function() {
357 + const fieldName = $(this).attr('name');
358 + if (fieldName) {
359 + userModifiedFields.add(fieldName);
360 + }
361 + });
362 +
84 363 // Handle real-time range slider value updates
85 364 $autosaveSections.find('input[type="range"]').on('input', function() {
86 365 const value = $(this).val();
87 366 $('#threshold_value').text(value);
@@ -87,16 +366,43 @@
87 366 $('#threshold_value').text(value);
88 367 });
89 368
90 369 // Handle all input changes (including range slider)
91 - $autosaveSections.find('input, textarea, select').on('change', function() {
370 + $autosaveSections.find('input, textarea, select').on('change', function() {
92 371 const $field = $(this);
93 372 const name = $field.attr('name');
373 +
374 + // Skip saving for API key fields that haven't been interacted with and are empty
375 + const isApiKeyField = name && (
376 + name === 'loops_api_key' ||
377 + name === 'api_key' ||
378 + name === 'xai_api_key' ||
379 + name === 'claude_api_key' ||
380 + name === 'voyage_api_key' ||
381 + name === 'gemini_api_key' ||
382 + name === 'deepseek_api_key' ||
383 + name.indexOf('_api_key') !== -1
384 + );
385 +
386 + // Skip processing if:
387 + // 1. It's an API key field
388 + // 2. The user hasn't interacted with it
389 + // 3. The field is empty
390 + if (isApiKeyField && !userModifiedFields.has(name) && (!$field.val() || $field.val().trim() === '')) {
391 + //console.log('Skipping auto-save for untouched API key field:', name);
392 + return;
393 + }
394 +
94 395 let value;
95 396
96 397 // Handle different input types
97 398 if ($field.attr('type') === 'checkbox') {
98 - value = $field.is(':checked') ? 'on' : 'off';
399 + // *** UPDATED: Handle Pinecone checkboxes differently ***
400 + if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) {
401 + value = $field.is(':checked') ? '1' : '0';
402 + } else {
403 + value = $field.is(':checked') ? 'on' : 'off';
404 + }
99 405 } else {
100 406 value = $field.val();
101 407 }
102 408
@@ -118,12 +424,14 @@
118 424 feedbackContainer.append(spinner);
119 425
120 426 // Determine which AJAX action and nonce to use:
121 427 var ajaxAction, nonce;
122 - // Use the new AJAX action for submenu fields:
123 - if ( name.indexOf('mxchat_prompts_options') !== -1 ||
124 - name === 'mxchat_auto_sync_posts' ||
125 - name === 'mxchat_auto_sync_pages' ) {
428 + // *** UPDATED: Add Pinecone fields to prompts action ***
429 + if (name.indexOf('mxchat_prompts_options') !== -1 ||
430 + name === 'mxchat_auto_sync_posts' ||
431 + name === 'mxchat_auto_sync_pages' ||
432 + name.indexOf('mxchat_auto_sync_') === 0 ||
433 + name.indexOf('mxchat_pinecone_addon_options') !== -1) { // *** ADD THIS LINE ***
126 434 ajaxAction = 'mxchat_save_prompts_setting';
127 435 nonce = mxchatPromptsAdmin.prompts_setting_nonce;
128 436 } else {
129 437 // Otherwise, use the existing AJAX action.
@@ -130,8 +438,13 @@
130 438 ajaxAction = 'mxchat_save_setting';
131 439 nonce = mxchatAdmin.setting_nonce;
132 440 }
133 441
442 + // *** ADD THIS: Debug logging for Pinecone fields ***
443 + if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) {
444 + console.log('Saving Pinecone field:', name, '=', value);
445 + }
446 +
134 447 // AJAX save request
135 448 $.ajax({
136 449 url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
137 450 type: 'POST',
@@ -141,28 +454,105 @@
141 454 value: value,
142 455 _ajax_nonce: nonce
143 456 },
144 457 success: function(response) {
145 - if (response.success) {
146 - spinner.fadeOut(200, function() {
147 - feedbackContainer.append(successIcon);
148 - successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
149 - feedbackContainer.remove();
150 - });
151 - });
152 - } else {
153 - alert('Error saving: ' + (response.data?.message || 'Unknown error'));
154 - if ($field.attr('type') === 'checkbox') {
155 - $field.prop('checked', !$field.is(':checked'));
156 - }
157 - feedbackContainer.remove();
158 - }
159 - },
160 - error: function() {
161 - alert('An error occurred while saving.');
458 + if (response.success) {
459 + spinner.fadeOut(200, function() {
460 + feedbackContainer.append(successIcon);
461 + successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
462 + feedbackContainer.remove();
463 + });
464 + });
465 +
466 + // *** ADD THIS: Update Pinecone checkbox state after successful save ***
467 + if (name && name.indexOf('mxchat_pinecone_addon_options[mxchat_use_pinecone]') !== -1) {
468 + console.log('Pinecone toggle saved successfully, value:', value);
469 +
470 + // The checkbox state is already updated by the user interaction
471 + // But let's make sure the UI state matches the saved value
472 + var $checkbox = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]');
473 + var settingsDiv = $('.mxchat-pinecone-settings');
474 +
475 + // Double-check the UI state matches what was saved
476 + if (value === '1' && !$checkbox.is(':checked')) {
477 + $checkbox.prop('checked', true);
478 + settingsDiv.slideDown(300);
479 + } else if (value === '0' && $checkbox.is(':checked')) {
480 + $checkbox.prop('checked', false);
481 + settingsDiv.slideUp(300);
482 + }
483 +
484 + console.log('Pinecone UI state synchronized');
485 +
486 + // Check if Knowledge Import tab is currently active
487 + if ($('.mxchat-kb-tab-button[data-tab="import"]').hasClass('active')) {
488 + // Show a notice that we need to refresh
489 + var $knowledgeCard = $('#mxchat-kb-tab-import .mxchat-card').eq(1);
490 + if ($knowledgeCard.length > 0) {
491 + // Add a refresh notice at the top of the knowledge base card
492 + var refreshNotice = $('<div class="notice notice-warning" style="margin: 15px 0; padding: 10px 15px;">' +
493 + '<p style="margin: 0;">' +
494 + '<span class="dashicons dashicons-info" style="color: #f0ad4e; margin-right: 5px;"></span>' +
495 + 'Database settings have changed. ' +
496 + '<a href="#" onclick="location.reload(); return false;" style="font-weight: bold;">Click here to refresh</a> to see the updated knowledge base.' +
497 + '</p></div>');
498 +
499 + $knowledgeCard.prepend(refreshNotice);
500 + }
501 + } else {
502 + // If not on import tab, set a flag to refresh when they go there
503 + sessionStorage.setItem('mxchat_pinecone_changed', 'true');
504 + }
505 + }
506 +
507 + // *** ADD THIS: Debug logging for successful saves ***
508 + if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) {
509 + console.log('Pinecone field saved successfully:', name, '=', value);
510 + }
511 +
512 + // Check if the response contains a "no changes" message and log it
513 + if (response.data && response.data.message === 'No changes detected') {
514 + //console.log('No changes detected for field:', name);
515 + }
516 + } else {
517 + // Only show alert for actual errors, not for "no changes"
518 + let errorMessage = response.data?.message || 'Unknown error';
519 +
520 + // Don't display an alert for "no changes" message
521 + if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') {
522 + alert('Error saving: ' + errorMessage);
523 + } else {
524 + // Still provide visual feedback that no changes were needed
525 + spinner.fadeOut(200, function() {
526 + feedbackContainer.append(successIcon);
527 + successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
528 + feedbackContainer.remove();
529 + });
530 + });
531 + //console.log('No changes detected for field:', name);
532 + return;
533 + }
534 +
535 + // Only revert checkbox state if it was an actual error
536 + if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') {
537 + if ($field.attr('type') === 'checkbox') {
538 + $field.prop('checked', !$field.is(':checked'));
539 + }
540 + }
541 +
542 + // Always clean up the feedback container
543 + feedbackContainer.remove();
544 + }
545 +},
546 + error: function(xhr, textStatus, error) {
547 + //console.error('AJAX Error:', textStatus, error);
548 + alert('An error occurred while saving. Please try again.');
549 +
550 + // Revert checkbox state on error
162 551 if ($field.attr('type') === 'checkbox') {
163 552 $field.prop('checked', !$field.is(':checked'));
164 553 }
554 +
165 555 feedbackContainer.remove();
166 556 }
167 557 });
168 558 });
@@ -174,9 +564,9 @@
174 564 $(this).wpColorPicker({
175 565 change: useDebounce(function(event, ui) {
176 566 // Safety check - ensure we have a valid field and value
177 567 if (!$colorField || !$colorField.val()) {
178 - console.warn('Color picker not ready');
568 + //console.warn('Color picker not ready');
179 569 return;
180 570 }
181 571
182 572 const name = $colorField.attr('name');
@@ -182,9 +572,9 @@
182 572 const name = $colorField.attr('name');
183 573 const value = $colorField.val();
184 574
185 575 if (!name || !value) {
186 - console.warn('Missing required color picker values');
576 + //console.warn('Missing required color picker values');
187 577 return;
188 578 }
189 579
190 580 // Create feedback container
@@ -195,20 +585,22 @@
195 585 // Position feedback container
196 586 $colorField.closest('.wp-picker-container').after(feedbackContainer);
197 587 feedbackContainer.append(spinner);
198 588
199 - // Determine AJAX action and nonce for color fields:
589 + // Determine which AJAX action and nonce to use:
200 590 var ajaxAction, nonce;
201 - if ( name.indexOf('mxchat_prompts_options') !== -1 ||
202 - name === 'mxchat_auto_sync_posts' ||
203 - name === 'mxchat_auto_sync_pages' ) {
591 + // Use the new AJAX action for submenu fields:
592 + if (name.indexOf('mxchat_prompts_options') !== -1 ||
593 + name === 'mxchat_auto_sync_posts' ||
594 + name === 'mxchat_auto_sync_pages' ||
595 + name.indexOf('mxchat_auto_sync_') === 0) { // Modified to catch all auto-sync fields
204 596 ajaxAction = 'mxchat_save_prompts_setting';
205 597 nonce = mxchatPromptsAdmin.prompts_setting_nonce;
206 598 } else {
599 + // Otherwise, use the existing AJAX action.
207 600 ajaxAction = 'mxchat_save_setting';
208 601 nonce = mxchatAdmin.setting_nonce;
209 602 }
210 -
211 603 // AJAX save request
212 604 $.ajax({
213 605 url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
214 606 type: 'POST',
@@ -240,9 +632,9 @@
240 632 });
241 633 });
242 634
243 635 // Reinitialize color pickers when switching tabs
244 - $('.mxchat-nav-tab').on('click.mxchat', function() {
636 + $('.mxchat-tab-button').on('click.mxchat', function() {
245 637 setTimeout(function() {
246 638 $('.my-color-field:visible').wpColorPicker('close');
247 639 }, 100);
248 640 });
@@ -247,107 +639,606 @@
247 639 }, 100);
248 640 });
249 641 }
250 642
251 - // Initialize tabs system
252 - function initTabs() {
253 - // Remove any existing handlers first
254 - $('.mxchat-nav-tab').off('click.mxchat');
643 +// Initialize tabs system
644 +function initTabs() {
645 + // Remove any existing handlers first
646 + $('.mxchat-tab-button').off('click.mxchat');
647 +
648 + // Add new click handlers
649 + $('.mxchat-tab-button').on('click.mxchat', function(e) {
650 + e.preventDefault();
651 + e.stopPropagation();
255 652
256 - // Add new click handlers
257 - $('.mxchat-nav-tab').on('click.mxchat', function(e) {
258 - e.preventDefault();
259 - e.stopPropagation();
260 -
261 - var $this = $(this);
262 -
263 - // Get tab ID - try href first, fallback to data-tab, then to default
264 - var tabId = $this.attr('href');
265 - if (tabId) {
266 - tabId = tabId.replace('#', '');
267 - } else {
268 - tabId = $this.data('tab') || 'chatbot';
653 + var $this = $(this);
654 +
655 + // Get tab ID from data-tab attribute
656 + var tabId = $this.data('tab') || 'chatbot';
657 +
658 + // Safety check for empty tabId
659 + if (!tabId) {
660 + //console.warn('No tab identifier found');
661 + return;
662 + }
663 +
664 + // Update tab buttons
665 + $('.mxchat-tab-button').removeClass('active');
666 + $this.addClass('active');
667 +
668 + // Update content areas - with safety check
669 + $('.mxchat-tab-content').removeClass('active');
670 + var $targetTab = $('#' + tabId);
671 + if ($targetTab.length) {
672 + $targetTab.addClass('active');
673 + // Removed localStorage saving functionality
674 + } else {
675 + //console.warn('Tab content #' + tabId + ' not found');
676 + }
677 + });
678 +}
679 +
680 +// Initialize tabs and handle events
681 +initTabs();
682 +$(document).on('widget-added widget-updated postbox-toggled', initTabs);
683 +
684 +// Always activate the first tab (Chatbot)
685 +$('.mxchat-tab-button').first().trigger('click.mxchat');
686 +
687 + // Attach edit modal event handler
688 + $(document).on('click', '.mxchat-edit-button', function() {
689 + const intentId = $(this).data('intent-id');
690 + const phrases = $(this).data('phrases');
691 + mxchatOpenEditModal(intentId, phrases);
692 + });
693 +
694 +// Toggle visibility handlers
695 +function toggleVisibility(selector) {
696 + $(selector).on('click', function() {
697 + var inputField = $(this).prev('input');
698 + if (inputField.attr('type') === 'password') {
699 + inputField.attr('type', 'text');
700 + $(this).text('Hide');
701 + } else {
702 + inputField.attr('type', 'password');
703 + $(this).text('Show');
704 + }
705 + });
706 +}
707 +
708 +// Initialize all toggle visibility buttons
709 +[
710 + '#toggleApiKeyVisibility',
711 + '#toggleWooCommerceSecretVisibility',
712 + '#toggleVoyageAPIKeyVisibility',
713 + '#toggleLoopsApiKeyVisibility',
714 + '#toggleXaiApiKeyVisibility',
715 + '#toggleClaudeApiKeyVisibility',
716 + '#toggleBraveApiKeyVisibility',
717 + '#toggleWebhookUrlVisibility',
718 + '#toggleSecretKeyVisibility',
719 + '#toggleBotTokenVisibility',
720 + '#toggleDeepSeekApiKeyVisibility',
721 + '#toggleGeminiApiKeyVisibility' // Added Gemini toggle
722 +].forEach(toggleVisibility);
723 +
724 +// Handle API key visibility based on model selection
725 +function setupAPIKeyVisibility() {
726 + // Cache the selectors
727 + const $chatModelSelect = $('#model');
728 + const $embeddingModelSelect = $('#embedding_model');
729 +
730 + // First, locate and mark the API key rows
731 + setupAPIKeyRows();
732 +
733 + // Initial setup based on current selections
734 + updateApiKeyVisibility();
735 +
736 + // Listen for changes to the model selectors
737 + $chatModelSelect.on('change', updateApiKeyVisibility);
738 + $embeddingModelSelect.on('change', updateApiKeyVisibility);
739 +
740 + /**
741 + * Locate and mark rows that contain API key fields
742 + */
743 + function setupAPIKeyRows() {
744 + // Find key rows by their field IDs
745 + const providerMap = {
746 + 'api_key': 'openai',
747 + 'xai_api_key': 'xai',
748 + 'claude_api_key': 'claude',
749 + 'deepseek_api_key': 'deepseek',
750 + 'voyage_api_key': 'voyage',
751 + 'gemini_api_key': 'gemini' // Added Gemini API key mapping
752 + };
753 +
754 + $.each(providerMap, function(fieldId, provider) {
755 + const $field = $('#' + fieldId);
756 + if ($field.length) {
757 + const $row = $field.closest('tr');
758 + $row.addClass('mxchat-setting-row');
759 + $row.attr('data-provider', provider);
269 760 }
270 -
271 - // Safety check for empty tabId
272 - if (!tabId) {
273 - console.warn('No tab identifier found');
274 - return;
761 + });
762 + }
763 +
764 +/**
765 + * Updates the visibility of API key fields based on current model selections
766 + */
767 +function updateApiKeyVisibility() {
768 + const chatModel = $chatModelSelect.val();
769 + const embeddingModel = $embeddingModelSelect.val();
770 +
771 + // Determine which providers are needed
772 + const isOpenAIChat = chatModel && chatModel.startsWith('gpt-');
773 + const isXAI = chatModel && chatModel.startsWith('grok-');
774 + const isClaude = chatModel && chatModel.startsWith('claude-');
775 + const isDeepSeek = chatModel && chatModel.startsWith('deepseek-');
776 + const isGemini = chatModel && chatModel.startsWith('gemini-'); // Added Gemini detection
777 +
778 + const isOpenAIEmbedding = embeddingModel && embeddingModel.startsWith('text-embedding-');
779 + const isVoyage = embeddingModel && embeddingModel.startsWith('voyage-');
780 + const isGeminiEmbedding = embeddingModel && embeddingModel.startsWith('gemini-embedding-');
781 +
782 + // Update API key visibility for each provider
783 + updateWrapperVisibility('openai', isOpenAIChat || isOpenAIEmbedding);
784 + updateWrapperVisibility('xai', isXAI);
785 + updateWrapperVisibility('claude', isClaude);
786 + updateWrapperVisibility('deepseek', isDeepSeek);
787 + updateWrapperVisibility('voyage', isVoyage);
788 + updateWrapperVisibility('gemini', isGemini || isGeminiEmbedding); // Updated Gemini visibility for both chat and embedding
789 +
790 + // Update provider-specific notices for OpenAI
791 + if (isOpenAIChat && isOpenAIEmbedding) {
792 + $('div[data-provider="openai"] .api-key-notice').text(
793 + 'Required for your selected chat model and embedding model. Important: You must add credits before use.'
794 + );
795 + } else if (isOpenAIChat) {
796 + $('div[data-provider="openai"] .api-key-notice').text(
797 + 'Required for your selected chat model. Important: You must add credits before use.'
798 + );
799 + } else if (isOpenAIEmbedding) {
800 + $('div[data-provider="openai"] .api-key-notice').text(
801 + 'Required for your selected embedding model. Important: You must add credits before use.'
802 + );
803 + }
804 +
805 + // Update provider-specific notices for Gemini
806 + if (isGemini && isGeminiEmbedding) {
807 + $('div[data-provider="gemini"] .api-key-notice').text(
808 + 'Required for your selected chat model and embedding model.'
809 + );
810 + } else if (isGemini) {
811 + $('div[data-provider="gemini"] .api-key-notice').text(
812 + 'Required for your selected chat model.'
813 + );
814 + } else if (isGeminiEmbedding) {
815 + $('div[data-provider="gemini"] .api-key-notice').text(
816 + 'Required for your selected embedding model.'
817 + );
818 + }
819 +}
820 +
821 + /**
822 + * Updates visibility of a specific provider's API key wrapper
823 + */
824 + function updateWrapperVisibility(provider, isVisible) {
825 + const $row = $('tr.mxchat-setting-row[data-provider="' + provider + '"]');
826 +
827 + if (!$row.length) {
828 + //console.warn('API key row not found for provider: ' + provider);
829 + return;
830 + }
831 +
832 + if (isVisible) {
833 + $row.show();
834 + if (!$row.hasClass('highlighted')) {
835 + $row.addClass('highlighted');
836 + setTimeout(() => {
837 + $row.removeClass('highlighted');
838 + }, 1500);
275 839 }
276 -
277 - // Update tabs
278 - $('.mxchat-nav-tab').removeClass('mxchat-nav-tab-active');
279 - $this.addClass('mxchat-nav-tab-active');
280 -
281 - // Update content areas - with safety check
282 - $('.mxchat-tab-content').removeClass('active').hide();
283 - var $targetTab = $('#' + tabId);
284 - if ($targetTab.length) {
285 - $targetTab.addClass('active').show();
286 -
287 - // Store active tab
288 - try {
289 - localStorage.setItem('mxchat_active_tab', tabId);
290 - } catch (e) {
291 - console.warn('LocalStorage not available:', e);
292 - }
293 - } else {
294 - console.warn('Tab content #' + tabId + ' not found');
840 + } else {
841 + $row.hide();
842 + }
843 + }
844 +}
845 +
846 +// Add this to your JavaScript file
847 +function setupMxChatModelSelector() {
848 + const $modelSelect = $('#model');
849 + const $modelSelectorButton = $('<button>', {
850 + type: 'button',
851 + id: 'mxchat_model_selector_btn',
852 + class: 'button-primary mxchat-model-selector-btn',
853 + text: 'Select AI Model'
854 + });
855 +
856 + // Replace the select dropdown with a button
857 + $modelSelect.hide().after($modelSelectorButton);
858 +
859 + // Update button text to show currently selected model
860 + function updateButtonText() {
861 + const selectedModel = $modelSelect.val();
862 + const selectedModelText = $modelSelect.find('option:selected').text();
863 + $modelSelectorButton.text(selectedModelText);
864 + }
865 +
866 + // Initialize button text
867 + updateButtonText();
868 +
869 + // Create and append modal HTML
870 + const modelSelectorModal = `
871 + <div id="mxchat_model_selector_modal" class="mxchat-model-selector-modal">
872 + <div class="mxchat-model-selector-modal-content">
873 + <div class="mxchat-model-selector-modal-header">
874 + <h3>Select AI Model</h3>
875 + <span class="mxchat-model-selector-modal-close">&times;</span>
876 + </div>
877 + <div class="mxchat-model-selector-modal-body">
878 + <div class="mxchat-model-selector-search-container">
879 + <input type="text" id="mxchat_model_search_input" class="mxchat-model-search-input" placeholder="Search models...">
880 + </div>
881 + <div class="mxchat-model-selector-categories">
882 + <button class="mxchat-model-category-btn active" data-category="all">All</button>
883 + <button class="mxchat-model-category-btn" data-category="gemini">Google Gemini</button>
884 + <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
885 + <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
886 + <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
887 + <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
888 + </div>
889 + <div class="mxchat-model-selector-grid" id="mxchat_models_grid"></div>
890 + </div>
891 + <div class="mxchat-model-selector-modal-footer">
892 + <button id="mxchat_cancel_model_selection" class="button mxchat-model-cancel-btn">Cancel</button>
893 + </div>
894 + </div>
895 + </div>
896 + `;
897 +
898 + $('body').append(modelSelectorModal);
899 +
900 + // Populate models grid
901 + function populateModelsGrid(filter = '', category = 'all') {
902 + const $grid = $('#mxchat_models_grid');
903 + $grid.empty();
904 +
905 + const models = {
906 + gemini: [
907 + { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', description: 'Next-Gen features, speed & multimodal generation' },
908 + { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash-Lite', description: 'Cost-efficient with low latency' },
909 + { value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', description: 'Complex reasoning tasks requiring more intelligence' },
910 + { value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', description: 'Fast and versatile performance' },
911 + ],
912 + openai: [
913 + { value: 'gpt-4.1-2025-04-14', label: 'GPT-4.1', description: 'Flagship model for complex tasks' },
914 + { value: 'gpt-4o', label: 'GPT-4o', description: 'Recommended for most use cases' },
915 + { value: 'gpt-4o-mini', label: 'GPT-4o Mini', description: 'Fast and lightweight' },
916 + { value: 'gpt-4-turbo', label: 'GPT-4 Turbo', description: 'High-performance model' },
917 + { value: 'gpt-4', label: 'GPT-4', description: 'High intelligence model' },
918 + { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', description: 'Affordable and fast' },
919 + ],
920 + claude: [
921 + { value: 'claude-opus-4-20250514', label: 'Claude 4 Opus', description: 'Most capable Claude model' },
922 + { value: 'claude-sonnet-4-20250514', label: 'Claude 4 Sonnet', description: 'High performance' },
923 + { value: 'claude-3-7-sonnet-20250219', label: 'Claude 3.7 Sonnet', description: 'High intelligence' },
924 + { value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', description: 'Intelligent and balanced' },
925 + { value: 'claude-3-opus-20240229', label: 'Claude 3 Opus', description: 'Highly complex tasks' },
926 + { value: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', description: 'Balanced performance' },
927 + { value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', description: 'Fastest Claude model' },
928 + ],
929 + xai: [
930 + { value: 'grok-3-beta', label: 'Grok-3', description: 'Powerful model with 131K context' },
931 + { value: 'grok-3-fast-beta', label: 'Grok-3 Fast', description: 'High performance with faster responses' },
932 + { value: 'grok-3-mini-beta', label: 'Grok-3 Mini', description: 'Affordable model with good performance' },
933 + { value: 'grok-3-mini-fast-beta', label: 'Grok-3 Mini Fast', description: 'Quick and cost-effective' },
934 + { value: 'grok-2', label: 'Grok 2', description: 'Latest X.AI model' },
935 + ],
936 + deepseek: [
937 + { value: 'deepseek-chat', label: 'DeepSeek-V3', description: 'Advanced AI assistant' },
938 + ],
939 + };
940 +
941 + let allModels = [];
942 + Object.keys(models).forEach(key => {
943 + if (category === 'all' || category === key) {
944 + allModels = allModels.concat(models[key]);
295 945 }
296 946 });
947 +
948 + // Filter by search term if present
949 + if (filter) {
950 + const lowerFilter = filter.toLowerCase();
951 + allModels = allModels.filter(model =>
952 + model.label.toLowerCase().includes(lowerFilter) ||
953 + model.description.toLowerCase().includes(lowerFilter)
954 + );
955 + }
956 +
957 + // Create model cards
958 + allModels.forEach(model => {
959 + const isSelected = $modelSelect.val() === model.value;
960 + const $modelCard = $(`
961 + <div class="mxchat-model-selector-card ${isSelected ? 'mxchat-model-selected' : ''}" data-value="${model.value}">
962 + <div class="mxchat-model-selector-icon">${getModelIcon(model.value)}</div>
963 + <div class="mxchat-model-selector-info">
964 + <h4 class="mxchat-model-selector-title">${model.label}</h4>
965 + <p class="mxchat-model-selector-description">${model.description}</p>
966 + </div>
967 + ${isSelected ? '<div class="mxchat-model-selector-checkmark">✓</div>' : ''}
968 + </div>
969 + `);
970 + $grid.append($modelCard);
971 + });
297 972 }
298 973
299 - // Initialize tabs and handle events
300 - initTabs();
301 - $(document).on('widget-added widget-updated postbox-toggled', initTabs);
974 +// Helper function to get icon for each model
975 +function getModelIcon(modelValue) {
976 + if (modelValue.startsWith('gemini-')) return '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" class="mxchat-model-icon-gemini"><defs><path id="a" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z"></path></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"></use></clipPath><path clip-path="url(#b)" fill="#FBBC05" d="M0 37V11l17 13z"></path><path clip-path="url(#b)" fill="#EA4335" d="M0 11l17 13 7-6.1L48 14V0H0z"></path><path clip-path="url(#b)" fill="#34A853" d="M0 37l30-23 7.9 1L48 0v48H0z"></path><path clip-path="url(#b)" fill="#4285F4" d="M48 48L17 24l-4-3 35-10z"></path></svg>';
977 + if (modelValue.startsWith('gpt-')) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" class="mxchat-model-icon-openai"><path fill="currentColor" d="M297 131a80.6 80.6 0 0 0-93.7-104.2 80.6 80.6 0 0 0-137 29A80.6 80.6 0 0 0 23 189a80.6 80.6 0 0 0 93.7 104.2 80.6 80.6 0 0 0 137-29A80.7 80.7 0 0 0 297.1 131zM176.9 299c-14 .1-27.6-4.8-38.4-13.8l1.9-1 63.7-36.9c3.3-1.8 5.3-5.3 5.2-9v-89.9l27 15.6c.3.1.4.4.5.7v74.4a60 60 0 0 1-60 60zM47.9 244a59.7 59.7 0 0 1-7.1-40.1l1.9 1.1 63.7 36.8c3.2 1.9 7.2 1.9 10.5 0l77.8-45V228c0 .3-.2.6-.4.8L129.9 266a60 60 0 0 1-82-22zM31.2 105c7-12.2 18-21.5 31.2-26.3v75.8c0 3.7 2 7.2 5.2 9l77.8 45-27 15.5a1 1 0 0 1-.9 0L53.1 187a60 60 0 0 1-22-82zm221.2 51.5-77.8-45 27-15.5a1 1 0 0 1 .9 0l64.4 37.1a60 60 0 0 1-9.3 108.2v-75.8c0-3.7-2-7.2-5.2-9zm26.8-40.4-1.9-1.1-63.7-36.8a10.4 10.4 0 0 0-10.5 0L125.4 123V92c0-.3 0-.6.3-.8L190.1 54a60 60 0 0 1 89.1 62.1zm-168.5 55.4-27-15.5a1 1 0 0 1-.4-.7V80.9a60 60 0 0 1 98.3-46.1l-1.9 1L116 72.8a10.3 10.3 0 0 0-5.2 9v89.8zm14.6-31.5 34.7-20 34.6 20v40L160 200l-34.7-20z"></path></svg>';
978 + if (modelValue.startsWith('claude-')) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 176" fill="none" class="mxchat-model-icon-claude"><path fill="currentColor" d="m147.487 0l70.081 175.78H256L185.919 0zM66.183 106.221l23.98-61.774l23.98 61.774zM70.07 0L0 175.78h39.18l14.33-36.914h73.308l14.328 36.914h39.179L110.255 0z"></path></svg>';
979 + if (modelValue.startsWith('grok-')) return '<svg fill="currentColor" fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="mxchat-model-icon-xai"><path d="M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z"></path></svg>';
980 + if (modelValue.startsWith('deepseek-')) return '<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg" class="mxchat-model-icon-deepseek"><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="currentColor"></path></svg>';
981 + return '<span class="dashicons dashicons-admin-generic mxchat-model-icon-generic"></span>';
982 +}
302 983
303 - // Activate initial tab
304 - try {
305 - var savedTab = localStorage.getItem('mxchat_active_tab');
306 - if (savedTab && $('#' + savedTab).length > 0) {
307 - $('.mxchat-nav-tab[href="#' + savedTab + '"]').trigger('click.mxchat');
308 - } else {
309 - $('.mxchat-nav-tab').first().trigger('click.mxchat');
984 + // Event handlers
985 + $modelSelectorButton.on('click', function() {
986 + $('#mxchat_model_selector_modal').show();
987 + populateModelsGrid('', 'all');
988 + });
989 +
990 + $('.mxchat-model-selector-modal-close, #mxchat_cancel_model_selection').on('click', function() {
991 + $('#mxchat_model_selector_modal').hide();
992 + });
993 +
994 + $('.mxchat-model-category-btn').on('click', function() {
995 + $('.mxchat-model-category-btn').removeClass('active');
996 + $(this).addClass('active');
997 + const category = $(this).data('category');
998 + const searchTerm = $('#mxchat_model_search_input').val();
999 + populateModelsGrid(searchTerm, category);
1000 + });
1001 +
1002 + $('#mxchat_model_search_input').on('input', function() {
1003 + const searchTerm = $(this).val();
1004 + const activeCategory = $('.mxchat-model-category-btn.active').data('category');
1005 + populateModelsGrid(searchTerm, activeCategory);
1006 + });
1007 +
1008 + $(document).on('click', '.mxchat-model-selector-card', function() {
1009 + const modelValue = $(this).data('value');
1010 + $modelSelect.val(modelValue).trigger('change');
1011 + updateButtonText();
1012 + $('#mxchat_model_selector_modal').hide();
1013 + });
1014 +
1015 + // Close modal when clicking outside
1016 + $(window).on('click', function(event) {
1017 + if ($(event.target).is('#mxchat_model_selector_modal')) {
1018 + $('#mxchat_model_selector_modal').hide();
310 1019 }
311 - } catch (e) {
312 - $('.mxchat-nav-tab').first().trigger('click.mxchat');
1020 + });
1021 +}
1022 +
1023 +// Embedding model selector - completely separate from chat model selector
1024 +function setupMxChatEmbeddingModelSelector() {
1025 + const $embeddingModelSelect = $('#embedding_model');
1026 +
1027 + // Skip if the element doesn't exist on the page
1028 + if ($embeddingModelSelect.length === 0) {
1029 + return;
313 1030 }
314 1031
315 - // Attach edit modal event handler
316 - $(document).on('click', '.mxchat-edit-button', function() {
317 - const intentId = $(this).data('intent-id');
318 - const phrases = $(this).data('phrases');
319 - mxchatOpenEditModal(intentId, phrases);
1032 + const $embeddingModelSelectorButton = $('<button>', {
1033 + type: 'button',
1034 + id: 'mxchat_embedding_model_selector_btn',
1035 + class: 'button-primary mxchat-embedding-model-selector-btn', // Changed class name to be more specific
1036 + text: 'Select Embedding Model'
320 1037 });
321 1038
322 - // Toggle visibility handlers
323 - function toggleVisibility(selector) {
324 - $(selector).on('click', function() {
325 - var inputField = $(this).prev('input');
326 - if (inputField.attr('type') === 'password') {
327 - inputField.attr('type', 'text');
328 - $(this).text('Hide');
1039 + // Replace the select dropdown with a button
1040 + $embeddingModelSelect.hide().after($embeddingModelSelectorButton);
1041 +
1042 + // Update button text to show currently selected model
1043 + function updateButtonText() {
1044 + const selectedModel = $embeddingModelSelect.val();
1045 + const selectedModelText = $embeddingModelSelect.find('option:selected').text();
1046 + $embeddingModelSelectorButton.text(selectedModelText);
1047 + }
1048 +
1049 + // Initialize button text
1050 + updateButtonText();
1051 +
1052 + // Create a unique ID for the modal to avoid conflicts
1053 + const embeddingModalId = 'mxchat_embedding_model_selector_modal';
1054 +
1055 + // Create and append modal HTML with unique IDs
1056 + const embeddingModelSelectorModal = `
1057 + <div id="${embeddingModalId}" class="mxchat-embedding-model-selector-modal">
1058 + <div class="mxchat-embedding-model-selector-modal-content">
1059 + <div class="mxchat-embedding-model-selector-modal-header">
1060 + <h3>Select Embedding Model</h3>
1061 + <span class="mxchat-embedding-model-selector-modal-close">&times;</span>
1062 + </div>
1063 + <div class="mxchat-embedding-model-selector-modal-body">
1064 + <div class="mxchat-embedding-model-selector-search-container">
1065 + <input type="text" id="mxchat_embedding_model_search_input" class="mxchat-embedding-model-search-input" placeholder="Search models...">
1066 + </div>
1067 + <div class="mxchat-embedding-model-selector-categories">
1068 + <button class="mxchat-embedding-model-category-btn active" data-category="all">All</button>
1069 + <button class="mxchat-embedding-model-category-btn" data-category="openai">OpenAI</button>
1070 + <button class="mxchat-embedding-model-category-btn" data-category="voyage">Voyage AI</button>
1071 + <button class="mxchat-embedding-model-category-btn" data-category="gemini">Google Gemini</button>
1072 + </div>
1073 + <div class="mxchat-embedding-model-selector-grid" id="mxchat_embedding_models_grid"></div>
1074 + </div>
1075 + <div class="mxchat-embedding-model-selector-modal-footer">
1076 + <button id="mxchat_cancel_embedding_model_selection" class="button mxchat-embedding-model-cancel-btn">Cancel</button>
1077 + </div>
1078 + </div>
1079 + </div>
1080 + `;
1081 +
1082 + // Use jQuery's append to ensure it doesn't clash with existing modals
1083 + $('body').append(embeddingModelSelectorModal);
1084 +
1085 + // Populate models grid
1086 + function populateEmbeddingModelsGrid(filter = '', category = 'all') {
1087 + const $grid = $('#mxchat_embedding_models_grid');
1088 + $grid.empty();
1089 +
1090 + // Define embedding models with descriptions and context lengths
1091 + const models = {
1092 + openai: [
1093 + {
1094 + value: 'text-embedding-3-small',
1095 + label: 'TE3 Small',
1096 + description: 'Fast and cost-effective embeddings (1536 dimensions, 8K context)'
1097 + },
1098 + {
1099 + value: 'text-embedding-ada-002',
1100 + label: 'Ada 2',
1101 + description: 'Balanced performance embeddings (1536 dimensions, 8K context)'
1102 + },
1103 + {
1104 + value: 'text-embedding-3-large',
1105 + label: 'TE3 Large',
1106 + description: 'High-performance embeddings (3072 dimensions, 8K context)'
1107 + }
1108 + ],
1109 + voyage: [
1110 + {
1111 + value: 'voyage-3-large',
1112 + label: 'Voyage-3 Large',
1113 + description: 'Advanced semantic search embeddings (2048 dimensions, 32K context)'
1114 + }
1115 + ],
1116 + gemini: [
1117 + {
1118 + value: 'gemini-embedding-exp-03-07',
1119 + label: 'Gemini Embedding',
1120 + description: 'Experimental SOTA embeddings (1536 dimensions, 8K context)'
1121 + }
1122 + ]
1123 + };
1124 +
1125 + let allModels = [];
1126 + Object.keys(models).forEach(key => {
1127 + if (category === 'all' || category === key) {
1128 + allModels = allModels.concat(models[key]);
1129 + }
1130 + });
1131 +
1132 + // Filter by search term if present
1133 + if (filter) {
1134 + const lowerFilter = filter.toLowerCase();
1135 + allModels = allModels.filter(model =>
1136 + model.label.toLowerCase().includes(lowerFilter) ||
1137 + model.description.toLowerCase().includes(lowerFilter)
1138 + );
1139 + }
1140 +
1141 + // Create model cards
1142 + allModels.forEach(model => {
1143 + const isSelected = $embeddingModelSelect.val() === model.value;
1144 + let providerClass = 'mxchat-embedding-model-provider-openai';
1145 +
1146 + if (model.value.startsWith('voyage-')) {
1147 + providerClass = 'mxchat-embedding-model-provider-voyage';
1148 + } else if (model.value.startsWith('gemini-embedding-')) {
1149 + providerClass = 'mxchat-embedding-model-provider-gemini';
1150 + }
1151 +
1152 + let iconHTML = '';
1153 + if (model.value.startsWith('voyage-')) {
1154 + iconHTML = '<span class="dashicons dashicons-chart-line mxchat-embedding-model-icon-voyage"></span>';
1155 + } else if (model.value.startsWith('gemini-embedding-')) {
1156 + iconHTML = '<span class="dashicons dashicons-google mxchat-embedding-model-icon-gemini"></span>';
329 1157 } else {
330 - inputField.attr('type', 'password');
331 - $(this).text('Show');
1158 + iconHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" class="mxchat-embedding-model-icon-openai"><path fill="currentColor" d="M297 131a80.6 80.6 0 0 0-93.7-104.2 80.6 80.6 0 0 0-137 29A80.6 80.6 0 0 0 23 189a80.6 80.6 0 0 0 93.7 104.2 80.6 80.6 0 0 0 137-29A80.7 80.7 0 0 0 297.1 131zM176.9 299c-14 .1-27.6-4.8-38.4-13.8l1.9-1 63.7-36.9c3.3-1.8 5.3-5.3 5.2-9v-89.9l27 15.6c.3.1.4.4.5.7v74.4a60 60 0 0 1-60 60zM47.9 244a59.7 59.7 0 0 1-7.1-40.1l1.9 1.1 63.7 36.8c3.2 1.9 7.2 1.9 10.5 0l77.8-45V228c0 .3-.2.6-.4.8L129.9 266a60 60 0 0 1-82-22zM31.2 105c7-12.2 18-21.5 31.2-26.3v75.8c0 3.7 2 7.2 5.2 9l77.8 45-27 15.5a1 1 0 0 1-.9 0L53.1 187a60 60 0 0 1-22-82zm221.2 51.5-77.8-45 27-15.5a1 1 0 0 1 .9 0l64.4 37.1a60 60 0 0 1-9.3 108.2v-75.8c0-3.7-2-7.2-5.2-9zm26.8-40.4-1.9-1.1-63.7-36.8a10.4 10.4 0 0 0-10.5 0L125.4 123V92c0-.3 0-.6.3-.8L190.1 54a60 60 0 0 1 89.1 62.1zm-168.5 55.4-27-15.5a1 1 0 0 1-.4-.7V80.9a60 60 0 0 1 98.3-46.1l-1.9 1L116 72.8a10.3 10.3 0 0 0-5.2 9v89.8zm14.6-31.5 34.7-20 34.6 20v40L160 200l-34.7-20z"></path></svg>';
332 1159 }
1160 +
1161 + const $modelCard = $(`
1162 + <div class="mxchat-embedding-model-selector-card ${isSelected ? 'mxchat-embedding-model-selected' : ''} ${providerClass}" data-value="${model.value}">
1163 + <div class="mxchat-embedding-model-selector-icon">
1164 + ${iconHTML}
1165 + </div>
1166 + <div class="mxchat-embedding-model-selector-info">
1167 + <h4 class="mxchat-embedding-model-selector-title">${model.label}</h4>
1168 + <p class="mxchat-embedding-model-selector-description">${model.description}</p>
1169 + </div>
1170 + ${isSelected ? '<div class="mxchat-embedding-model-selector-checkmark">✓</div>' : ''}
1171 + </div>
1172 + `);
1173 +
1174 + $grid.append($modelCard);
333 1175 });
334 1176 }
335 1177
336 - // Initialize all toggle visibility buttons
337 - [
338 - '#toggleApiKeyVisibility',
339 - '#toggleWooCommerceSecretVisibility',
340 - '#toggleLoopsApiKeyVisibility',
341 - '#toggleXaiApiKeyVisibility',
342 - '#toggleClaudeApiKeyVisibility',
343 - '#toggleBraveApiKeyVisibility',
344 - '#toggleWebhookUrlVisibility',
345 - '#toggleSecretKeyVisibility',
346 - '#toggleBotTokenVisibility',
347 - '#toggleDeepSeekApiKeyVisibility'
348 - ].forEach(toggleVisibility);
1178 + // Event handlers - use namespaced events to avoid conflicts
1179 + $embeddingModelSelectorButton.on('click.embeddingModelSelector', function(e) {
1180 + e.stopPropagation(); // Prevent event bubbling
1181 + $('#' + embeddingModalId).show();
1182 + populateEmbeddingModelsGrid('', 'all');
1183 + });
349 1184
1185 + $('.mxchat-embedding-model-selector-modal-close, #mxchat_cancel_embedding_model_selection').on('click.embeddingModelSelector', function(e) {
1186 + e.stopPropagation(); // Prevent event bubbling
1187 + $('#' + embeddingModalId).hide();
1188 + });
1189 +
1190 + $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').on('click.embeddingModelSelector', function(e) {
1191 + e.stopPropagation(); // Prevent event bubbling
1192 + $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').removeClass('active');
1193 + $(this).addClass('active');
1194 + const category = $(this).data('category');
1195 + const searchTerm = $('#mxchat_embedding_model_search_input').val();
1196 + populateEmbeddingModelsGrid(searchTerm, category);
1197 + });
1198 +
1199 + $('#mxchat_embedding_model_search_input').on('input.embeddingModelSelector', function() {
1200 + const searchTerm = $(this).val();
1201 + const activeCategory = $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn.active').data('category');
1202 + populateEmbeddingModelsGrid(searchTerm, activeCategory);
1203 + });
1204 +
1205 + // Use a direct selector to avoid conflicts with other card elements
1206 + $(document).on('click.embeddingModelSelector', '.mxchat-embedding-model-selector-grid .mxchat-embedding-model-selector-card', function(e) {
1207 + e.stopPropagation(); // Prevent event bubbling
1208 + const modelValue = $(this).data('value');
1209 +
1210 + // Important: Only update this specific select element
1211 + $embeddingModelSelect.val(modelValue);
1212 +
1213 + // Manually trigger change only on this element
1214 + const changeEvent = new Event('change', { bubbles: true });
1215 + $embeddingModelSelect[0].dispatchEvent(changeEvent);
1216 +
1217 + // Update button text
1218 + updateButtonText();
1219 +
1220 + // Hide modal
1221 + $('#' + embeddingModalId).hide();
1222 + });
1223 +
1224 + // Close modal when clicking outside - use namespaced events
1225 + $(window).on('click.embeddingModelSelector', function(event) {
1226 + if ($(event.target).is('#' + embeddingModalId)) {
1227 + $('#' + embeddingModalId).hide();
1228 + }
1229 + });
1230 +}
1231 +
1232 +// Call this function after the DOM is fully loaded
1233 +$(document).ready(function() {
1234 + setupMxChatModelSelector();
1235 + setupMxChatEmbeddingModelSelector();
1236 +});
1237 +
1238 + // Initialize API key visibility
1239 + setupAPIKeyVisibility();
1240 +
350 1241 // Add Intent Form Submission
351 1242 $('#mxchat-add-intent-form').on('submit', function(event) {
352 1243 $('#mxchat-intent-loading').show();
353 1244 $('#mxchat-intent-loading-text').show();
@@ -411,50 +1302,9 @@
411 1302 }
412 1303 });
413 1304 });
414 1305
415 - // Activation handling
416 - const form = $('#mxchat-activation-form');
417 - const spinner = $('#mxchat-activation-spinner');
418 - const submitButton = $('#activate_license_button');
419 - const licenseStatus = $('#mxchat-license-status');
420 1306
421 - if (form.length && licenseStatus.length && submitButton.length) {
422 - function handleActivationResponse(response) {
423 - spinner.hide();
424 - if (response.success) {
425 - licenseStatus.text('Active');
426 - licenseStatus.removeClass('inactive').addClass('active');
427 - form.hide();
428 - } else {
429 - licenseStatus.text('Inactive');
430 - alert(response.data || 'Activation failed. Please check your input.');
431 - submitButton.prop('disabled', false);
432 - }
433 - }
434 -
435 - form.on('submit', function(event) {
436 - event.preventDefault();
437 - spinner.show();
438 - submitButton.prop('disabled', true);
439 -
440 - var formData = {
441 - action: 'mxchat_activate_license',
442 - mxchat_pro_email: $('#mxchat_pro_email').val(),
443 - mxchat_activation_key: $('#mxchat_activation_key').val(),
444 - security: mxchatAdmin.license_nonce
445 - };
446 -
447 - $.post(mxchatAdmin.ajax_url, formData, function(response) {
448 - handleActivationResponse(response);
449 - }).fail(function() {
450 - alert('Server error. Please try again.');
451 - spinner.hide();
452 - submitButton.prop('disabled', false);
453 - });
454 - });
455 - }
456 -
457 1307 // Questions handling
458 1308 $('.mxchat-add-question').on('click', function () {
459 1309 const container = $('#mxchat-additional-questions-container');
460 1310 const questionCount = container.find('.mxchat-question-row').length + 4;
@@ -544,9 +1394,1282 @@
544 1394 }
545 1395 });
546 1396 }
547 1397
1398 + // Function to adjust the textarea height to content
1399 + function adjustTextareaHeight() {
1400 + this.style.height = 'auto'; // Reset to auto to calculate scrollHeight
1401 + this.style.height = this.scrollHeight + 'px'; // Expand to content height
1402 + }
1403 +
1404 + // Function to reset the textarea height to initial
1405 + function resetTextareaHeight() {
1406 + this.style.height = ''; // Remove inline height, reverting to CSS default
1407 + }
1408 +
1409 + // Target the specific textarea by ID
1410 + var $textarea = $('#system_prompt_instructions');
1411 +
1412 + // Bind events
1413 + $textarea.on('focus input', adjustTextareaHeight) // Expand on focus and input
1414 + .on('blur', resetTextareaHeight); // Reset on blur
1415 +});
1416 +
1417 +
1418 +
1419 +document.addEventListener('DOMContentLoaded', function() {
1420 + // Check if we're on the correct page before initializing
1421 + const modal = document.getElementById('mxchat-action-modal');
548 1422
1423 + // Only initialize if the modal exists on this page
1424 + if (modal) {
1425 + //console.log('MXChat Action Modal JS Loaded');
1426 +
1427 + // Initialize the action modal functionality
1428 + initStepBasedActionModal();
1429 + }
1430 +
1431 + // Function to initialize the step-based action modal
1432 + function initStepBasedActionModal() {
1433 + // We already checked for modal existence above, so no need to check again
1434 +
1435 + const actionStep1 = document.getElementById('mxchat-action-step-1');
1436 + const actionStep2 = document.getElementById('mxchat-action-step-2');
1437 + const backToStep1Btn = document.getElementById('mxchat-back-to-step-1');
1438 + const searchInput = document.getElementById('action-type-search');
1439 + const categoryButtons = modal.querySelectorAll('.mxchat-category-button');
1440 + const actionCards = modal.querySelectorAll('.mxchat-action-type-card');
1441 + const actionForm = document.getElementById('mxchat-action-form');
1442 + const callbackInput = document.getElementById('callback_function');
1443 + const actionIdField = document.getElementById('edit_action_id');
1444 + const labelField = document.getElementById('intent_label');
1445 + const phrasesField = document.getElementById('action_phrases');
1446 + const formActionType = document.getElementById('form_action_type');
1447 + const nonceContainer = document.getElementById('action-nonce-container');
1448 + const thresholdSlider = document.getElementById('similarity_threshold');
1449 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
1450 +
1451 + // Rest of your initialization code remains the same...
1452 +
1453 + // Log the structure of one action card for debugging
1454 + if (actionCards.length > 0) {
1455 + //console.log('First action card data attributes:', actionCards[0].dataset);
1456 + //console.log('First action card HTML:', actionCards[0].outerHTML);
1457 + }
1458 +
1459 + // Add click event listeners to category buttons
1460 + categoryButtons.forEach(button => {
1461 + button.addEventListener('click', function() {
1462 + //console.log('Category button clicked:', this.dataset.category);
1463 +
1464 + // Remove active class from all buttons
1465 + categoryButtons.forEach(btn => btn.classList.remove('active'));
1466 +
1467 + // Add active class to clicked button
1468 + this.classList.add('active');
1469 +
1470 + // Get selected category
1471 + const category = this.dataset.category;
1472 +
1473 + // Filter action cards
1474 + filterActionCards(category, searchInput.value);
1475 + });
1476 + });
1477 +
1478 + // Add search functionality
1479 + if (searchInput) {
1480 + searchInput.addEventListener('input', function() {
1481 + // Get active category
1482 + const activeCategory = modal.querySelector('.mxchat-category-button.active')?.dataset.category || 'all';
1483 + //console.log('Search input changed, active category:', activeCategory);
1484 +
1485 + // Filter action cards
1486 + filterActionCards(activeCategory, this.value);
1487 + });
1488 + }
1489 +
1490 + // Add click event listeners to action cards
1491 + actionCards.forEach(card => {
1492 + card.addEventListener('click', function() {
1493 + // Get the action data
1494 + const isPro = this.dataset.pro === 'true';
1495 + const isInstalled = this.dataset.installed === 'true';
1496 + const addonName = this.dataset.addon || '';
1497 + const actionValue = this.dataset.value;
1498 + const actionLabel = this.dataset.label;
1499 + const actionIcon = this.querySelector('.dashicons').getAttribute('class').replace('dashicons dashicons-', '');
1500 + const actionDescription = this.querySelector('p').textContent;
1501 +
1502 + // Pro check using the proper detection method
1503 + const proIsActivated = typeof mxchatAdmin !== 'undefined' &&
1504 + (mxchatAdmin.is_activated === '1' ||
1505 + mxchatAdmin.is_activated === 'true' ||
1506 + mxchatAdmin.is_activated === true);
1507 +
1508 + // Handle different states
1509 + if (isPro && !proIsActivated) {
1510 + // Pro feature but no Pro license
1511 + showProFeatureNotice();
1512 + return;
1513 + }
1514 +
1515 + if (addonName && !isInstalled) {
1516 + // Add-on required but not installed
1517 + const addonDisplayName = this.querySelector('.mxchat-addon-info')?.textContent?.replace('Requires ', '') || addonName + ' Add-on';
1518 + showAddonRequiredNotice(addonDisplayName);
1519 + return;
1520 + }
1521 +
1522 + // If we get here, the action is available - proceed as normal
1523 + callbackInput.value = actionValue;
1524 +
1525 + // Update the selected action display in step 2
1526 + document.getElementById('selected-action-title').textContent = actionLabel;
1527 + document.getElementById('selected-action-description').textContent = actionDescription;
1528 + document.getElementById('selected-action-icon').innerHTML =
1529 + `<span class="dashicons dashicons-${actionIcon}"></span>`;
1530 +
1531 + // Set a default label based on the action type (user can change it)
1532 + if (!labelField.value) {
1533 + labelField.value = actionLabel;
1534 + }
1535 +
1536 + // Move to step 2
1537 + actionStep1.classList.remove('active');
1538 + actionStep2.classList.add('active');
1539 +
1540 + // Update modal title
1541 + });
1542 + });
1543 +
1544 + // Back button functionality
1545 + if (backToStep1Btn) {
1546 + backToStep1Btn.addEventListener('click', function() {
1547 + //console.log('Back button clicked');
1548 + actionStep2.classList.remove('active');
1549 + actionStep1.classList.add('active');
1550 + });
1551 + }
1552 +
1553 + // Function to filter action cards by category and search term
1554 + function filterActionCards(category, searchTerm) {
1555 + searchTerm = searchTerm.toLowerCase().trim();
1556 + //console.log(`Filtering cards by category: "${category}", search: "${searchTerm}"`);
1557 +
1558 + let visibleCount = 0;
1559 +
1560 + // Show all cards initially with animation
1561 + actionCards.forEach((card, index) => {
1562 + // Reset animation
1563 + card.style.animation = 'none';
1564 + // Trigger reflow
1565 + void card.offsetWidth;
1566 +
1567 + // Determine if card should be visible based on category and search term
1568 + const cardCategory = card.dataset.category || '';
1569 + const matchesCategory = category === 'all' || cardCategory === category;
1570 +
1571 + const cardTitle = card.querySelector('h4')?.textContent?.toLowerCase() || '';
1572 + const cardDesc = card.querySelector('p')?.textContent?.toLowerCase() || '';
1573 + const matchesSearch = searchTerm === '' ||
1574 + cardTitle.includes(searchTerm) ||
1575 + cardDesc.includes(searchTerm);
1576 +
1577 + // Show/hide card with animation
1578 + if (matchesCategory && matchesSearch) {
1579 + card.style.display = 'flex';
1580 + // Staggered animation for cards
1581 + card.style.animation = `fadeIn 0.2s ease forwards ${index * 0.03}s`;
1582 + visibleCount++;
1583 + } else {
1584 + card.style.display = 'none';
1585 + }
1586 + });
1587 +
1588 + //console.log(`Filter results: ${visibleCount} cards visible out of ${actionCards.length}`);
1589 + }
1590 +
1591 + // Function to show notice for Pro features
1592 + function showProFeatureNotice() {
1593 + //console.log('Showing Pro feature notice');
1594 + // Check if we already have a notification container
1595 + let noticeContainer = document.querySelector('.mxchat-pro-notice');
1596 +
1597 + if (!noticeContainer) {
1598 + // Create the notice container
1599 + noticeContainer = document.createElement('div');
1600 + noticeContainer.className = 'mxchat-pro-notice';
1601 +
1602 + // Create content
1603 + noticeContainer.innerHTML = `
1604 + <div class="mxchat-pro-notice-content">
1605 + <h3>MxChat Pro Feature</h3>
1606 + <p>This action is available in the Pro version only.</p>
1607 + <div class="mxchat-pro-notice-buttons">
1608 + <button class="mxchat-button-secondary mxchat-pro-notice-close">Close</button>
1609 + <a href="https://mxchat.ai/" class="mxchat-button-primary">Upgrade to Pro</a>
1610 + </div>
1611 + </div>
1612 + `;
1613 +
1614 + // Append to body
1615 + document.body.appendChild(noticeContainer);
1616 +
1617 + // Add close functionality
1618 + const closeButton = noticeContainer.querySelector('.mxchat-pro-notice-close');
1619 + closeButton.addEventListener('click', function() {
1620 + noticeContainer.classList.remove('active');
1621 + setTimeout(() => {
1622 + noticeContainer.remove();
1623 + }, 300);
1624 + });
1625 +
1626 + // Click outside to close
1627 + noticeContainer.addEventListener('click', function(e) {
1628 + if (e.target === noticeContainer) {
1629 + closeButton.click();
1630 + }
1631 + });
1632 +
1633 + // Show with animation
1634 + setTimeout(() => {
1635 + noticeContainer.classList.add('active');
1636 + }, 10);
1637 + } else {
1638 + // If it already exists, just make it visible again
1639 + noticeContainer.classList.add('active');
1640 + }
1641 + }
1642 +
1643 + // Function to show notice for add-on requirements
1644 + function showAddonRequiredNotice(addonName) {
1645 + //console.log(`Showing add-on notice for: ${addonName}`);
1646 + // Check if we already have a notification container
1647 + let noticeContainer = document.querySelector('.mxchat-addon-notice');
1648 +
1649 + if (!noticeContainer) {
1650 + // Create the notice container
1651 + noticeContainer = document.createElement('div');
1652 + noticeContainer.className = 'mxchat-addon-notice';
1653 +
1654 + // Create content
1655 + noticeContainer.innerHTML = `
1656 + <div class="mxchat-addon-notice-content">
1657 + <span class="mxchat-addon-notice-icon">🧩</span>
1658 + <h3>Add-on Required</h3>
1659 + <p>This action requires the <strong>${addonName}</strong> add-on to be installed.</p>
1660 + <div class="mxchat-addon-notice-buttons">
1661 + <button class="mxchat-button-secondary mxchat-addon-notice-close">Close</button>
1662 + <a href="admin.php?page=mxchat-addons" class="mxchat-button-primary">Get Add-ons</a>
1663 + </div>
1664 + </div>
1665 + `;
1666 +
1667 + // Append to body
1668 + document.body.appendChild(noticeContainer);
1669 +
1670 + // Add close functionality
1671 + const closeButton = noticeContainer.querySelector('.mxchat-addon-notice-close');
1672 + closeButton.addEventListener('click', function() {
1673 + noticeContainer.classList.remove('active');
1674 + setTimeout(() => {
1675 + noticeContainer.remove();
1676 + }, 300);
1677 + });
1678 +
1679 + // Click outside to close
1680 + noticeContainer.addEventListener('click', function(e) {
1681 + if (e.target === noticeContainer) {
1682 + closeButton.click();
1683 + }
1684 + });
1685 +
1686 + // Show with animation
1687 + setTimeout(() => {
1688 + noticeContainer.classList.add('active');
1689 + }, 10);
1690 + } else {
1691 + // If it already exists, update the content
1692 + const addonNameElement = noticeContainer.querySelector('p strong');
1693 + if (addonNameElement) {
1694 + addonNameElement.textContent = addonName;
1695 + }
1696 +
1697 + // Make it visible again
1698 + noticeContainer.classList.add('active');
1699 + }
1700 + }
1701 +
1702 + // Form submission handling
1703 + if (actionForm) {
1704 + actionForm.addEventListener('submit', function() {
1705 + //console.log('Form submitted');
1706 + document.getElementById('mxchat-action-loading').style.display = 'flex';
1707 + this.querySelector('button[type="submit"]').disabled = true;
1708 + });
1709 + }
1710 + }
1711 +
1712 + // Setup add action buttons (only if we're on the correct page)
1713 + if (modal) {
1714 + // Update the modal open function to support the step-based flow
1715 + window.mxchatOpenActionModal = function(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '') {
1716 + //console.log('Modal opening, edit mode:', isEdit);
1717 +
1718 + // No need to check again, we already verified modal exists
1719 +
1720 + // Get form fields
1721 + const actionIdField = document.getElementById('edit_action_id');
1722 + const labelField = document.getElementById('intent_label');
1723 + const phrasesField = document.getElementById('action_phrases');
1724 + const formActionType = document.getElementById('form_action_type');
1725 + const callbackInput = document.getElementById('callback_function');
1726 + const saveButton = document.getElementById('mxchat-save-action-btn');
1727 + const nonceContainer = document.getElementById('action-nonce-container');
1728 + const thresholdSlider = document.getElementById('similarity_threshold');
1729 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
1730 + const actionStep1 = document.getElementById('mxchat-action-step-1');
1731 + const actionStep2 = document.getElementById('mxchat-action-step-2');
1732 + const searchInput = document.getElementById('action-type-search');
1733 +
1734 + // Set up modal for edit or create
1735 + if (isEdit) {
1736 + saveButton.textContent = 'Update Action';
1737 + formActionType.value = 'mxchat_edit_intent';
1738 + actionIdField.value = actionId;
1739 + labelField.value = label;
1740 + phrasesField.value = phrases;
1741 + callbackInput.value = callbackFunction;
1742 + thresholdSlider.value = threshold; // Set the current threshold value
1743 + thresholdDisplay.textContent = threshold + '%'; // Update display
1744 +
1745 + // Update the nonce field for editing
1746 + nonceContainer.innerHTML = ''; // Clear existing nonce
1747 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) {
1748 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.edit_intent_nonce}">`;
1749 + }
1750 +
1751 + // For editing, go directly to step 2 and update the selected action display
1752 + actionStep1.classList.remove('active');
1753 + actionStep2.classList.add('active');
1754 +
1755 + // Find the matching action card to get its details
1756 + const actionCards = document.querySelectorAll('.mxchat-action-type-card');
1757 + let foundCard = null;
1758 +
1759 + actionCards.forEach(card => {
1760 + if (card.dataset.value === callbackFunction) {
1761 + foundCard = card;
1762 + }
1763 + });
1764 +
1765 + if (foundCard) {
1766 + //console.log('Found matching action card for:', callbackFunction);
1767 + const actionLabel = foundCard.dataset.label || foundCard.querySelector('h4')?.textContent || '';
1768 + const actionIconElement = foundCard.querySelector('.dashicons');
1769 + const actionIcon = actionIconElement
1770 + ? actionIconElement.getAttribute('class').replace('dashicons dashicons-', '')
1771 + : 'admin-generic';
1772 + const actionDescription = foundCard.querySelector('p')?.textContent || '';
1773 +
1774 + document.getElementById('selected-action-title').textContent = actionLabel;
1775 + document.getElementById('selected-action-description').textContent = actionDescription;
1776 + document.getElementById('selected-action-icon').innerHTML =
1777 + `<span class="dashicons dashicons-${actionIcon}"></span>`;
1778 + } else {
1779 + //console.log('No matching action card found for:', callbackFunction);
1780 + // Fallback if we can't find the card
1781 + document.getElementById('selected-action-title').textContent = label;
1782 + document.getElementById('selected-action-description').textContent = 'Configure this action for your chatbot';
1783 + document.getElementById('selected-action-icon').innerHTML =
1784 + `<span class="dashicons dashicons-admin-generic"></span>`;
1785 + }
1786 + } else {
1787 + //console.log('Setting up create mode');
1788 + saveButton.textContent = 'Save Action';
1789 + formActionType.value = 'mxchat_add_intent';
1790 + actionIdField.value = '';
1791 + labelField.value = '';
1792 + phrasesField.value = '';
1793 + callbackInput.value = '';
1794 + thresholdSlider.value = 85; // Default value for new actions
1795 + thresholdDisplay.textContent = '85%'; // Default display
1796 +
1797 + // Update the nonce field for adding
1798 + nonceContainer.innerHTML = ''; // Clear existing nonce
1799 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) {
1800 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.add_intent_nonce}">`;
1801 + }
1802 +
1803 + // For creating new, start at step 1
1804 + actionStep1.classList.add('active');
1805 + actionStep2.classList.remove('active');
1806 + }
1807 +
1808 + // Show modal with animation
1809 + modal.style.display = 'flex';
1810 + requestAnimationFrame(() => {
1811 + modal.classList.add('active');
1812 + });
1813 +
1814 + // Set up close handlers
1815 + const closeModal = () => {
1816 + //console.log('Closing modal');
1817 + modal.classList.remove('active');
1818 + setTimeout(() => {
1819 + modal.style.display = 'none';
1820 + }, 300); // Match the CSS transition time
1821 + };
1822 +
1823 + // Close button handler
1824 + const closeBtn = modal.querySelector('.mxchat-modal-close');
1825 + if (closeBtn) {
1826 + closeBtn.onclick = closeModal;
1827 + }
1828 +
1829 + // Cancel button handler
1830 + const cancelBtns = modal.querySelectorAll('.mxchat-modal-cancel');
1831 + if (cancelBtns) {
1832 + cancelBtns.forEach(btn => {
1833 + btn.onclick = closeModal;
1834 + });
1835 + }
1836 +
1837 + // Click outside modal to close
1838 + modal.onclick = (e) => {
1839 + if (e.target === modal) {
1840 + closeModal();
1841 + }
1842 + };
1843 +
1844 + // Escape key to close modal
1845 + document.addEventListener('keydown', function(e) {
1846 + if (e.key === 'Escape' && modal.classList.contains('active')) {
1847 + closeModal();
1848 + }
1849 + }, { once: true });
1850 +
1851 + // Focus appropriate field based on current step
1852 + if (isEdit || actionStep2.classList.contains('active')) {
1853 + if (labelField) labelField.focus();
1854 + } else {
1855 + if (searchInput) searchInput.focus();
1856 + }
1857 +
1858 + return closeModal; // Return close function for external use
1859 + };
1860 +
1861 + // Setup add action buttons
1862 + const addActionBtn = document.getElementById('mxchat-add-action-btn');
1863 + if (addActionBtn) {
1864 + //console.log('Add action button found');
1865 + addActionBtn.onclick = () => window.mxchatOpenActionModal();
1866 + }
1867 +
1868 + const createFirstAction = document.getElementById('mxchat-create-first-action');
1869 + if (createFirstAction) {
1870 + //console.log('Create first action button found');
1871 + createFirstAction.onclick = () => window.mxchatOpenActionModal();
1872 + }
1873 +
1874 + // Setup edit buttons
1875 + const editButtons = document.querySelectorAll('.mxchat-action-card .mxchat-edit-button');
1876 + //console.log('Edit buttons found:', editButtons.length);
1877 + editButtons.forEach(button => {
1878 + button.onclick = () => {
1879 + const actionId = button.dataset.actionId;
1880 + const phrases = button.dataset.phrases;
1881 + const label = button.dataset.label;
1882 + const threshold = button.dataset.threshold || 85;
1883 + const callbackFunction = button.dataset.callbackFunction;
1884 +
1885 + window.mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction);
1886 + };
1887 + });
1888 + }
549 1889 });
550 1890
1891 +jQuery(document).ready(function($) {
1892 + // Toggle custom post types container
1893 + $('#mxchat-custom-post-types-toggle').on('click', function(e) {
1894 + e.preventDefault();
1895 +
1896 + $('#mxchat-custom-post-types-container').slideToggle(300);
1897 +
1898 + // Rotate the toggle icon
1899 + const $icon = $(this).find('.mxchat-accordion-icon');
1900 + if ($('#mxchat-custom-post-types-container').is(':visible')) {
1901 + $icon.css('transform', 'rotate(180deg)');
1902 + $(this).closest('.mxchat-settings-accordion').addClass('active');
1903 + } else {
1904 + $icon.css('transform', 'rotate(0deg)');
1905 + $(this).closest('.mxchat-settings-accordion').removeClass('active');
1906 + }
1907 + });
1908 +
1909 + // If there are any selections made, auto-expand the container
1910 + function autoExpandIfNeeded() {
1911 + // Check if any checkbox in the container is checked
1912 + const hasCheckedItems = $('#mxchat-custom-post-types-container input[type="checkbox"]:checked').length > 0;
1913 +
1914 + if (hasCheckedItems) {
1915 + $('#mxchat-custom-post-types-container').show();
1916 + $('#mxchat-custom-post-types-toggle .mxchat-accordion-icon').css('transform', 'rotate(180deg)');
1917 + $('.mxchat-settings-accordion').addClass('active');
1918 + }
1919 + }
1920 +
1921 + // Run on page load
1922 + autoExpandIfNeeded();
1923 +});
551 1924
1925 +jQuery(document).ready(function($) {
1926 + // Track if a form has been submitted to trigger updates
1927 + let formSubmitted = false;
1928 +
1929 + // Global interval ID to manage the polling
1930 + let updateIntervalId = null;
1931 +
1932 + // Check if we're on the right admin page with status cards or import forms
1933 + if ($('.mxchat-status-card').length > 0 || $('.mxchat-import-options').length > 0) {
1934 + //console.log('MxChat: Status update script initialized');
1935 + // Initialize AJAX status updates
1936 + initStatusUpdates();
1937 + }
1938 +
1939 + // Initialize status updates
1940 +// Initialize status updates
1941 +function initStatusUpdates() {
1942 + // Get the refresh interval (default to 2 seconds for more responsive updates)
1943 + const refreshInterval = parseInt(mxchatAdmin.status_refresh_interval || 2000);
1944 +
1945 + // Check if there are active status cards
1946 + const hasActiveStatus = $('.mxchat-status-card').length > 0;
1947 +
1948 + // Set up form submission listeners
1949 + $('#mxchat-url-form, #mxchat-content-form').on('submit', function() {
1950 + console.log('MxChat: Form submitted, will start checking for updates');
1951 + formSubmitted = true;
1952 +
1953 + // Store submission info in sessionStorage to persist through redirects
1954 + sessionStorage.setItem('mxchat_form_submitted', 'true');
1955 + sessionStorage.setItem('mxchat_form_submitted_time', Date.now());
1956 +
1957 + // Start checking for status updates right away
1958 + startPolling(refreshInterval);
1959 +
1960 + // Create a temporary message
1961 + if ($('.mxchat-processing-message').length === 0) {
1962 + const message = $('<div class="mxchat-processing-message" style="text-align: center; padding: 15px; background: #f0f7ff; border-radius: 8px; margin-top: 15px;">Processing request... Status will update automatically.</div>');
1963 + $('.mxchat-import-section').after(message);
1964 +
1965 + // Fade out after 5 seconds
1966 + setTimeout(function() {
1967 + message.fadeOut(500, function() {
1968 + $(this).remove();
1969 + });
1970 + }, 5000);
1971 + }
1972 + });
1973 +
1974 + // Listen for import option clicks
1975 + $('.mxchat-import-box').on('click', function() {
1976 + const option = $(this).data('option');
1977 + console.log('MxChat: Import option clicked - ' + option);
1978 + });
1979 +
1980 + // Check if we recently submitted a form (within last 60 seconds for sitemap processing)
1981 + if (sessionStorage.getItem('mxchat_form_submitted') === 'true') {
1982 + const submittedTime = parseInt(sessionStorage.getItem('mxchat_form_submitted_time') || '0');
1983 + if (Date.now() - submittedTime < 60000) { // 60 seconds
1984 + console.log('MxChat: Detected recent form submission via sessionStorage');
1985 + formSubmitted = true;
1986 + } else {
1987 + // Clear old submission data
1988 + sessionStorage.removeItem('mxchat_form_submitted');
1989 + sessionStorage.removeItem('mxchat_form_submitted_time');
1990 + }
1991 + }
1992 +
1993 + // Attach event listener to stop button to clear the interval
1994 + $('.mxchat-stop-form').on('submit', function() {
1995 + console.log('MxChat: Stop processing requested, clearing update interval');
1996 + stopPolling();
1997 + sessionStorage.removeItem('mxchat_form_submitted');
1998 + sessionStorage.removeItem('mxchat_form_submitted_time');
1999 + });
2000 +
2001 + // Start the interval for automatic updates if we have status cards or a form was submitted
2002 + if (hasActiveStatus || formSubmitted) {
2003 + console.log('MxChat: Starting automatic status checks');
2004 + startPolling(refreshInterval);
2005 + }
2006 +}
2007 +
2008 + // Function to start polling
2009 + function startPolling(interval) {
2010 + // Clear any existing interval first
2011 + stopPolling();
2012 +
2013 + // Do an initial fetch immediately
2014 + fetchStatusUpdates();
2015 +
2016 + // Set up new interval
2017 + updateIntervalId = setInterval(function() {
2018 + fetchStatusUpdates();
2019 + }, interval);
2020 +
2021 + //console.log('MxChat: Polling started with interval', interval);
2022 + }
2023 +
2024 + // Function to stop polling
2025 + function stopPolling() {
2026 + if (updateIntervalId !== null) {
2027 + clearInterval(updateIntervalId);
2028 + updateIntervalId = null;
2029 + //console.log('MxChat: Polling stopped');
2030 + }
2031 + }
2032 +
2033 +// Fetch status updates from the server
2034 +function fetchStatusUpdates() {
2035 + // If user is actively viewing the failed URLs, don't refresh as frequently
2036 + const $details = $('.mxchat-failed-urls-container details');
2037 + const isUserViewing = $details.length > 0 && $details.prop('open');
2038 +
2039 + // If details are open, we'll refresh at a slower rate
2040 + if (isUserViewing) {
2041 + // Alternative: Update less frequently when details are open
2042 + setTimeout(function() {
2043 + performStatusUpdate(false); // Pass false for normal updates
2044 + }, 5000); // Slow down updates to every 5 seconds when details are open
2045 + } else {
2046 + performStatusUpdate(false); // Pass false for normal updates
2047 + }
2048 +}
2049 +
2050 +// Perform the actual AJAX request
2051 +function performStatusUpdate(clearCompleted = false) {
2052 + console.log('MxChat: Checking for status updates...');
2053 +
2054 + $.ajax({
2055 + url: ajaxurl,
2056 + type: 'POST',
2057 + data: {
2058 + action: 'mxchat_get_status_updates',
2059 + nonce: mxchatAdmin.status_nonce,
2060 + clear_completed: clearCompleted ? 'true' : 'false'
2061 + },
2062 + success: function(response) {
2063 + console.log('MxChat: Status update received', response);
2064 +
2065 + // Log specific status details for debugging
2066 + if (response.sitemap_status) {
2067 + console.log('Sitemap status:', response.sitemap_status.status, 'Processed:', response.sitemap_status.processed_urls, 'Total:', response.sitemap_status.total_urls);
2068 + }
2069 +
2070 + // Check for completion BEFORE updating UI
2071 + let shouldReload = false;
2072 +
2073 + if (response.sitemap_status && response.sitemap_status.status === 'complete') {
2074 + console.log('MxChat: Sitemap processing complete, will reload page');
2075 + shouldReload = true;
2076 + }
2077 +
2078 + if (response.pdf_status && response.pdf_status.status === 'complete') {
2079 + console.log('MxChat: PDF processing complete, will reload page');
2080 + shouldReload = true;
2081 + }
2082 +
2083 + // Always update UI first
2084 + if ((response && response.is_processing) || formSubmitted || shouldReload) {
2085 + updateStatusUI(response);
2086 + }
2087 +
2088 + // Show single URL status if available and no active processing
2089 + if (response.single_url_status && !response.is_processing) {
2090 + updateSingleUrlStatus(response.single_url_status);
2091 + }
2092 +
2093 + // Handle completion
2094 + if (shouldReload) {
2095 + // Clear session storage
2096 + sessionStorage.removeItem('mxchat_form_submitted');
2097 + sessionStorage.removeItem('mxchat_form_submitted_time');
2098 +
2099 + // Stop polling
2100 + stopPolling();
2101 +
2102 + // Make one more request to clear the completed status
2103 + setTimeout(function() {
2104 + console.log('MxChat: Clearing completed status and reloading');
2105 + performStatusUpdate(true); // Pass true to clear completed
2106 +
2107 + // Then reload after a short delay
2108 + setTimeout(function() {
2109 + location.reload();
2110 + }, 500);
2111 + }, 1500);
2112 +
2113 + return; // Exit early
2114 + }
2115 +
2116 + // Reset form submitted flag if no active processing
2117 + if (!response.is_processing) {
2118 + formSubmitted = false;
2119 + sessionStorage.removeItem('mxchat_form_submitted');
2120 + sessionStorage.removeItem('mxchat_form_submitted_time');
2121 + stopPolling();
2122 + }
2123 + },
2124 + error: function(xhr, status, error) {
2125 + console.error('MxChat: Status update failed:', error);
2126 + }
2127 + });
2128 +}
2129 +
2130 + // Update the UI with status information
2131 + function updateStatusUI(data) {
2132 + // Update PDF status if available
2133 + if (data.pdf_status) {
2134 + updatePdfStatus(data.pdf_status);
2135 + }
2136 +
2137 + // Update sitemap status if available
2138 + if (data.sitemap_status) {
2139 + updateSitemapStatus(data.sitemap_status);
2140 + }
2141 +
2142 + // Handle single URL status if available and no active processing
2143 + if (data.single_url_status && !data.is_processing) {
2144 + updateSingleUrlStatus(data.single_url_status);
2145 + } else if (data.is_processing) {
2146 + // Hide single URL status while processing
2147 + $('#mxchat-single-url-status-container').hide();
2148 + }
2149 + }
2150 +
2151 + // Update PDF status card
2152 + function updatePdfStatus(status) {
2153 + // Check if PDF card exists
2154 + let $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
2155 +
2156 + // If no card exists but we have status, create it
2157 + if ($pdfCard.length === 0 && status) {
2158 + //console.log('MxChat: Creating new PDF status card');
2159 + createPdfStatusCard(status);
2160 + $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
2161 + }
2162 +
2163 + // If card exists, update it
2164 + if ($pdfCard.length > 0) {
2165 + // Update progress bar
2166 + $pdfCard.find('.mxchat-progress-fill').css('width', status.percentage + '%');
2167 +
2168 + // Update progress text
2169 + $pdfCard.find('.mxchat-status-details p:first').text(
2170 + 'Progress: ' + status.processed_pages + ' of ' +
2171 + status.total_pages + ' pages (' + status.percentage + '%)'
2172 + );
2173 +
2174 + // Update status text (if it exists)
2175 + const $statusText = $pdfCard.find('.mxchat-status-details p:nth-child(2)');
2176 + if ($statusText.length > 0) {
2177 + $statusText.text('Status: ' + status.status.charAt(0).toUpperCase() + status.status.slice(1));
2178 + }
2179 +
2180 + // Update last update text (if it exists)
2181 + const $lastUpdateText = $pdfCard.find('.mxchat-status-details p:nth-child(3)');
2182 + if ($lastUpdateText.length > 0) {
2183 + $lastUpdateText.text('Last update: ' + status.last_update);
2184 + }
2185 +
2186 + // If we have an error, show it
2187 + if (status.status === 'error' && status.error) {
2188 + let $errorNotice = $pdfCard.find('.mxchat-error-notice');
2189 +
2190 + if ($errorNotice.length === 0) {
2191 + $errorNotice = $('<div class="mxchat-error-notice"><p class="error"></p></div>');
2192 + $pdfCard.find('.mxchat-status-details').append($errorNotice);
2193 + }
2194 +
2195 + $errorNotice.find('p.error').text(status.error);
2196 +
2197 + // Make sure error badge is shown
2198 + if ($pdfCard.find('.mxchat-status-badge.mxchat-status-failed').length === 0) {
2199 + $pdfCard.find('.mxchat-status-header').append('<span class="mxchat-status-badge mxchat-status-failed">Error</span>');
2200 + }
2201 + }
2202 + }
2203 + }
2204 +
2205 + // Create a new PDF status card
2206 + function createPdfStatusCard(status) {
2207 + let html = '<div class="mxchat-status-card">';
2208 + html += '<div class="mxchat-status-header">';
2209 + html += '<h4>PDF Processing Status</h4>';
2210 +
2211 + // Add stop processing form if processing
2212 + if (status.status === 'processing') {
2213 + html += '<form method="post" class="mxchat-stop-form" action="' +
2214 + mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_stop_processing">';
2215 + html += '<input type="hidden" name="mxchat_stop_processing_nonce" value="' +
2216 + mxchatAdmin.stop_nonce + '">';
2217 + html += '<button type="submit" name="stop_processing" class="mxchat-button-secondary">';
2218 + html += 'Stop Processing</button></form>';
2219 + }
2220 +
2221 + // Add error badge if error
2222 + if (status.status === 'error') {
2223 + html += '<span class="mxchat-status-badge mxchat-status-failed">Error</span>';
2224 + }
2225 +
2226 + html += '</div>'; // End header
2227 +
2228 + // Progress bar
2229 + html += '<div class="mxchat-progress-bar">';
2230 + html += '<div class="mxchat-progress-fill" style="width: ' + status.percentage + '%"></div>';
2231 + html += '</div>';
2232 +
2233 + // Status details
2234 + html += '<div class="mxchat-status-details">';
2235 + html += '<p>Progress: ' + status.processed_pages + ' of ' +
2236 + status.total_pages + ' pages (' + status.percentage + '%)</p>';
2237 + html += '<p>Status: ' + status.status.charAt(0).toUpperCase() + status.status.slice(1) + '</p>';
2238 + html += '<p>Last update: ' + status.last_update + '</p>';
2239 +
2240 + // Add error message if any
2241 + if (status.status === 'error' && status.error) {
2242 + html += '<div class="mxchat-error-notice">';
2243 + html += '<p class="error">' + status.error + '</p>';
2244 + html += '</div>';
2245 + }
2246 +
2247 + html += '</div>'; // End details
2248 + html += '</div>'; // End card
2249 +
2250 + // Try to find the import tab content to insert the status card into
2251 + let $importTabContent = $('#mxchat-kb-tab-import');
2252 + if ($importTabContent.length > 0) {
2253 + // For the tabbed interface, add to the import tab
2254 + let $sitemapCard = $importTabContent.find('.mxchat-status-card:contains("Sitemap Processing")');
2255 + if ($sitemapCard.length > 0) {
2256 + $sitemapCard.before($(html));
2257 + } else {
2258 + $importTabContent.find('.mxchat-import-section').after($(html));
2259 + }
2260 + } else {
2261 + // Fallback to the old method
2262 + let $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
2263 + if ($sitemapCard.length > 0) {
2264 + $sitemapCard.before($(html));
2265 + } else {
2266 + $('.mxchat-import-section').after($(html));
2267 + }
2268 + }
2269 + }
2270 +
2271 + // Update sitemap status card
2272 + function updateSitemapStatus(status) {
2273 + // Check if sitemap card exists
2274 + let $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
2275 +
2276 + // If no card exists but we have status, create it
2277 + if ($sitemapCard.length === 0 && status) {
2278 + //console.log('MxChat: Creating new sitemap status card');
2279 + createSitemapStatusCard(status);
2280 + $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
2281 + }
2282 +
2283 + // If card exists, update it
2284 + if ($sitemapCard.length > 0) {
2285 + // Update progress bar
2286 + $sitemapCard.find('.mxchat-progress-fill').css('width', status.percentage + '%');
2287 +
2288 + // Update progress text
2289 + $sitemapCard.find('.mxchat-status-details p:first').text(
2290 + 'Progress: ' + status.processed_urls + ' of ' +
2291 + status.total_urls + ' URLs (' + status.percentage + '%)'
2292 + );
2293 +
2294 + // Check if details is already open before updating
2295 + const isDetailsOpen = $sitemapCard.find('.mxchat-failed-urls-container details').prop('open');
2296 +
2297 + // Update errors display
2298 + let $errorContainer = $sitemapCard.find('.mxchat-error-notice');
2299 +
2300 + if ($errorContainer.length === 0 &&
2301 + (status.error || status.last_error || (status.failed_urls_list && status.failed_urls_list.length > 0))) {
2302 + // Create error container if it doesn't exist
2303 + $errorContainer = $('<div class="mxchat-error-notice"></div>');
2304 + $sitemapCard.find('.mxchat-status-details').append($errorContainer);
2305 + }
2306 +
2307 + // Update or create error notices
2308 + if ($errorContainer.length > 0) {
2309 + let errorHTML = '';
2310 +
2311 + if (status.error) {
2312 + errorHTML += '<p class="error">' + status.error + '</p>';
2313 + }
2314 +
2315 + if (status.last_error) {
2316 + errorHTML += '<p class="last-error">Last error: ' + status.last_error + '</p>';
2317 + }
2318 +
2319 + // Add failed URLs list
2320 + if (status.failed_urls_list && status.failed_urls_list.length > 0) {
2321 + errorHTML += '<div class="mxchat-failed-urls-container">';
2322 + errorHTML += '<h5>Failed URLs (' + status.failed_urls_list.length + ')</h5>';
2323 +
2324 + // Set the 'open' attribute based on previous state
2325 + errorHTML += '<details' + (isDetailsOpen ? ' open' : '') + '>';
2326 + errorHTML += '<summary>Show Failed URLs</summary>';
2327 + errorHTML += '<div class="mxchat-failed-urls-list">';
2328 +
2329 + // Create table for failed URLs
2330 + errorHTML += '<table class="widefat striped">';
2331 + errorHTML += '<thead><tr><th>URL</th><th>Error</th><th>Time</th></tr></thead>';
2332 + errorHTML += '<tbody>';
2333 +
2334 + // Sort failed URLs by most recent
2335 + const sortedFailedUrls = [...status.failed_urls_list].sort((a, b) => b.time - a.time);
2336 +
2337 + // Show up to 50 failed URLs
2338 + const displayUrls = sortedFailedUrls.slice(0, 50);
2339 +
2340 + displayUrls.forEach(item => {
2341 + const timeAgo = formatTimeAgo(item.time);
2342 + errorHTML += '<tr>';
2343 + errorHTML += '<td style="word-break: break-all;">';
2344 + errorHTML += '<a href="' + item.url + '" target="_blank" rel="noopener noreferrer">';
2345 + errorHTML += truncateUrl(item.url) + '</a></td>';
2346 + errorHTML += '<td>' + item.error + '</td>';
2347 + errorHTML += '<td>' + timeAgo + '</td>';
2348 + errorHTML += '</tr>';
2349 + });
2350 +
2351 + errorHTML += '</tbody></table>';
2352 +
2353 + if (status.failed_urls_list.length > 50) {
2354 + errorHTML += '<div class="mxchat-failed-urls-more">+ ' +
2355 + (status.failed_urls_list.length - 50) +
2356 + ' more failed URLs not shown</div>';
2357 + }
2358 +
2359 + errorHTML += '</div>'; // End of failed-urls-list
2360 + errorHTML += '</details>';
2361 + errorHTML += '</div>'; // End of failed-urls-container
2362 + }
2363 +
2364 + $errorContainer.html(errorHTML);
2365 +
2366 + // Additionally, add a click handler to pause refreshes when viewing details
2367 + $sitemapCard.find('.mxchat-failed-urls-container details').on('toggle', function() {
2368 + if (this.open) {
2369 + // User opened the details - set a flag
2370 + $(this).data('user-opened', true);
2371 + } else {
2372 + // User closed the details - remove the flag
2373 + $(this).data('user-opened', false);
2374 + }
2375 + });
2376 + }
2377 + }
2378 + }
2379 +
2380 + // Create a new sitemap status card
2381 + function createSitemapStatusCard(status) {
2382 + let html = '<div class="mxchat-status-card">';
2383 + html += '<div class="mxchat-status-header">';
2384 + html += '<h4>Sitemap Processing Status</h4>';
2385 +
2386 + // Add stop processing form if processing
2387 + if (status.status === 'processing') {
2388 + html += '<form method="post" class="mxchat-stop-form" action="' +
2389 + mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_stop_processing">';
2390 + html += '<input type="hidden" name="mxchat_stop_processing_nonce" value="' +
2391 + mxchatAdmin.stop_nonce + '">';
2392 + html += '<button type="submit" name="stop_processing" class="mxchat-button-secondary">';
2393 + html += 'Stop Processing</button></form>';
2394 + }
2395 +
2396 + // Add error badge if error
2397 + if (status.status === 'error') {
2398 + html += '<span class="mxchat-status-badge mxchat-status-failed">Error</span>';
2399 + }
2400 +
2401 + html += '</div>'; // End header
2402 +
2403 + // Progress bar
2404 + html += '<div class="mxchat-progress-bar">';
2405 + html += '<div class="mxchat-progress-fill" style="width: ' + status.percentage + '%"></div>';
2406 + html += '</div>';
2407 +
2408 + // Status details
2409 + html += '<div class="mxchat-status-details">';
2410 + html += '<p>Progress: ' + status.processed_urls + ' of ' +
2411 + status.total_urls + ' URLs (' + status.percentage + '%)</p>';
2412 +
2413 + // Add error message if any
2414 + if ((status.error || status.last_error) && status.status === 'error') {
2415 + html += '<div class="mxchat-error-notice">';
2416 +
2417 + if (status.error) {
2418 + html += '<p class="error">' + status.error + '</p>';
2419 + }
2420 +
2421 + if (status.last_error) {
2422 + html += '<p class="last-error">Last error: ' + status.last_error + '</p>';
2423 + }
2424 +
2425 + html += '</div>';
2426 + }
2427 +
2428 + html += '</div>'; // End details
2429 + html += '</div>'; // End card
2430 +
2431 + // Try to find the import tab content to insert the status card into
2432 + let $importTabContent = $('#mxchat-kb-tab-import');
2433 + if ($importTabContent.length > 0) {
2434 + // For the tabbed interface, add to the import tab
2435 + let $pdfCard = $importTabContent.find('.mxchat-status-card:contains("PDF Processing")');
2436 + if ($pdfCard.length > 0) {
2437 + $pdfCard.after($(html));
2438 + } else {
2439 + $importTabContent.find('.mxchat-import-section').after($(html));
2440 + }
2441 + } else {
2442 + // Fallback to the old method
2443 + let $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
2444 + if ($pdfCard.length > 0) {
2445 + $pdfCard.after($(html));
2446 + } else {
2447 + $('.mxchat-import-section').after($(html));
2448 + }
2449 + }
2450 + }
2451 +
2452 + // Update single URL status
2453 + function updateSingleUrlStatus(status) {
2454 + // Check if container exists
2455 + let $container = $('#mxchat-single-url-status-container');
2456 +
2457 + if ($container.length === 0) {
2458 + // Create container
2459 + $container = $('<div id="mxchat-single-url-status-container"></div>');
2460 +
2461 + // Try to find the import tab content to insert the status card into
2462 + let $importTabContent = $('#mxchat-kb-tab-import');
2463 + if ($importTabContent.length > 0) {
2464 + // For the tabbed interface, add to the import tab
2465 + let $lastStatusCard = $importTabContent.find('.mxchat-status-card').last();
2466 + if ($lastStatusCard.length > 0) {
2467 + $lastStatusCard.after($container);
2468 + } else {
2469 + $importTabContent.find('.mxchat-import-section').after($container);
2470 + }
2471 + } else {
2472 + // Fallback to the old method
2473 + let $lastStatusCard = $('.mxchat-status-card').last();
2474 + if ($lastStatusCard.length > 0) {
2475 + $lastStatusCard.after($container);
2476 + } else {
2477 + $('.mxchat-import-section').after($container);
2478 + }
2479 + }
2480 + }
2481 +
2482 + // Update container content
2483 + let html = '<div class="mxchat-status-card">';
2484 + html += '<div class="mxchat-status-header">';
2485 + html += '<h4>Last URL Submission</h4>';
2486 +
2487 + if (status.status === 'failed') {
2488 + html += '<span class="mxchat-status-badge mxchat-status-failed">Failed</span>';
2489 + } else {
2490 + html += '<span class="mxchat-status-badge mxchat-status-success">Success</span>';
2491 + }
2492 +
2493 + html += '</div>'; // End header
2494 +
2495 + html += '<div class="mxchat-status-details">';
2496 + html += '<p><strong>URL:</strong> ';
2497 + html += '<a href="' + status.url + '" target="_blank">';
2498 +
2499 + // Truncate URL if needed
2500 + const displayUrl = status.url.length > 60 ? status.url.substring(0, 57) + '...' : status.url;
2501 + html += displayUrl;
2502 +
2503 + html += '</a></p>';
2504 + html += '<p><strong>Submitted:</strong> ' + status.human_time + '</p>';
2505 +
2506 + if (status.status === 'failed' && status.error) {
2507 + html += '<div class="mxchat-error-notice">';
2508 + html += '<p class="error">' + status.error + '</p>';
2509 + html += '</div>';
2510 + }
2511 +
2512 + if (status.status === 'complete') {
2513 + html += '<p><strong>Content Length:</strong> ' + status.content_length + ' characters</p>';
2514 + html += '<p><strong>Embedding Dimensions:</strong> ' + status.embedding_dimensions + '</p>';
2515 + }
2516 +
2517 + html += '</div>'; // End details
2518 + html += '</div>'; // End card
2519 +
2520 + $container.html(html).show();
2521 + }
2522 +
2523 + // Helper function to format time ago
2524 + function formatTimeAgo(timestamp) {
2525 + const now = Math.floor(Date.now() / 1000);
2526 + const seconds = now - timestamp;
2527 +
2528 + if (seconds < 60) {
2529 + return seconds + ' seconds ago';
2530 + } else if (seconds < 3600) {
2531 + return Math.floor(seconds / 60) + ' minutes ago';
2532 + } else if (seconds < 86400) {
2533 + return Math.floor(seconds / 3600) + ' hours ago';
2534 + } else {
2535 + return Math.floor(seconds / 86400) + ' days ago';
2536 + }
2537 + }
2538 +
2539 + // Helper function to truncate long URLs
2540 + function truncateUrl(url) {
2541 + const maxLength = 50;
2542 + if (url.length <= maxLength) return url;
2543 +
2544 + // Remove protocol
2545 + let displayUrl = url.replace(/^https?:\/\//, '');
2546 +
2547 + if (displayUrl.length <= maxLength) return displayUrl;
2548 +
2549 + // Keep the domain and truncate the path
2550 + const domainMatch = displayUrl.match(/^([^\/]+)\//);
2551 + if (domainMatch) {
2552 + const domain = domainMatch[1];
2553 + const path = displayUrl.substring(domain.length);
2554 +
2555 + if (path.length > 10) {
2556 + return domain + path.substring(0, maxLength - domain.length - 3) + '...';
2557 + }
2558 + }
2559 +
2560 + // Final fallback for very long strings
2561 + return displayUrl.substring(0, maxLength - 3) + '...';
2562 + }
2563 +});
552 2564
2565 +
2566 +document.addEventListener('DOMContentLoaded', function() {
2567 + var viewSampleBtn = document.getElementById('mxchatViewSampleBtn');
2568 + var modal = document.getElementById('mxchatSampleModal');
2569 + var modalClose = document.getElementById('mxchatModalClose');
2570 + var closeBtn = document.getElementById('mxchatCloseBtn');
2571 + var copyBtn = document.getElementById('mxchatCopyBtn');
2572 + var instructionsContent = document.querySelector('.mxchat-instructions-content');
2573 + var modalContent = document.querySelector('.mxchat-instructions-modal-content');
2574 +
2575 + if (!viewSampleBtn || !modal) {
2576 + return;
2577 + }
2578 +
2579 + // Open modal
2580 + viewSampleBtn.addEventListener('click', function(e) {
2581 + e.preventDefault();
2582 + e.stopPropagation();
2583 + modal.classList.add('mxchat-instructions-show');
2584 + });
2585 +
2586 + // Close modal function
2587 + function closeModal(e) {
2588 + if (e) {
2589 + e.preventDefault();
2590 + e.stopPropagation();
2591 + }
2592 + modal.classList.remove('mxchat-instructions-show');
2593 + }
2594 +
2595 + // Close modal events
2596 + if (modalClose) {
2597 + modalClose.addEventListener('click', function(e) {
2598 + closeModal(e);
2599 + });
2600 + }
2601 +
2602 + if (closeBtn) {
2603 + closeBtn.addEventListener('click', function(e) {
2604 + closeModal(e);
2605 + });
2606 + }
2607 +
2608 + // Close on backdrop click ONLY (not on hover)
2609 + modal.addEventListener('click', function(e) {
2610 + // Only close if clicking directly on the overlay, not on child elements
2611 + if (e.target === modal) {
2612 + closeModal(e);
2613 + }
2614 + });
2615 +
2616 + // Prevent modal content clicks from closing the modal
2617 + if (modalContent) {
2618 + modalContent.addEventListener('click', function(e) {
2619 + e.stopPropagation();
2620 + });
2621 + }
2622 +
2623 + // Close on escape key
2624 + document.addEventListener('keydown', function(e) {
2625 + if (e.key === 'Escape' && modal.classList.contains('mxchat-instructions-show')) {
2626 + closeModal();
2627 + }
2628 + });
2629 +
2630 + // Copy functionality
2631 + if (copyBtn && instructionsContent) {
2632 + copyBtn.addEventListener('click', function(e) {
2633 + e.preventDefault();
2634 + e.stopPropagation();
2635 +
2636 + var text = instructionsContent.textContent;
2637 +
2638 + if (navigator.clipboard) {
2639 + navigator.clipboard.writeText(text).then(function() {
2640 + showCopySuccess();
2641 + }).catch(function() {
2642 + fallbackCopy(text);
2643 + });
2644 + } else {
2645 + fallbackCopy(text);
2646 + }
2647 + });
2648 + }
2649 +
2650 + function fallbackCopy(text) {
2651 + var textArea = document.createElement('textarea');
2652 + textArea.value = text;
2653 + textArea.style.position = 'fixed';
2654 + textArea.style.left = '-999999px';
2655 + textArea.style.top = '-999999px';
2656 + document.body.appendChild(textArea);
2657 + textArea.select();
2658 + try {
2659 + document.execCommand('copy');
2660 + showCopySuccess();
2661 + } catch (err) {
2662 + console.error('Copy failed');
2663 + }
2664 + document.body.removeChild(textArea);
2665 + }
2666 +
2667 + function showCopySuccess() {
2668 + var originalText = copyBtn.innerHTML;
2669 + copyBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20,6 9,17 4,12"/></svg>Copied!';
2670 +
2671 + setTimeout(function() {
2672 + copyBtn.innerHTML = originalText;
2673 + }, 2000);
2674 + }
2675 +});