PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.4.6
MxChat – AI Chatbot & Content Generation for WordPress v2.4.6
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
mxchat-basic / js / mxchat-admin.js

mxchat-admin.js in MxChat – AI Chatbot & Content Generation for WordPress 2.4.6, at js/mxchat-admin.js

2,202 lines 101.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // Simple debounce function implementation
2 function debounce(func, wait) {
3 let timeout;
4 return function executedFunction(...args) {
5 const later = () => {
6 clearTimeout(timeout);
7 func(...args);
8 };
9 clearTimeout(timeout);
10 timeout = setTimeout(later, wait);
11 };
12 }
13
14 // Helper function to open edit modal for intents/actions
15 function mxchatOpenEditModal(intentId, phrases) {
16 const modal = document.getElementById('mxchat-edit-modal');
17 if (!modal) return;
18
19 // Get form fields
20 const intentIdField = document.getElementById('edit_intent_id');
21 const phrasesField = document.getElementById('edit_phrases');
22
23 // Set values
24 intentIdField.value = intentId;
25 phrasesField.value = phrases;
26
27 // Show modal with animation
28 modal.style.display = 'flex';
29 requestAnimationFrame(() => {
30 modal.classList.add('active');
31 });
32
33 // Set up close handlers
34 const closeModal = () => {
35 modal.classList.remove('active');
36 setTimeout(() => {
37 modal.style.display = 'none';
38 }, 300); // Match the CSS transition time
39 };
40
41 // Close button handler
42 const closeBtn = modal.querySelector('.mxchat-modal-close');
43 if (closeBtn) {
44 closeBtn.onclick = closeModal;
45 }
46
47 // Cancel button handler
48 const cancelBtn = modal.querySelector('.mxchat-modal-cancel');
49 if (cancelBtn) {
50 cancelBtn.onclick = closeModal;
51 }
52
53 // Click outside modal to close
54 modal.onclick = (e) => {
55 if (e.target === modal) {
56 closeModal();
57 }
58 };
59
60 // Focus the textarea
61 phrasesField.focus();
62 }
63 // Live Agent Notice Dismissal Function
64 function dismissLiveAgentNotice() {
65 if (typeof jQuery !== 'undefined' && typeof mxchatLiveAgent !== 'undefined') {
66 jQuery.post(mxchatLiveAgent.ajaxurl, {
67 action: 'dismiss_live_agent_notice',
68 nonce: mxchatLiveAgent.nonce
69 }, function(response) {
70 if (response.success) {
71 jQuery('#mxchat-disabled-notice').fadeOut(300);
72 }
73 }).fail(function() {
74 // Fallback: just hide the notice if AJAX fails
75 jQuery('#mxchat-disabled-notice').fadeOut(300);
76 });
77 } else {
78 // Fallback for cases where jQuery or localized data isn't available
79 var notice = document.getElementById('mxchat-disabled-notice');
80 if (notice) {
81 notice.style.display = 'none';
82 }
83 }
84 }
85
86 // Updated mxchatOpenActionModal function to integrate with the new selector
87 function mxchatOpenActionModal(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '') {
88 const modal = document.getElementById('mxchat-action-modal');
89 if (!modal) return;
90
91 // Get form fields
92 const actionIdField = document.getElementById('edit_action_id');
93 const labelField = document.getElementById('intent_label');
94 const phrasesField = document.getElementById('action_phrases');
95 const formActionType = document.getElementById('form_action_type');
96 const callbackGroup = document.getElementById('callback_selection_group');
97 const callbackSelect = document.getElementById('callback_function');
98 const saveButton = document.getElementById('mxchat-save-action-btn');
99 const nonceContainer = document.getElementById('action-nonce-container');
100 const thresholdSlider = document.getElementById('similarity_threshold');
101 const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
102
103 // Set up modal for edit or create
104 if (isEdit) {
105 saveButton.textContent = 'Update Action';
106 formActionType.value = 'mxchat_edit_intent';
107 actionIdField.value = actionId;
108 labelField.value = label;
109 phrasesField.value = phrases;
110 callbackGroup.style.display = 'none'; // Hide callback selection when editing
111 thresholdSlider.value = threshold; // Set the current threshold value
112 thresholdDisplay.textContent = threshold + '%'; // Update display
113
114 // Remove the required attribute when editing
115 callbackSelect.removeAttribute('required');
116
117 // Update the nonce field for editing
118 nonceContainer.innerHTML = ''; // Clear existing nonce
119 if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) {
120 nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.edit_intent_nonce}">`;
121 }
122 } else {
123 saveButton.textContent = 'Save Action';
124 formActionType.value = 'mxchat_add_intent';
125 actionIdField.value = '';
126 labelField.value = '';
127 phrasesField.value = '';
128 callbackGroup.style.display = 'block'; // Show callback selection when creating
129 thresholdSlider.value = 85; // Default value for new actions
130 thresholdDisplay.textContent = '85%'; // Default display
131
132 // Ensure the required attribute is present when adding
133 callbackSelect.setAttribute('required', 'required');
134
135 // Update the nonce field for adding
136 nonceContainer.innerHTML = ''; // Clear existing nonce
137 if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) {
138 nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.add_intent_nonce}">`;
139 }
140 }
141
142 // Show modal with animation
143 modal.style.display = 'flex';
144 requestAnimationFrame(() => {
145 modal.classList.add('active');
146 });
147
148 // Set up close handlers
149 const closeModal = () => {
150 modal.classList.remove('active');
151 setTimeout(() => {
152 modal.style.display = 'none';
153 }, 300); // Match the CSS transition time
154 };
155
156 // Close button handler
157 const closeBtn = modal.querySelector('.mxchat-modal-close');
158 if (closeBtn) {
159 closeBtn.onclick = closeModal;
160 }
161
162 // Cancel button handler
163 const cancelBtn = modal.querySelector('.mxchat-modal-cancel');
164 if (cancelBtn) {
165 cancelBtn.onclick = closeModal;
166 }
167
168 // Click outside modal to close
169 modal.onclick = (e) => {
170 if (e.target === modal) {
171 closeModal();
172 }
173 };
174
175 // Escape key to close modal
176 document.addEventListener('keydown', function(e) {
177 if (e.key === 'Escape' && modal.classList.contains('active')) {
178 closeModal();
179 }
180 }, { once: true });
181
182 // Focus the first field
183 labelField.focus();
184
185 // Dispatch an event for the action type selector to catch
186 const event = new CustomEvent('mxchatModalOpened', {
187 detail: {
188 isEdit: isEdit,
189 callbackFunction: callbackFunction || (isEdit ? callbackSelect.value : '')
190 }
191 });
192 document.dispatchEvent(event);
193
194 return closeModal; // Return close function for external use
195 }
196
197 // Initialize event listeners
198 document.addEventListener('DOMContentLoaded', () => {
199 // Set up edit button handlers for intents
200 document.querySelectorAll('.mxchat-edit-button').forEach(button => {
201 button.onclick = () => {
202 const intentId = button.dataset.intentId;
203 const phrases = button.dataset.phrases;
204 mxchatOpenEditModal(intentId, phrases);
205 };
206 });
207
208 document.querySelectorAll('.mxchat-action-card .mxchat-edit-button').forEach(button => {
209 button.onclick = () => {
210 const actionId = button.dataset.actionId;
211 const phrases = button.dataset.phrases;
212 const label = button.dataset.label;
213 const threshold = button.dataset.threshold || 85;
214 const callbackFunction = button.dataset.callbackFunction;
215 const enabledBots = button.dataset.enabledBots; // ADD THIS LINE
216
217 mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction, enabledBots);
218 };
219 });
220
221 // Set up add new action buttons (new functionality)
222 const addActionBtn = document.getElementById('mxchat-add-action-btn');
223 if (addActionBtn) {
224 addActionBtn.onclick = () => mxchatOpenActionModal();
225 }
226
227 const createFirstAction = document.getElementById('mxchat-create-first-action');
228 if (createFirstAction) {
229 createFirstAction.onclick = () => mxchatOpenActionModal();
230 }
231
232 // Setup category-specific new action buttons (new functionality)
233 document.querySelectorAll('.mxchat-new-action-button').forEach(button => {
234 button.onclick = () => {
235 const category = button.closest('.mxchat-new-action-card').dataset.category;
236 const closeModal = mxchatOpenActionModal();
237
238 // Pre-select the appropriate callback based on category
239 if (category) {
240 const callbackSelect = document.getElementById('callback_function');
241 if (callbackSelect) {
242 setTimeout(() => {
243 // Map categories to default callbacks
244 const categoryToCallback = {
245 'data_collection': 'mxchat_handle_form_collection',
246 'integrations': 'mxchat_handle_slack_message',
247 'custom_actions': 'mxchat_handle_custom_action',
248 'recommendations': 'mxchat_handle_product_recommendations'
249 // Add more mappings as needed
250 };
251
252 if (categoryToCallback[category]) {
253 callbackSelect.value = categoryToCallback[category];
254 }
255 }, 100);
256 }
257 }
258 };
259 });
260
261 // Handle action toggle switches (new functionality)
262 document.querySelectorAll('.mxchat-action-toggle').forEach(toggle => {
263 toggle.onchange = function() {
264 const actionId = this.dataset.actionId;
265 const isEnabled = this.checked;
266
267 // Show loading indicator
268 const loadingEl = document.getElementById('mxchat-action-loading');
269 if (loadingEl) loadingEl.style.display = 'flex';
270
271 // Send AJAX request to update status
272 fetch(ajaxurl, {
273 method: 'POST',
274 headers: {
275 'Content-Type': 'application/x-www-form-urlencoded',
276 },
277 body: new URLSearchParams({
278 action: 'mxchat_toggle_action',
279 intent_id: actionId,
280 enabled: isEnabled ? 1 : 0,
281 nonce: mxchatAdmin.toggle_action_nonce // Use the correct nonce
282 })
283 })
284 .then(response => response.json())
285 .then(data => {
286 if (!data.success) {
287 alert('Failed to update action status: ' + (data.data?.message || 'Unknown error'));
288 this.checked = !isEnabled; // Revert the toggle
289 }
290 })
291 .catch(error => {
292 //console.error('Error:', error);
293 alert('Server error. Please try again.');
294 this.checked = !isEnabled; // Revert the toggle
295 })
296 .finally(() => {
297 if (loadingEl) loadingEl.style.display = 'none';
298 });
299 };
300 });
301
302 // Handle threshold sliders in action cards (new functionality)
303 document.querySelectorAll('.mxchat-threshold-slider').forEach(slider => {
304 slider.oninput = function() {
305 const actionId = this.id.replace('intent_threshold_', '');
306 document.getElementById('threshold_output_' + actionId).textContent = this.value + '%';
307 };
308 });
309
310 // Handle threshold save buttons in action cards (new functionality)
311 document.querySelectorAll('.mxchat-threshold-save').forEach(button => {
312 button.onclick = function(e) {
313 e.preventDefault();
314 const form = this.closest('form');
315 const intentId = form.querySelector('input[name="intent_id"]').value;
316 const threshold = form.querySelector('input[name="intent_threshold"]').value;
317 const nonce = form.querySelector('input[name="_wpnonce"]').value;
318
319 // Show loading indicator
320 const loadingEl = document.getElementById('mxchat-action-loading');
321 if (loadingEl) loadingEl.style.display = 'flex';
322
323 // Send AJAX request
324 fetch(ajaxurl, {
325 method: 'POST',
326 headers: {
327 'Content-Type': 'application/x-www-form-urlencoded',
328 },
329 body: new URLSearchParams({
330 action: 'mxchat_update_intent_threshold',
331 intent_id: intentId,
332 intent_threshold: threshold,
333 _wpnonce: nonce
334 })
335 })
336 .then(response => response.json())
337 .then(data => {
338 if (data.success) {
339 // Visual feedback of success
340 const card = this.closest('.mxchat-action-card');
341 card.style.background = 'rgba(120, 115, 245, 0.1)';
342 setTimeout(() => {
343 card.style.background = 'white';
344 }, 300);
345 } else {
346 alert('Failed to update threshold: ' + (data.data?.message || 'Unknown error'));
347 }
348 })
349 .catch(error => {
350 //console.error('Error:', error);
351 alert('Server error. Please try again.');
352 })
353 .finally(() => {
354 if (loadingEl) loadingEl.style.display = 'none';
355 });
356 };
357 });
358 });
359
360 jQuery(document).ready(function($) {
361 // Ensure we have a debounce function (use lodash if available, otherwise use our implementation)
362 const useDebounce = (window._ && window._.debounce) ? window._.debounce : debounce;
363
364 // --- AJAX Auto-Save ---
365 let $autosaveSections = $('.mxchat-autosave-section');
366
367 // *** ADD THIS: Extend auto-save sections to include Pinecone settings ***
368 const $pineconeAutosaveSection = $('#mxchat-kb-tab-pinecone');
369 if ($pineconeAutosaveSection.length) {
370 $autosaveSections = $autosaveSections.add($pineconeAutosaveSection);
371 //console.log('Added Pinecone section to auto-save monitoring');
372 }
373
374 // Track whether fields have been modified by user
375 const userModifiedFields = new Set();
376
377 if ($autosaveSections.length) {
378 // Track user interactions with input fields to determine if changes are user-initiated
379 $autosaveSections.find('input, textarea, select').on('focus keydown paste', function() {
380 const fieldName = $(this).attr('name');
381 if (fieldName) {
382 userModifiedFields.add(fieldName);
383 }
384 });
385
386 // Handle real-time range slider value updates
387 $autosaveSections.find('input[type="range"]').on('input', function() {
388 const value = $(this).val();
389 $('#threshold_value').text(value);
390 });
391
392 // Handle all input changes (including range slider)
393 $autosaveSections.find('input, textarea, select').on('change', function() {
394 const $field = $(this);
395 const name = $field.attr('name');
396
397 // Skip saving for API key fields that haven't been interacted with and are empty
398 const isApiKeyField = name && (
399 name === 'loops_api_key' ||
400 name === 'api_key' ||
401 name === 'xai_api_key' ||
402 name === 'claude_api_key' ||
403 name === 'voyage_api_key' ||
404 name === 'gemini_api_key' ||
405 name === 'deepseek_api_key' ||
406 name.indexOf('_api_key') !== -1
407 );
408
409 // Skip processing if:
410 // 1. It's an API key field
411 // 2. The user hasn't interacted with it
412 // 3. The field is empty
413 if (isApiKeyField && !userModifiedFields.has(name) && (!$field.val() || $field.val().trim() === '')) {
414 //console.log('Skipping auto-save for untouched API key field:', name);
415 return;
416 }
417
418 let value;
419
420 // Handle different input types
421 if ($field.attr('type') === 'checkbox') {
422 // *** UPDATED: Handle Pinecone checkboxes differently ***
423 if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) {
424 value = $field.is(':checked') ? '1' : '0';
425 } else {
426 value = $field.is(':checked') ? 'on' : 'off';
427 }
428 } else {
429 value = $field.val();
430 }
431
432 // Create feedback container
433 const feedbackContainer = $('<div class="feedback-container"></div>');
434 const spinner = $('<div class="saving-spinner"></div>');
435 const successIcon = $('<div class="success-icon">✔</div>');
436
437 // Position feedback container based on input type
438 if ($field.closest('.toggle-switch').length) {
439 $field.closest('td').append(feedbackContainer);
440 } else if ($field.closest('.mxchat-toggle-switch').length) {
441 $field.closest('.mxchat-toggle-container').append(feedbackContainer);
442 } else if ($field.closest('.slider-container').length) {
443 $field.closest('.slider-container').after(feedbackContainer);
444 } else {
445 $field.after(feedbackContainer);
446 }
447 feedbackContainer.append(spinner);
448
449 // Determine which AJAX action and nonce to use:
450 var ajaxAction, nonce;
451 // *** UPDATED: Add Pinecone fields to prompts action ***
452 if (name.indexOf('mxchat_prompts_options') !== -1 ||
453 name === 'mxchat_auto_sync_posts' ||
454 name === 'mxchat_auto_sync_pages' ||
455 name.indexOf('mxchat_auto_sync_') === 0 ||
456 name.indexOf('mxchat_pinecone_addon_options') !== -1) { // *** ADD THIS LINE ***
457 ajaxAction = 'mxchat_save_prompts_setting';
458 nonce = mxchatPromptsAdmin.prompts_setting_nonce;
459 } else {
460 // Otherwise, use the existing AJAX action.
461 ajaxAction = 'mxchat_save_setting';
462 nonce = mxchatAdmin.setting_nonce;
463 }
464
465 // *** ADD THIS: Debug logging for Pinecone fields ***
466 if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) {
467 //console.log('Saving Pinecone field:', name, '=', value);
468 }
469
470 // AJAX save request
471 $.ajax({
472 url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
473 type: 'POST',
474 data: {
475 action: ajaxAction,
476 name: name,
477 value: value,
478 _ajax_nonce: nonce
479 },
480 success: function(response) {
481 if (response.success) {
482 spinner.fadeOut(200, function() {
483 feedbackContainer.append(successIcon);
484 successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
485 feedbackContainer.remove();
486 });
487 });
488
489 // *** ADD THIS: Update Pinecone checkbox state after successful save ***
490 if (name && name.indexOf('mxchat_pinecone_addon_options[mxchat_use_pinecone]') !== -1) {
491 //console.log('Pinecone toggle saved successfully, value:', value);
492
493 // The checkbox state is already updated by the user interaction
494 // But let's make sure the UI state matches the saved value
495 var $checkbox = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]');
496 var settingsDiv = $('.mxchat-pinecone-settings');
497
498 // Double-check the UI state matches what was saved
499 if (value === '1' && !$checkbox.is(':checked')) {
500 $checkbox.prop('checked', true);
501 settingsDiv.slideDown(300);
502 } else if (value === '0' && $checkbox.is(':checked')) {
503 $checkbox.prop('checked', false);
504 settingsDiv.slideUp(300);
505 }
506
507 //console.log('Pinecone UI state synchronized');
508
509 // Check if Knowledge Import tab is currently active
510 if ($('.mxchat-kb-tab-button[data-tab="import"]').hasClass('active')) {
511 // Show a notice that we need to refresh
512 var $knowledgeCard = $('#mxchat-kb-tab-import .mxchat-card').eq(1);
513 if ($knowledgeCard.length > 0) {
514 // Add a refresh notice at the top of the knowledge base card
515 var refreshNotice = $('<div class="notice notice-warning" style="margin: 15px 0; padding: 10px 15px;">' +
516 '<p style="margin: 0;">' +
517 '<span class="dashicons dashicons-info" style="color: #f0ad4e; margin-right: 5px;"></span>' +
518 'Database settings have changed. ' +
519 '<a href="#" onclick="location.reload(); return false;" style="font-weight: bold;">Click here to refresh</a> to see the updated knowledge base.' +
520 '</p></div>');
521
522 $knowledgeCard.prepend(refreshNotice);
523 }
524 } else {
525 // If not on import tab, set a flag to refresh when they go there
526 sessionStorage.setItem('mxchat_pinecone_changed', 'true');
527 }
528 }
529
530 // *** ADD THIS: Debug logging for successful saves ***
531 if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) {
532 //console.log('Pinecone field saved successfully:', name, '=', value);
533 }
534
535 // Check if the response contains a "no changes" message and log it
536 if (response.data && response.data.message === 'No changes detected') {
537 //console.log('No changes detected for field:', name);
538 }
539 } else {
540 // Only show alert for actual errors, not for "no changes"
541 let errorMessage = response.data?.message || 'Unknown error';
542
543 // Don't display an alert for "no changes" message
544 if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') {
545 alert('Error saving: ' + errorMessage);
546 } else {
547 // Still provide visual feedback that no changes were needed
548 spinner.fadeOut(200, function() {
549 feedbackContainer.append(successIcon);
550 successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
551 feedbackContainer.remove();
552 });
553 });
554 //console.log('No changes detected for field:', name);
555 return;
556 }
557
558 // Only revert checkbox state if it was an actual error
559 if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') {
560 if ($field.attr('type') === 'checkbox') {
561 $field.prop('checked', !$field.is(':checked'));
562 }
563 }
564
565 // Always clean up the feedback container
566 feedbackContainer.remove();
567 }
568 },
569 error: function(xhr, textStatus, error) {
570 //console.error('AJAX Error:', textStatus, error);
571 alert('An error occurred while saving. Please try again.');
572
573 // Revert checkbox state on error
574 if ($field.attr('type') === 'checkbox') {
575 $field.prop('checked', !$field.is(':checked'));
576 }
577
578 feedbackContainer.remove();
579 }
580 });
581 });
582
583 // Initialize color pickers with debouncing
584 $autosaveSections.find('.my-color-field').each(function() {
585 const $colorField = $(this);
586
587 $(this).wpColorPicker({
588 change: useDebounce(function(event, ui) {
589 // Safety check - ensure we have a valid field and value
590 if (!$colorField || !$colorField.val()) {
591 //console.warn('Color picker not ready');
592 return;
593 }
594
595 const name = $colorField.attr('name');
596 const value = $colorField.val();
597
598 if (!name || !value) {
599 //console.warn('Missing required color picker values');
600 return;
601 }
602
603 // Create feedback container
604 const feedbackContainer = $('<div class="feedback-container"></div>');
605 const spinner = $('<div class="saving-spinner"></div>');
606 const successIcon = $('<div class="success-icon">✔</div>');
607
608 // Position feedback container
609 $colorField.closest('.wp-picker-container').after(feedbackContainer);
610 feedbackContainer.append(spinner);
611
612 // Determine which AJAX action and nonce to use:
613 var ajaxAction, nonce;
614 // Use the new AJAX action for submenu fields:
615 if (name.indexOf('mxchat_prompts_options') !== -1 ||
616 name === 'mxchat_auto_sync_posts' ||
617 name === 'mxchat_auto_sync_pages' ||
618 name.indexOf('mxchat_auto_sync_') === 0) { // Modified to catch all auto-sync fields
619 ajaxAction = 'mxchat_save_prompts_setting';
620 nonce = mxchatPromptsAdmin.prompts_setting_nonce;
621 } else {
622 // Otherwise, use the existing AJAX action.
623 ajaxAction = 'mxchat_save_setting';
624 nonce = mxchatAdmin.setting_nonce;
625 }
626 // AJAX save request
627 $.ajax({
628 url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
629 type: 'POST',
630 data: {
631 action: ajaxAction,
632 name: name,
633 value: value,
634 _ajax_nonce: nonce
635 },
636 success: function(response) {
637 if (response.success) {
638 spinner.fadeOut(200, function() {
639 feedbackContainer.append(successIcon);
640 successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
641 feedbackContainer.remove();
642 });
643 });
644 } else {
645 alert('Error saving: ' + (response.data?.message || 'Unknown error'));
646 feedbackContainer.remove();
647 }
648 },
649 error: function() {
650 alert('An error occurred while saving.');
651 feedbackContainer.remove();
652 }
653 });
654 }, 500)
655 });
656 });
657
658 // Reinitialize color pickers when switching tabs
659 $('.mxchat-tab-button').on('click.mxchat', function() {
660 setTimeout(function() {
661 $('.my-color-field:visible').wpColorPicker('close');
662 }, 100);
663 });
664 }
665
666 // Initialize tabs system
667 function initTabs() {
668 // Remove any existing handlers first
669 $('.mxchat-tab-button').off('click.mxchat');
670
671 // Add new click handlers
672 $('.mxchat-tab-button').on('click.mxchat', function(e) {
673 e.preventDefault();
674 e.stopPropagation();
675
676 var $this = $(this);
677
678 // Get tab ID from data-tab attribute
679 var tabId = $this.data('tab') || 'chatbot';
680
681 // Safety check for empty tabId
682 if (!tabId) {
683 //console.warn('No tab identifier found');
684 return;
685 }
686
687 // Update tab buttons
688 $('.mxchat-tab-button').removeClass('active');
689 $this.addClass('active');
690
691 // Update content areas - with safety check
692 $('.mxchat-tab-content').removeClass('active');
693 var $targetTab = $('#' + tabId);
694 if ($targetTab.length) {
695 $targetTab.addClass('active');
696 // Removed localStorage saving functionality
697 } else {
698 //console.warn('Tab content #' + tabId + ' not found');
699 }
700 });
701 }
702
703 // Initialize tabs and handle events
704 initTabs();
705 $(document).on('widget-added widget-updated postbox-toggled', initTabs);
706
707 // Always activate the first tab (Chatbot)
708 $('.mxchat-tab-button').first().trigger('click.mxchat');
709
710
711 // Toggle visibility handlers
712 function toggleVisibility(selector) {
713 $(selector).on('click', function() {
714 var inputField = $(this).prev('input');
715 if (inputField.attr('type') === 'password') {
716 inputField.attr('type', 'text');
717 $(this).text('Hide');
718 } else {
719 inputField.attr('type', 'password');
720 $(this).text('Show');
721 }
722 });
723 }
724
725 // Initialize all toggle visibility buttons
726 [
727 '#toggleApiKeyVisibility',
728 '#toggleWooCommerceSecretVisibility',
729 '#toggleVoyageAPIKeyVisibility',
730 '#toggleLoopsApiKeyVisibility',
731 '#toggleXaiApiKeyVisibility',
732 '#toggleClaudeApiKeyVisibility',
733 '#toggleBraveApiKeyVisibility',
734 '#toggleWebhookUrlVisibility',
735 '#toggleSecretKeyVisibility',
736 '#toggleBotTokenVisibility',
737 '#toggleDeepSeekApiKeyVisibility',
738 '#toggleGeminiApiKeyVisibility' // Added Gemini toggle
739 ].forEach(toggleVisibility);
740
741 // Handle API key visibility based on model selection
742 function setupAPIKeyVisibility() {
743 // Cache the selectors
744 const $chatModelSelect = $('#model');
745 const $embeddingModelSelect = $('#embedding_model');
746
747 // First, locate and mark the API key rows
748 setupAPIKeyRows();
749
750 // Initial setup based on current selections
751 updateApiKeyVisibility();
752
753 // Listen for changes to the model selectors
754 $chatModelSelect.on('change', updateApiKeyVisibility);
755 $embeddingModelSelect.on('change', updateApiKeyVisibility);
756
757 /**
758 * Locate and mark rows that contain API key fields
759 */
760 function setupAPIKeyRows() {
761 // Find key rows by their field IDs
762 const providerMap = {
763 'api_key': 'openai',
764 'xai_api_key': 'xai',
765 'claude_api_key': 'claude',
766 'deepseek_api_key': 'deepseek',
767 'voyage_api_key': 'voyage',
768 'gemini_api_key': 'gemini' // Added Gemini API key mapping
769 };
770
771 $.each(providerMap, function(fieldId, provider) {
772 const $field = $('#' + fieldId);
773 if ($field.length) {
774 const $row = $field.closest('tr');
775 $row.addClass('mxchat-setting-row');
776 $row.attr('data-provider', provider);
777 }
778 });
779 }
780
781 /**
782 * Updates the visibility of API key fields based on current model selections
783 */
784 function updateApiKeyVisibility() {
785 const chatModel = $chatModelSelect.val();
786 const embeddingModel = $embeddingModelSelect.val();
787
788 // Determine which providers are needed
789 const isOpenAIChat = chatModel && chatModel.startsWith('gpt-');
790 const isXAI = chatModel && chatModel.startsWith('grok-');
791 const isClaude = chatModel && chatModel.startsWith('claude-');
792 const isDeepSeek = chatModel && chatModel.startsWith('deepseek-');
793 const isGemini = chatModel && chatModel.startsWith('gemini-'); // Added Gemini detection
794
795 const isOpenAIEmbedding = embeddingModel && embeddingModel.startsWith('text-embedding-');
796 const isVoyage = embeddingModel && embeddingModel.startsWith('voyage-');
797 const isGeminiEmbedding = embeddingModel && embeddingModel.startsWith('gemini-embedding-');
798
799 // Update API key visibility for each provider
800 updateWrapperVisibility('openai', isOpenAIChat || isOpenAIEmbedding);
801 updateWrapperVisibility('xai', isXAI);
802 updateWrapperVisibility('claude', isClaude);
803 updateWrapperVisibility('deepseek', isDeepSeek);
804 updateWrapperVisibility('voyage', isVoyage);
805 updateWrapperVisibility('gemini', isGemini || isGeminiEmbedding); // Updated Gemini visibility for both chat and embedding
806
807 // Update provider-specific notices for OpenAI
808 if (isOpenAIChat && isOpenAIEmbedding) {
809 $('div[data-provider="openai"] .api-key-notice').text(
810 'Required for your selected chat model and embedding model. Important: You must add credits before use.'
811 );
812 } else if (isOpenAIChat) {
813 $('div[data-provider="openai"] .api-key-notice').text(
814 'Required for your selected chat model. Important: You must add credits before use.'
815 );
816 } else if (isOpenAIEmbedding) {
817 $('div[data-provider="openai"] .api-key-notice').text(
818 'Required for your selected embedding model. Important: You must add credits before use.'
819 );
820 }
821
822 // Update provider-specific notices for Gemini
823 if (isGemini && isGeminiEmbedding) {
824 $('div[data-provider="gemini"] .api-key-notice').text(
825 'Required for your selected chat model and embedding model.'
826 );
827 } else if (isGemini) {
828 $('div[data-provider="gemini"] .api-key-notice').text(
829 'Required for your selected chat model.'
830 );
831 } else if (isGeminiEmbedding) {
832 $('div[data-provider="gemini"] .api-key-notice').text(
833 'Required for your selected embedding model.'
834 );
835 }
836 }
837
838 /**
839 * Updates visibility of a specific provider's API key wrapper
840 */
841 function updateWrapperVisibility(provider, isVisible) {
842 const $row = $('tr.mxchat-setting-row[data-provider="' + provider + '"]');
843
844 if (!$row.length) {
845 //console.warn('API key row not found for provider: ' + provider);
846 return;
847 }
848
849 if (isVisible) {
850 $row.show();
851 if (!$row.hasClass('highlighted')) {
852 $row.addClass('highlighted');
853 setTimeout(() => {
854 $row.removeClass('highlighted');
855 }, 1500);
856 }
857 } else {
858 $row.hide();
859 }
860 }
861 }
862
863 // Add this to your JavaScript file
864 function setupMxChatModelSelector() {
865 const $modelSelect = $('#model');
866 const $modelSelectorButton = $('<button>', {
867 type: 'button',
868 id: 'mxchat_model_selector_btn',
869 class: 'button-primary mxchat-model-selector-btn',
870 text: 'Select AI Model'
871 });
872
873 // Replace the select dropdown with a button
874 $modelSelect.hide().after($modelSelectorButton);
875
876 // Update button text to show currently selected model
877 function updateButtonText() {
878 const selectedModel = $modelSelect.val();
879 const selectedModelText = $modelSelect.find('option:selected').text();
880 $modelSelectorButton.text(selectedModelText);
881 }
882
883 // Initialize button text
884 updateButtonText();
885
886 // Create and append modal HTML
887 const modelSelectorModal = `
888 <div id="mxchat_model_selector_modal" class="mxchat-model-selector-modal">
889 <div class="mxchat-model-selector-modal-content">
890 <div class="mxchat-model-selector-modal-header">
891 <h3>Select AI Model</h3>
892 <span class="mxchat-model-selector-modal-close">&times;</span>
893 </div>
894 <div class="mxchat-model-selector-modal-body">
895 <div class="mxchat-model-selector-search-container">
896 <input type="text" id="mxchat_model_search_input" class="mxchat-model-search-input" placeholder="Search models...">
897 </div>
898 <div class="mxchat-model-selector-categories">
899 <button class="mxchat-model-category-btn active" data-category="all">All</button>
900 <button class="mxchat-model-category-btn" data-category="gemini">Google Gemini</button>
901 <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
902 <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
903 <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
904 <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
905 </div>
906 <div class="mxchat-model-selector-grid" id="mxchat_models_grid"></div>
907 </div>
908 <div class="mxchat-model-selector-modal-footer">
909 <button id="mxchat_cancel_model_selection" class="button mxchat-model-cancel-btn">Cancel</button>
910 </div>
911 </div>
912 </div>
913 `;
914
915 $('body').append(modelSelectorModal);
916
917 // Populate models grid
918 // Populate models grid
919 function populateModelsGrid(filter = '', category = 'all') {
920 const $grid = $('#mxchat_models_grid');
921 $grid.empty();
922
923 const models = {
924 gemini: [
925 { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', description: 'Next-Gen features, speed & multimodal generation' },
926 { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash-Lite', description: 'Cost-efficient with low latency' },
927 { value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', description: 'Complex reasoning tasks requiring more intelligence' },
928 { value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', description: 'Fast and versatile performance' },
929 ],
930 openai: [
931 // NEW: GPT-5 family
932 { value: 'gpt-5', label: 'GPT-5', description: 'Flagship for coding, reasoning, and agentic tasks across domains' },
933 { value: 'gpt-5-mini', label: 'GPT-5 Mini', description: 'Faster, more cost-efficient for well-defined tasks and precise prompts' },
934 { value: 'gpt-5-nano', label: 'GPT-5 Nano', description: 'Fastest and cheapest; ideal for summarization and classification' },
935
936 // Existing OpenAI models
937 { value: 'gpt-4.1-2025-04-14', label: 'GPT-4.1', description: 'Flagship model for complex tasks' },
938 { value: 'gpt-4o', label: 'GPT-4o', description: 'Recommended for most use cases' },
939 { value: 'gpt-4o-mini', label: 'GPT-4o Mini', description: 'Fast and lightweight' },
940 { value: 'gpt-4-turbo', label: 'GPT-4 Turbo', description: 'High-performance model' },
941 { value: 'gpt-4', label: 'GPT-4', description: 'High intelligence model' },
942 { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', description: 'Affordable and fast' },
943 ],
944 claude: [
945 { value: 'claude-opus-4-20250514', label: 'Claude 4 Opus', description: 'Most capable Claude model' },
946 { value: 'claude-sonnet-4-20250514', label: 'Claude 4 Sonnet', description: 'High performance' },
947 { value: 'claude-3-7-sonnet-20250219', label: 'Claude 3.7 Sonnet', description: 'High intelligence' },
948 { value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', description: 'Intelligent and balanced' },
949 { value: 'claude-3-opus-20240229', label: 'Claude 3 Opus', description: 'Highly complex tasks' },
950 { value: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', description: 'Balanced performance' },
951 { value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', description: 'Fastest Claude model' },
952 ],
953 xai: [
954 { value: 'grok-4-0709', label: 'Grok 4', description: 'Latest flagship model - unparalleled performance in natural language, math and reasoning' }, // Added Grok-4
955 { value: 'grok-3-beta', label: 'Grok-3', description: 'Powerful model with 131K context' },
956 { value: 'grok-3-fast-beta', label: 'Grok-3 Fast', description: 'High performance with faster responses' },
957 { value: 'grok-3-mini-beta', label: 'Grok-3 Mini', description: 'Affordable model with good performance' },
958 { value: 'grok-3-mini-fast-beta', label: 'Grok-3 Mini Fast', description: 'Quick and cost-effective' },
959 { value: 'grok-2', label: 'Grok 2', description: 'Latest X.AI model' },
960 ],
961 deepseek: [
962 { value: 'deepseek-chat', label: 'DeepSeek-V3', description: 'Advanced AI assistant' },
963 ],
964 };
965
966 let allModels = [];
967 Object.keys(models).forEach(key => {
968 if (category === 'all' || category === key) {
969 allModels = allModels.concat(models[key]);
970 }
971 });
972
973 // Filter by search term if present
974 if (filter) {
975 const lowerFilter = filter.toLowerCase();
976 allModels = allModels.filter(model =>
977 model.label.toLowerCase().includes(lowerFilter) ||
978 model.description.toLowerCase().includes(lowerFilter)
979 );
980 }
981
982 // Create model cards
983 allModels.forEach(model => {
984 const isSelected = $modelSelect.val() === model.value;
985 const $modelCard = $(`
986 <div class="mxchat-model-selector-card ${isSelected ? 'mxchat-model-selected' : ''}" data-value="${model.value}">
987 <div class="mxchat-model-selector-icon">${getModelIcon(model.value)}</div>
988 <div class="mxchat-model-selector-info">
989 <h4 class="mxchat-model-selector-title">${model.label}</h4>
990 <p class="mxchat-model-selector-description">${model.description}</p>
991 </div>
992 ${isSelected ? '<div class="mxchat-model-selector-checkmark">✓</div>' : ''}
993 </div>
994 `);
995 $grid.append($modelCard);
996 });
997 }
998
999
1000
1001 // Helper function to get icon for each model
1002 function getModelIcon(modelValue) {
1003 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>';
1004 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>';
1005 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>';
1006 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>';
1007 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>';
1008 return '<span class="dashicons dashicons-admin-generic mxchat-model-icon-generic"></span>';
1009 }
1010
1011 // Event handlers
1012 $modelSelectorButton.on('click', function() {
1013 $('#mxchat_model_selector_modal').show();
1014 populateModelsGrid('', 'all');
1015 });
1016
1017 $('.mxchat-model-selector-modal-close, #mxchat_cancel_model_selection').on('click', function() {
1018 $('#mxchat_model_selector_modal').hide();
1019 });
1020
1021 $('.mxchat-model-category-btn').on('click', function() {
1022 $('.mxchat-model-category-btn').removeClass('active');
1023 $(this).addClass('active');
1024 const category = $(this).data('category');
1025 const searchTerm = $('#mxchat_model_search_input').val();
1026 populateModelsGrid(searchTerm, category);
1027 });
1028
1029 $('#mxchat_model_search_input').on('input', function() {
1030 const searchTerm = $(this).val();
1031 const activeCategory = $('.mxchat-model-category-btn.active').data('category');
1032 populateModelsGrid(searchTerm, activeCategory);
1033 });
1034
1035 $(document).on('click', '.mxchat-model-selector-card', function() {
1036 const modelValue = $(this).data('value');
1037 $modelSelect.val(modelValue).trigger('change');
1038 updateButtonText();
1039 $('#mxchat_model_selector_modal').hide();
1040 });
1041
1042 // Close modal when clicking outside
1043 $(window).on('click', function(event) {
1044 if ($(event.target).is('#mxchat_model_selector_modal')) {
1045 $('#mxchat_model_selector_modal').hide();
1046 }
1047 });
1048 }
1049
1050 // Embedding model selector - completely separate from chat model selector
1051 function setupMxChatEmbeddingModelSelector() {
1052 const $embeddingModelSelect = $('#embedding_model');
1053
1054 // Skip if the element doesn't exist on the page
1055 if ($embeddingModelSelect.length === 0) {
1056 return;
1057 }
1058
1059 const $embeddingModelSelectorButton = $('<button>', {
1060 type: 'button',
1061 id: 'mxchat_embedding_model_selector_btn',
1062 class: 'button-primary mxchat-embedding-model-selector-btn', // Changed class name to be more specific
1063 text: 'Select Embedding Model'
1064 });
1065
1066 // Replace the select dropdown with a button
1067 $embeddingModelSelect.hide().after($embeddingModelSelectorButton);
1068
1069 // Update button text to show currently selected model
1070 function updateButtonText() {
1071 const selectedModel = $embeddingModelSelect.val();
1072 const selectedModelText = $embeddingModelSelect.find('option:selected').text();
1073 $embeddingModelSelectorButton.text(selectedModelText);
1074 }
1075
1076 // Initialize button text
1077 updateButtonText();
1078
1079 // Create a unique ID for the modal to avoid conflicts
1080 const embeddingModalId = 'mxchat_embedding_model_selector_modal';
1081
1082 // Create and append modal HTML with unique IDs
1083 const embeddingModelSelectorModal = `
1084 <div id="${embeddingModalId}" class="mxchat-embedding-model-selector-modal">
1085 <div class="mxchat-embedding-model-selector-modal-content">
1086 <div class="mxchat-embedding-model-selector-modal-header">
1087 <h3>Select Embedding Model</h3>
1088 <span class="mxchat-embedding-model-selector-modal-close">&times;</span>
1089 </div>
1090 <div class="mxchat-embedding-model-selector-modal-body">
1091 <div class="mxchat-embedding-model-selector-search-container">
1092 <input type="text" id="mxchat_embedding_model_search_input" class="mxchat-embedding-model-search-input" placeholder="Search models...">
1093 </div>
1094 <div class="mxchat-embedding-model-selector-categories">
1095 <button class="mxchat-embedding-model-category-btn active" data-category="all">All</button>
1096 <button class="mxchat-embedding-model-category-btn" data-category="openai">OpenAI</button>
1097 <button class="mxchat-embedding-model-category-btn" data-category="voyage">Voyage AI</button>
1098 <button class="mxchat-embedding-model-category-btn" data-category="gemini">Google Gemini</button>
1099 </div>
1100 <div class="mxchat-embedding-model-selector-grid" id="mxchat_embedding_models_grid"></div>
1101 </div>
1102 <div class="mxchat-embedding-model-selector-modal-footer">
1103 <button id="mxchat_cancel_embedding_model_selection" class="button mxchat-embedding-model-cancel-btn">Cancel</button>
1104 </div>
1105 </div>
1106 </div>
1107 `;
1108
1109 // Use jQuery's append to ensure it doesn't clash with existing modals
1110 $('body').append(embeddingModelSelectorModal);
1111
1112 // Populate models grid
1113 function populateEmbeddingModelsGrid(filter = '', category = 'all') {
1114 const $grid = $('#mxchat_embedding_models_grid');
1115 $grid.empty();
1116
1117 // Define embedding models with descriptions and context lengths
1118 const models = {
1119 openai: [
1120 {
1121 value: 'text-embedding-3-small',
1122 label: 'TE3 Small',
1123 description: 'Fast and cost-effective embeddings (1536 dimensions, 8K context)'
1124 },
1125 {
1126 value: 'text-embedding-ada-002',
1127 label: 'Ada 2',
1128 description: 'Balanced performance embeddings (1536 dimensions, 8K context)'
1129 },
1130 {
1131 value: 'text-embedding-3-large',
1132 label: 'TE3 Large',
1133 description: 'High-performance embeddings (3072 dimensions, 8K context)'
1134 }
1135 ],
1136 voyage: [
1137 {
1138 value: 'voyage-3-large',
1139 label: 'Voyage-3 Large',
1140 description: 'Advanced semantic search embeddings (2048 dimensions, 32K context)'
1141 }
1142 ],
1143 gemini: [
1144 {
1145 value: 'gemini-embedding-exp-03-07',
1146 label: 'Gemini Embedding',
1147 description: 'Experimental SOTA embeddings (1536 dimensions, 8K context)'
1148 }
1149 ]
1150 };
1151
1152 let allModels = [];
1153 Object.keys(models).forEach(key => {
1154 if (category === 'all' || category === key) {
1155 allModels = allModels.concat(models[key]);
1156 }
1157 });
1158
1159 // Filter by search term if present
1160 if (filter) {
1161 const lowerFilter = filter.toLowerCase();
1162 allModels = allModels.filter(model =>
1163 model.label.toLowerCase().includes(lowerFilter) ||
1164 model.description.toLowerCase().includes(lowerFilter)
1165 );
1166 }
1167
1168 // Create model cards
1169 allModels.forEach(model => {
1170 const isSelected = $embeddingModelSelect.val() === model.value;
1171 let providerClass = 'mxchat-embedding-model-provider-openai';
1172
1173 if (model.value.startsWith('voyage-')) {
1174 providerClass = 'mxchat-embedding-model-provider-voyage';
1175 } else if (model.value.startsWith('gemini-embedding-')) {
1176 providerClass = 'mxchat-embedding-model-provider-gemini';
1177 }
1178
1179 let iconHTML = '';
1180 if (model.value.startsWith('voyage-')) {
1181 iconHTML = '<span class="dashicons dashicons-chart-line mxchat-embedding-model-icon-voyage"></span>';
1182 } else if (model.value.startsWith('gemini-embedding-')) {
1183 iconHTML = '<span class="dashicons dashicons-google mxchat-embedding-model-icon-gemini"></span>';
1184 } else {
1185 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>';
1186 }
1187
1188 const $modelCard = $(`
1189 <div class="mxchat-embedding-model-selector-card ${isSelected ? 'mxchat-embedding-model-selected' : ''} ${providerClass}" data-value="${model.value}">
1190 <div class="mxchat-embedding-model-selector-icon">
1191 ${iconHTML}
1192 </div>
1193 <div class="mxchat-embedding-model-selector-info">
1194 <h4 class="mxchat-embedding-model-selector-title">${model.label}</h4>
1195 <p class="mxchat-embedding-model-selector-description">${model.description}</p>
1196 </div>
1197 ${isSelected ? '<div class="mxchat-embedding-model-selector-checkmark">✓</div>' : ''}
1198 </div>
1199 `);
1200
1201 $grid.append($modelCard);
1202 });
1203 }
1204
1205 // Event handlers - use namespaced events to avoid conflicts
1206 $embeddingModelSelectorButton.on('click.embeddingModelSelector', function(e) {
1207 e.stopPropagation(); // Prevent event bubbling
1208 $('#' + embeddingModalId).show();
1209 populateEmbeddingModelsGrid('', 'all');
1210 });
1211
1212 $('.mxchat-embedding-model-selector-modal-close, #mxchat_cancel_embedding_model_selection').on('click.embeddingModelSelector', function(e) {
1213 e.stopPropagation(); // Prevent event bubbling
1214 $('#' + embeddingModalId).hide();
1215 });
1216
1217 $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').on('click.embeddingModelSelector', function(e) {
1218 e.stopPropagation(); // Prevent event bubbling
1219 $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').removeClass('active');
1220 $(this).addClass('active');
1221 const category = $(this).data('category');
1222 const searchTerm = $('#mxchat_embedding_model_search_input').val();
1223 populateEmbeddingModelsGrid(searchTerm, category);
1224 });
1225
1226 $('#mxchat_embedding_model_search_input').on('input.embeddingModelSelector', function() {
1227 const searchTerm = $(this).val();
1228 const activeCategory = $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn.active').data('category');
1229 populateEmbeddingModelsGrid(searchTerm, activeCategory);
1230 });
1231
1232 // Use a direct selector to avoid conflicts with other card elements
1233 $(document).on('click.embeddingModelSelector', '.mxchat-embedding-model-selector-grid .mxchat-embedding-model-selector-card', function(e) {
1234 e.stopPropagation(); // Prevent event bubbling
1235 const modelValue = $(this).data('value');
1236
1237 // Important: Only update this specific select element
1238 $embeddingModelSelect.val(modelValue);
1239
1240 // Manually trigger change only on this element
1241 const changeEvent = new Event('change', { bubbles: true });
1242 $embeddingModelSelect[0].dispatchEvent(changeEvent);
1243
1244 // Update button text
1245 updateButtonText();
1246
1247 // Hide modal
1248 $('#' + embeddingModalId).hide();
1249 });
1250
1251 // Close modal when clicking outside - use namespaced events
1252 $(window).on('click.embeddingModelSelector', function(event) {
1253 if ($(event.target).is('#' + embeddingModalId)) {
1254 $('#' + embeddingModalId).hide();
1255 }
1256 });
1257 }
1258
1259 // Call this function after the DOM is fully loaded
1260 $(document).ready(function() {
1261 setupMxChatModelSelector();
1262 setupMxChatEmbeddingModelSelector();
1263 });
1264
1265 // Initialize API key visibility
1266 setupAPIKeyVisibility();
1267
1268 // Add Intent Form Submission
1269 $('#mxchat-add-intent-form').on('submit', function(event) {
1270 $('#mxchat-intent-loading').show();
1271 $('#mxchat-intent-loading-text').show();
1272 $(this).find('button[type="submit"]').hide();
1273 });
1274
1275 // Inline Edit Functionality
1276 $('.edit-button').on('click', function() {
1277 var row = $(this).closest('tr');
1278 row.find('.content-view, .url-view').hide();
1279 row.find('.content-edit, .url-edit').show();
1280 row.find('.edit-button').hide();
1281 row.find('.save-button').show();
1282 });
1283
1284 // Save button handler
1285 // Save button handler
1286 $('.save-button').on('click', function() {
1287 var button = $(this);
1288 var row = button.closest('tr');
1289 var id = button.data('id');
1290 var nonce = button.data('nonce'); // Get nonce from button data attribute
1291 var newContent = row.find('.content-edit').val();
1292 var newUrl = row.find('.url-edit').val();
1293
1294 //console.log('Nonce from button:', nonce); // Debug
1295
1296 button.prop('disabled', true);
1297 button.text('Saving...');
1298
1299 $.ajax({
1300 url: mxchatAdmin.ajax_url,
1301 type: 'POST',
1302 data: {
1303 action: 'mxchat_save_inline_prompt',
1304 id: id,
1305 article_content: newContent,
1306 article_url: newUrl,
1307 _ajax_nonce: nonce // Use nonce from button
1308 },
1309 success: function(response) {
1310 button.prop('disabled', false);
1311 button.text('Save');
1312
1313 if (response.success) {
1314 row.find('.content-view').html(newContent.replace(/\n/g, "<br>"));
1315 if (newUrl) {
1316 row.find('.url-view').html('<a href="' + newUrl + '" target="_blank">' + newUrl + '</a>');
1317 } else {
1318 row.find('.url-view').html('N/A');
1319 }
1320
1321 row.find('.content-edit, .url-edit').hide();
1322 row.find('.content-view, .url-view').show();
1323 row.find('.save-button').hide();
1324 row.find('.edit-button').show();
1325 } else {
1326 alert('Error saving content: ' + (response.data?.message || 'Unknown error'));
1327 }
1328 },
1329 error: function() {
1330 button.prop('disabled', false);
1331 button.text('Save');
1332 alert('An error occurred while saving.');
1333 }
1334 });
1335 });
1336
1337
1338 // Questions handling
1339 $('.mxchat-add-question').on('click', function () {
1340 const container = $('#mxchat-additional-questions-container');
1341 const questionCount = container.find('.mxchat-question-row').length + 4;
1342 const questionIndex = container.find('.mxchat-question-row').length;
1343
1344 const newQuestion = `
1345 <div class="mxchat-question-row">
1346 <input type="text"
1347 name="additional_popular_questions[]"
1348 placeholder="Enter Additional Popular Question ${questionCount}"
1349 class="regular-text mxchat-question-input"
1350 data-question-index="${questionIndex}" />
1351 <button type="button" class="button mxchat-remove-question"
1352 aria-label="Remove question">Remove</button>
1353 </div>
1354 `;
1355 container.append(newQuestion);
1356 });
1357
1358 $(document).on('click', '.mxchat-remove-question', function () {
1359 $(this).closest('.mxchat-question-row').remove();
1360 saveQuestions();
1361 });
1362
1363 $(document).on('change', '.mxchat-question-input', function() {
1364 saveQuestions();
1365 });
1366
1367 function saveQuestions() {
1368 const questions = [];
1369 $('.mxchat-question-input').each(function() {
1370 const value = $(this).val().trim();
1371 if (value) {
1372 questions.push(value);
1373 }
1374 });
1375
1376 const feedbackContainer = $('<div class="feedback-container"></div>');
1377 const spinner = $('<div class="saving-spinner"></div>');
1378 const successIcon = $('<div class="success-icon">✔</div>');
1379
1380 // Append feedback after the add button
1381 $('.mxchat-add-question').after(feedbackContainer);
1382 feedbackContainer.append(spinner);
1383
1384 // Save via AJAX
1385 $.ajax({
1386 url: mxchatAdmin.ajax_url,
1387 type: 'POST',
1388 data: {
1389 action: 'mxchat_save_setting',
1390 name: 'additional_popular_questions',
1391 value: JSON.stringify(questions),
1392 _ajax_nonce: mxchatAdmin.setting_nonce
1393 },
1394 success: function(response) {
1395 if (response.success) {
1396 spinner.fadeOut(200, function() {
1397 feedbackContainer.append(successIcon);
1398 successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
1399 feedbackContainer.remove();
1400 });
1401 });
1402 } else {
1403 alert('Error saving questions: ' + (response.data?.message || 'Unknown error'));
1404 feedbackContainer.remove();
1405 }
1406 },
1407 error: function() {
1408 alert('An error occurred while saving questions.');
1409 feedbackContainer.remove();
1410 }
1411 });
1412 }
1413
1414 // Live agent status handler
1415 const statusToggle = document.getElementById('live_agent_status');
1416 const statusText = statusToggle?.parentElement.nextElementSibling?.querySelector('.status-text');
1417 if (statusToggle && statusText) {
1418 statusToggle.addEventListener('change', function() {
1419 // Update display text
1420 statusText.textContent = this.checked ? 'Online' : 'Offline';
1421
1422 // Send the correct on/off value to the server
1423 if (window.mxchatSaveSetting) {
1424 window.mxchatSaveSetting('live_agent_status', this.checked ? 'on' : 'off');
1425 }
1426 });
1427 }
1428
1429 // Function to adjust the textarea height to content
1430 function adjustTextareaHeight() {
1431 this.style.height = 'auto'; // Reset to auto to calculate scrollHeight
1432 this.style.height = this.scrollHeight + 'px'; // Expand to content height
1433 }
1434
1435 // Function to reset the textarea height to initial
1436 function resetTextareaHeight() {
1437 this.style.height = ''; // Remove inline height, reverting to CSS default
1438 }
1439
1440 // Target the specific textarea by ID
1441 var $textarea = $('#system_prompt_instructions');
1442
1443 // Bind events
1444 $textarea.on('focus input', adjustTextareaHeight) // Expand on focus and input
1445 .on('blur', resetTextareaHeight); // Reset on blur
1446 });
1447
1448
1449
1450 document.addEventListener('DOMContentLoaded', function() {
1451 // Check if we're on the correct page before initializing
1452 const modal = document.getElementById('mxchat-action-modal');
1453
1454 // Only initialize if the modal exists on this page
1455 if (modal) {
1456 //console.log('MXChat Action Modal JS Loaded');
1457
1458 // Initialize the action modal functionality
1459 initStepBasedActionModal();
1460 }
1461
1462 // Function to initialize the step-based action modal
1463 function initStepBasedActionModal() {
1464 // We already checked for modal existence above, so no need to check again
1465
1466 const actionStep1 = document.getElementById('mxchat-action-step-1');
1467 const actionStep2 = document.getElementById('mxchat-action-step-2');
1468 const backToStep1Btn = document.getElementById('mxchat-back-to-step-1');
1469 const searchInput = document.getElementById('action-type-search');
1470 const categoryButtons = modal.querySelectorAll('.mxchat-category-button');
1471 const actionCards = modal.querySelectorAll('.mxchat-action-type-card');
1472 const actionForm = document.getElementById('mxchat-action-form');
1473 const callbackInput = document.getElementById('callback_function');
1474 const actionIdField = document.getElementById('edit_action_id');
1475 const labelField = document.getElementById('intent_label');
1476 const phrasesField = document.getElementById('action_phrases');
1477 const formActionType = document.getElementById('form_action_type');
1478 const nonceContainer = document.getElementById('action-nonce-container');
1479 const thresholdSlider = document.getElementById('similarity_threshold');
1480 const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
1481
1482 // Rest of your initialization code remains the same...
1483
1484 // Log the structure of one action card for debugging
1485 if (actionCards.length > 0) {
1486 //console.log('First action card data attributes:', actionCards[0].dataset);
1487 //console.log('First action card HTML:', actionCards[0].outerHTML);
1488 }
1489
1490 // Add click event listeners to category buttons
1491 categoryButtons.forEach(button => {
1492 button.addEventListener('click', function() {
1493 //console.log('Category button clicked:', this.dataset.category);
1494
1495 // Remove active class from all buttons
1496 categoryButtons.forEach(btn => btn.classList.remove('active'));
1497
1498 // Add active class to clicked button
1499 this.classList.add('active');
1500
1501 // Get selected category
1502 const category = this.dataset.category;
1503
1504 // Filter action cards
1505 filterActionCards(category, searchInput.value);
1506 });
1507 });
1508
1509 // Add search functionality
1510 if (searchInput) {
1511 searchInput.addEventListener('input', function() {
1512 // Get active category
1513 const activeCategory = modal.querySelector('.mxchat-category-button.active')?.dataset.category || 'all';
1514 //console.log('Search input changed, active category:', activeCategory);
1515
1516 // Filter action cards
1517 filterActionCards(activeCategory, this.value);
1518 });
1519 }
1520
1521 // Add click event listeners to action cards
1522 actionCards.forEach(card => {
1523 card.addEventListener('click', function() {
1524 // Get the action data
1525 const isPro = this.dataset.pro === 'true';
1526 const isInstalled = this.dataset.installed === 'true';
1527 const addonName = this.dataset.addon || '';
1528 const actionValue = this.dataset.value;
1529 const actionLabel = this.dataset.label;
1530 const actionIcon = this.querySelector('.dashicons').getAttribute('class').replace('dashicons dashicons-', '');
1531 const actionDescription = this.querySelector('p').textContent;
1532
1533 // Pro check using the proper detection method
1534 const proIsActivated = typeof mxchatAdmin !== 'undefined' &&
1535 (mxchatAdmin.is_activated === '1' ||
1536 mxchatAdmin.is_activated === 'true' ||
1537 mxchatAdmin.is_activated === true);
1538
1539 // Handle different states
1540 if (isPro && !proIsActivated) {
1541 // Pro feature but no Pro license
1542 showProFeatureNotice();
1543 return;
1544 }
1545
1546 if (addonName && !isInstalled) {
1547 // Add-on required but not installed
1548 const addonDisplayName = this.querySelector('.mxchat-addon-info')?.textContent?.replace('Requires ', '') || addonName + ' Add-on';
1549 showAddonRequiredNotice(addonDisplayName);
1550 return;
1551 }
1552
1553 // If we get here, the action is available - proceed as normal
1554 callbackInput.value = actionValue;
1555
1556 // Update the selected action display in step 2
1557 document.getElementById('selected-action-title').textContent = actionLabel;
1558 document.getElementById('selected-action-description').textContent = actionDescription;
1559 document.getElementById('selected-action-icon').innerHTML =
1560 `<span class="dashicons dashicons-${actionIcon}"></span>`;
1561
1562 // Set a default label based on the action type (user can change it)
1563 if (!labelField.value) {
1564 labelField.value = actionLabel;
1565 }
1566
1567 // Move to step 2
1568 actionStep1.classList.remove('active');
1569 actionStep2.classList.add('active');
1570
1571 // Update modal title
1572 });
1573 });
1574
1575 // Back button functionality
1576 if (backToStep1Btn) {
1577 backToStep1Btn.addEventListener('click', function() {
1578 //console.log('Back button clicked');
1579 actionStep2.classList.remove('active');
1580 actionStep1.classList.add('active');
1581 });
1582 }
1583
1584 // Function to filter action cards by category and search term
1585 function filterActionCards(category, searchTerm) {
1586 searchTerm = searchTerm.toLowerCase().trim();
1587 //console.log(`Filtering cards by category: "${category}", search: "${searchTerm}"`);
1588
1589 let visibleCount = 0;
1590
1591 // Show all cards initially with animation
1592 actionCards.forEach((card, index) => {
1593 // Reset animation
1594 card.style.animation = 'none';
1595 // Trigger reflow
1596 void card.offsetWidth;
1597
1598 // Determine if card should be visible based on category and search term
1599 const cardCategory = card.dataset.category || '';
1600 const matchesCategory = category === 'all' || cardCategory === category;
1601
1602 const cardTitle = card.querySelector('h4')?.textContent?.toLowerCase() || '';
1603 const cardDesc = card.querySelector('p')?.textContent?.toLowerCase() || '';
1604 const matchesSearch = searchTerm === '' ||
1605 cardTitle.includes(searchTerm) ||
1606 cardDesc.includes(searchTerm);
1607
1608 // Show/hide card with animation
1609 if (matchesCategory && matchesSearch) {
1610 card.style.display = 'flex';
1611 // Staggered animation for cards
1612 card.style.animation = `fadeIn 0.2s ease forwards ${index * 0.03}s`;
1613 visibleCount++;
1614 } else {
1615 card.style.display = 'none';
1616 }
1617 });
1618
1619 //console.log(`Filter results: ${visibleCount} cards visible out of ${actionCards.length}`);
1620 }
1621
1622 // Function to show notice for Pro features
1623 function showProFeatureNotice() {
1624 //console.log('Showing Pro feature notice');
1625 // Check if we already have a notification container
1626 let noticeContainer = document.querySelector('.mxchat-pro-notice');
1627
1628 if (!noticeContainer) {
1629 // Create the notice container
1630 noticeContainer = document.createElement('div');
1631 noticeContainer.className = 'mxchat-pro-notice';
1632
1633 // Create content
1634 noticeContainer.innerHTML = `
1635 <div class="mxchat-pro-notice-content">
1636 <h3>MxChat Pro Feature</h3>
1637 <p>This action is available in the Pro version only.</p>
1638 <div class="mxchat-pro-notice-buttons">
1639 <button class="mxchat-button-secondary mxchat-pro-notice-close">Close</button>
1640 <a href="https://mxchat.ai/" class="mxchat-button-primary">Upgrade to Pro</a>
1641 </div>
1642 </div>
1643 `;
1644
1645 // Append to body
1646 document.body.appendChild(noticeContainer);
1647
1648 // Add close functionality
1649 const closeButton = noticeContainer.querySelector('.mxchat-pro-notice-close');
1650 closeButton.addEventListener('click', function() {
1651 noticeContainer.classList.remove('active');
1652 setTimeout(() => {
1653 noticeContainer.remove();
1654 }, 300);
1655 });
1656
1657 // Click outside to close
1658 noticeContainer.addEventListener('click', function(e) {
1659 if (e.target === noticeContainer) {
1660 closeButton.click();
1661 }
1662 });
1663
1664 // Show with animation
1665 setTimeout(() => {
1666 noticeContainer.classList.add('active');
1667 }, 10);
1668 } else {
1669 // If it already exists, just make it visible again
1670 noticeContainer.classList.add('active');
1671 }
1672 }
1673
1674 // Function to show notice for add-on requirements
1675 function showAddonRequiredNotice(addonName) {
1676 //console.log(`Showing add-on notice for: ${addonName}`);
1677 // Check if we already have a notification container
1678 let noticeContainer = document.querySelector('.mxchat-addon-notice');
1679
1680 if (!noticeContainer) {
1681 // Create the notice container
1682 noticeContainer = document.createElement('div');
1683 noticeContainer.className = 'mxchat-addon-notice';
1684
1685 // Create content
1686 noticeContainer.innerHTML = `
1687 <div class="mxchat-addon-notice-content">
1688 <span class="mxchat-addon-notice-icon">🧩</span>
1689 <h3>Add-on Required</h3>
1690 <p>This action requires the <strong>${addonName}</strong> add-on to be installed.</p>
1691 <div class="mxchat-addon-notice-buttons">
1692 <button class="mxchat-button-secondary mxchat-addon-notice-close">Close</button>
1693 <a href="admin.php?page=mxchat-addons" class="mxchat-button-primary">Get Add-ons</a>
1694 </div>
1695 </div>
1696 `;
1697
1698 // Append to body
1699 document.body.appendChild(noticeContainer);
1700
1701 // Add close functionality
1702 const closeButton = noticeContainer.querySelector('.mxchat-addon-notice-close');
1703 closeButton.addEventListener('click', function() {
1704 noticeContainer.classList.remove('active');
1705 setTimeout(() => {
1706 noticeContainer.remove();
1707 }, 300);
1708 });
1709
1710 // Click outside to close
1711 noticeContainer.addEventListener('click', function(e) {
1712 if (e.target === noticeContainer) {
1713 closeButton.click();
1714 }
1715 });
1716
1717 // Show with animation
1718 setTimeout(() => {
1719 noticeContainer.classList.add('active');
1720 }, 10);
1721 } else {
1722 // If it already exists, update the content
1723 const addonNameElement = noticeContainer.querySelector('p strong');
1724 if (addonNameElement) {
1725 addonNameElement.textContent = addonName;
1726 }
1727
1728 // Make it visible again
1729 noticeContainer.classList.add('active');
1730 }
1731 }
1732
1733 // Form submission handling
1734 if (actionForm) {
1735 actionForm.addEventListener('submit', function() {
1736 //console.log('Form submitted');
1737 document.getElementById('mxchat-action-loading').style.display = 'flex';
1738 this.querySelector('button[type="submit"]').disabled = true;
1739 });
1740 }
1741 }
1742
1743 // Setup add action buttons (only if we're on the correct page)
1744 if (modal) {
1745 // Update the modal open function to support the step-based flow
1746 window.mxchatOpenActionModal = function(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '', enabledBots = null) {
1747 //console.log('Modal opening, edit mode:', isEdit);
1748
1749 // Get form fields
1750 const actionIdField = document.getElementById('edit_action_id');
1751 const labelField = document.getElementById('intent_label');
1752 const phrasesField = document.getElementById('action_phrases');
1753 const formActionType = document.getElementById('form_action_type');
1754 const callbackInput = document.getElementById('callback_function');
1755 const saveButton = document.getElementById('mxchat-save-action-btn');
1756 const nonceContainer = document.getElementById('action-nonce-container');
1757 const thresholdSlider = document.getElementById('similarity_threshold');
1758 const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
1759 const actionStep1 = document.getElementById('mxchat-action-step-1');
1760 const actionStep2 = document.getElementById('mxchat-action-step-2');
1761 const searchInput = document.getElementById('action-type-search');
1762
1763 // Set up modal for edit or create
1764 if (isEdit) {
1765 saveButton.textContent = 'Update Action';
1766 formActionType.value = 'mxchat_edit_intent';
1767 actionIdField.value = actionId;
1768 labelField.value = label;
1769 phrasesField.value = phrases;
1770 callbackInput.value = callbackFunction;
1771 thresholdSlider.value = threshold; // Set the current threshold value
1772 thresholdDisplay.textContent = threshold + '%'; // Update display
1773
1774 // NEW: Handle bot selection checkboxes for editing
1775 // First uncheck all bot checkboxes
1776 document.querySelectorAll('input[name="enabled_bots[]"]').forEach(checkbox => {
1777 checkbox.checked = false;
1778 });
1779
1780 // Then check the ones that should be enabled
1781 if (enabledBots) {
1782 let botsArray;
1783 if (typeof enabledBots === 'string') {
1784 try {
1785 botsArray = JSON.parse(enabledBots);
1786 } catch (e) {
1787 console.warn('Failed to parse enabledBots:', enabledBots);
1788 botsArray = ['default']; // fallback
1789 }
1790 } else if (Array.isArray(enabledBots)) {
1791 botsArray = enabledBots;
1792 } else {
1793 botsArray = ['default']; // fallback
1794 }
1795
1796 botsArray.forEach(botId => {
1797 const checkbox = document.querySelector(`input[name="enabled_bots[]"][value="${botId}"]`);
1798 if (checkbox) {
1799 checkbox.checked = true;
1800 }
1801 });
1802 } else {
1803 // Fallback to default if no bot data
1804 const defaultCheckbox = document.querySelector('input[name="enabled_bots[]"][value="default"]');
1805 if (defaultCheckbox) {
1806 defaultCheckbox.checked = true;
1807 }
1808 }
1809
1810 // Update the nonce field for editing
1811 nonceContainer.innerHTML = ''; // Clear existing nonce
1812 if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) {
1813 nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.edit_intent_nonce}">`;
1814 }
1815
1816 // For editing, go directly to step 2 and update the selected action display
1817 actionStep1.classList.remove('active');
1818 actionStep2.classList.add('active');
1819
1820 // Find the matching action card to get its details
1821 const actionCards = document.querySelectorAll('.mxchat-action-type-card');
1822 let foundCard = null;
1823
1824 actionCards.forEach(card => {
1825 if (card.dataset.value === callbackFunction) {
1826 foundCard = card;
1827 }
1828 });
1829
1830 if (foundCard) {
1831 //console.log('Found matching action card for:', callbackFunction);
1832 const actionLabel = foundCard.dataset.label || foundCard.querySelector('h4')?.textContent || '';
1833 const actionIconElement = foundCard.querySelector('.dashicons');
1834 const actionIcon = actionIconElement
1835 ? actionIconElement.getAttribute('class').replace('dashicons dashicons-', '')
1836 : 'admin-generic';
1837 const actionDescription = foundCard.querySelector('p')?.textContent || '';
1838
1839 document.getElementById('selected-action-title').textContent = actionLabel;
1840 document.getElementById('selected-action-description').textContent = actionDescription;
1841 document.getElementById('selected-action-icon').innerHTML =
1842 `<span class="dashicons dashicons-${actionIcon}"></span>`;
1843 } else {
1844 //console.log('No matching action card found for:', callbackFunction);
1845 // Fallback if we can't find the card
1846 document.getElementById('selected-action-title').textContent = label;
1847 document.getElementById('selected-action-description').textContent = 'Configure this action for your chatbot';
1848 document.getElementById('selected-action-icon').innerHTML =
1849 `<span class="dashicons dashicons-admin-generic"></span>`;
1850 }
1851 } else {
1852 //console.log('Setting up create mode');
1853 saveButton.textContent = 'Save Action';
1854 formActionType.value = 'mxchat_add_intent';
1855 actionIdField.value = '';
1856 labelField.value = '';
1857 phrasesField.value = '';
1858 callbackInput.value = '';
1859 thresholdSlider.value = 85; // Default value for new actions
1860 thresholdDisplay.textContent = '85%'; // Default display
1861
1862 // For new actions, ensure default is checked and others are unchecked
1863 document.querySelectorAll('input[name="enabled_bots[]"]').forEach(checkbox => {
1864 checkbox.checked = (checkbox.value === 'default');
1865 });
1866
1867 // Update the nonce field for adding
1868 nonceContainer.innerHTML = ''; // Clear existing nonce
1869 if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) {
1870 nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.add_intent_nonce}">`;
1871 }
1872
1873 // For creating new, start at step 1
1874 actionStep1.classList.add('active');
1875 actionStep2.classList.remove('active');
1876 }
1877
1878 // Show modal with animation
1879 modal.style.display = 'flex';
1880 requestAnimationFrame(() => {
1881 modal.classList.add('active');
1882 });
1883
1884 // Set up close handlers
1885 const closeModal = () => {
1886 //console.log('Closing modal');
1887 modal.classList.remove('active');
1888 setTimeout(() => {
1889 modal.style.display = 'none';
1890 }, 300); // Match the CSS transition time
1891 };
1892
1893 // Close button handler
1894 const closeBtn = modal.querySelector('.mxchat-modal-close');
1895 if (closeBtn) {
1896 closeBtn.onclick = closeModal;
1897 }
1898
1899 // Cancel button handler
1900 const cancelBtns = modal.querySelectorAll('.mxchat-modal-cancel');
1901 if (cancelBtns) {
1902 cancelBtns.forEach(btn => {
1903 btn.onclick = closeModal;
1904 });
1905 }
1906
1907 // Click outside modal to close
1908 modal.onclick = (e) => {
1909 if (e.target === modal) {
1910 closeModal();
1911 }
1912 };
1913
1914 // Escape key to close modal
1915 document.addEventListener('keydown', function(e) {
1916 if (e.key === 'Escape' && modal.classList.contains('active')) {
1917 closeModal();
1918 }
1919 }, { once: true });
1920
1921 // Focus appropriate field based on current step
1922 if (isEdit || actionStep2.classList.contains('active')) {
1923 if (labelField) labelField.focus();
1924 } else {
1925 if (searchInput) searchInput.focus();
1926 }
1927
1928 return closeModal; // Return close function for external use
1929 };
1930 // Setup add action buttons
1931 const addActionBtn = document.getElementById('mxchat-add-action-btn');
1932 if (addActionBtn) {
1933 //console.log('Add action button found');
1934 addActionBtn.onclick = () => window.mxchatOpenActionModal();
1935 }
1936
1937 const createFirstAction = document.getElementById('mxchat-create-first-action');
1938 if (createFirstAction) {
1939 //console.log('Create first action button found');
1940 createFirstAction.onclick = () => window.mxchatOpenActionModal();
1941 }
1942
1943 // Setup edit buttons
1944 const editButtons = document.querySelectorAll('.mxchat-action-card .mxchat-edit-button');
1945 //console.log('Edit buttons found:', editButtons.length);
1946 editButtons.forEach(button => {
1947 button.onclick = () => {
1948 const actionId = button.dataset.actionId;
1949 const phrases = button.dataset.phrases;
1950 const label = button.dataset.label;
1951 const threshold = button.dataset.threshold || 85;
1952 const callbackFunction = button.dataset.callbackFunction;
1953 const enabledBots = button.dataset.enabledBots; // ADD THIS LINE
1954
1955 window.mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction, enabledBots);
1956 };
1957 });
1958 }
1959 });
1960
1961 jQuery(document).ready(function($) {
1962 // Toggle custom post types container
1963 $('#mxchat-custom-post-types-toggle').on('click', function(e) {
1964 e.preventDefault();
1965
1966 $('#mxchat-custom-post-types-container').slideToggle(300);
1967
1968 // Rotate the toggle icon
1969 const $icon = $(this).find('.mxchat-accordion-icon');
1970 if ($('#mxchat-custom-post-types-container').is(':visible')) {
1971 $icon.css('transform', 'rotate(180deg)');
1972 $(this).closest('.mxchat-settings-accordion').addClass('active');
1973 } else {
1974 $icon.css('transform', 'rotate(0deg)');
1975 $(this).closest('.mxchat-settings-accordion').removeClass('active');
1976 }
1977 });
1978
1979 // If there are any selections made, auto-expand the container
1980 function autoExpandIfNeeded() {
1981 // Check if any checkbox in the container is checked
1982 const hasCheckedItems = $('#mxchat-custom-post-types-container input[type="checkbox"]:checked').length > 0;
1983
1984 if (hasCheckedItems) {
1985 $('#mxchat-custom-post-types-container').show();
1986 $('#mxchat-custom-post-types-toggle .mxchat-accordion-icon').css('transform', 'rotate(180deg)');
1987 $('.mxchat-settings-accordion').addClass('active');
1988 }
1989 }
1990
1991 // Run on page load
1992 autoExpandIfNeeded();
1993 });
1994
1995 document.addEventListener('DOMContentLoaded', function() {
1996 var viewSampleBtn = document.getElementById('mxchatViewSampleBtn');
1997 var modal = document.getElementById('mxchatSampleModal');
1998 var modalClose = document.getElementById('mxchatModalClose');
1999 var closeBtn = document.getElementById('mxchatCloseBtn');
2000 var copyBtn = document.getElementById('mxchatCopyBtn');
2001 var instructionsContent = document.querySelector('.mxchat-instructions-content');
2002 var modalContent = document.querySelector('.mxchat-instructions-modal-content');
2003
2004 if (!viewSampleBtn || !modal) {
2005 return;
2006 }
2007
2008 // Open modal
2009 viewSampleBtn.addEventListener('click', function(e) {
2010 e.preventDefault();
2011 e.stopPropagation();
2012 modal.classList.add('mxchat-instructions-show');
2013 });
2014
2015 // Close modal function
2016 function closeModal(e) {
2017 if (e) {
2018 e.preventDefault();
2019 e.stopPropagation();
2020 }
2021 modal.classList.remove('mxchat-instructions-show');
2022 }
2023
2024 // Close modal events
2025 if (modalClose) {
2026 modalClose.addEventListener('click', function(e) {
2027 closeModal(e);
2028 });
2029 }
2030
2031 if (closeBtn) {
2032 closeBtn.addEventListener('click', function(e) {
2033 closeModal(e);
2034 });
2035 }
2036
2037 // Close on backdrop click ONLY (not on hover)
2038 modal.addEventListener('click', function(e) {
2039 // Only close if clicking directly on the overlay, not on child elements
2040 if (e.target === modal) {
2041 closeModal(e);
2042 }
2043 });
2044
2045 // Prevent modal content clicks from closing the modal
2046 if (modalContent) {
2047 modalContent.addEventListener('click', function(e) {
2048 e.stopPropagation();
2049 });
2050 }
2051
2052 // Close on escape key
2053 document.addEventListener('keydown', function(e) {
2054 if (e.key === 'Escape' && modal.classList.contains('mxchat-instructions-show')) {
2055 closeModal();
2056 }
2057 });
2058
2059 // Copy functionality
2060 if (copyBtn && instructionsContent) {
2061 copyBtn.addEventListener('click', function(e) {
2062 e.preventDefault();
2063 e.stopPropagation();
2064
2065 var text = instructionsContent.textContent;
2066
2067 if (navigator.clipboard) {
2068 navigator.clipboard.writeText(text).then(function() {
2069 showCopySuccess();
2070 }).catch(function() {
2071 fallbackCopy(text);
2072 });
2073 } else {
2074 fallbackCopy(text);
2075 }
2076 });
2077 }
2078
2079 function fallbackCopy(text) {
2080 var textArea = document.createElement('textarea');
2081 textArea.value = text;
2082 textArea.style.position = 'fixed';
2083 textArea.style.left = '-999999px';
2084 textArea.style.top = '-999999px';
2085 document.body.appendChild(textArea);
2086 textArea.select();
2087 try {
2088 document.execCommand('copy');
2089 showCopySuccess();
2090 } catch (err) {
2091 console.error('Copy failed');
2092 }
2093 document.body.removeChild(textArea);
2094 }
2095
2096 function showCopySuccess() {
2097 var originalText = copyBtn.innerHTML;
2098 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!';
2099
2100 setTimeout(function() {
2101 copyBtn.innerHTML = originalText;
2102 }, 2000);
2103 }
2104 });
2105
2106 jQuery(document).ready(function($) {
2107 var emailDependentFields = [
2108 '#email_blocker_header_content',
2109 '#email_blocker_button_text',
2110 '#enable_name_field',
2111 '#name_field_placeholder'
2112 ];
2113
2114 // Add visual indicators
2115 emailDependentFields.forEach(function(fieldId) {
2116 var $row = $(fieldId).closest('tr');
2117 $row.addClass('email-dependent-field');
2118
2119 // Add an icon to show it's dependent
2120 var $label = $row.find('th label, th');
2121 $label.prepend('<span class="email-dependent-icon" style="color: #0073aa; margin-right: 5px;">↳</span>');
2122 });
2123
2124 // Hide initially with opacity for smoother transition
2125 emailDependentFields.forEach(function(fieldId) {
2126 $(fieldId).closest('tr').hide().css('opacity', '0');
2127 });
2128
2129 function toggleEmailFields() {
2130 var emailEnabled = $('#enable_email_block').is(':checked');
2131
2132 emailDependentFields.forEach(function(fieldId) {
2133 var $row = $(fieldId).closest('tr');
2134 if (emailEnabled) {
2135 $row.slideDown(400).animate({opacity: 1}, 200);
2136 } else {
2137 $row.animate({opacity: 0}, 200).slideUp(400);
2138 }
2139 });
2140 }
2141
2142 toggleEmailFields();
2143 $('#enable_email_block').on('change', toggleEmailFields);
2144 });
2145
2146
2147
2148 //Handle role restriction changes
2149 jQuery(document).ready(function($) {
2150 //Handle role restriction changes for both data sources
2151 $(document).on('change', '.mxchat-role-select', function() {
2152 const $select = $(this);
2153 const entryId = $select.data('entry-id');
2154 const dataSource = $select.data('data-source');
2155 const roleRestriction = $select.val();
2156 const nonce = $select.data('nonce');
2157
2158 // Visual feedback
2159 $select.prop('disabled', true).addClass('updating');
2160
2161 $.ajax({
2162 url: ajaxurl,
2163 type: 'POST',
2164 data: {
2165 action: 'mxchat_update_role_restriction',
2166 nonce: nonce,
2167 entry_id: entryId,
2168 data_source: dataSource, // NEW: Include data source
2169 role_restriction: roleRestriction
2170 },
2171 success: function(response) {
2172 if (response.success) {
2173 // Show success feedback
2174 $select.removeClass('updating').addClass('updated');
2175 setTimeout(() => {
2176 $select.removeClass('updated');
2177 }, 2000);
2178 } else {
2179 alert('Failed to update role restriction: ' + response.data);
2180 // Revert selection
2181 $select.val($select.data('original-value'));
2182 }
2183 },
2184 error: function() {
2185 alert('Error updating role restriction');
2186 // Revert selection
2187 $select.val($select.data('original-value'));
2188 },
2189 complete: function() {
2190 $select.prop('disabled', false);
2191 }
2192 });
2193
2194 // Store original value for potential revert
2195 $select.data('original-value', roleRestriction);
2196 });
2197
2198 // Store initial values
2199 $('.mxchat-role-select').each(function() {
2200 $(this).data('original-value', $(this).val());
2201 });
2202 });