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