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