PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.5.2
MxChat – AI Chatbot & Content Generation for WordPress v2.5.2
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 +2377 -240 2.0.32.5.2 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').not('#model, #openrouter_selected_model').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,1097 @@
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();
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',
739 + '#toggleOpenRouterApiKeyVisibility'
740 +].forEach(toggleVisibility);
741 +
742 +// Handle API key visibility based on model selection
743 +function setupAPIKeyVisibility() {
744 + // Cache the selectors
745 + const $chatModelSelect = $('#model');
746 + const $embeddingModelSelect = $('#embedding_model');
747 +
748 + // First, locate and mark the API key rows
749 + setupAPIKeyRows();
750 +
751 + // Initial setup based on current selections
752 + updateApiKeyVisibility();
753 +
754 + // Listen for changes to the model selectors
755 + $chatModelSelect.on('change', updateApiKeyVisibility);
756 + $embeddingModelSelect.on('change', updateApiKeyVisibility);
757 +
758 + /**
759 + * Locate and mark rows that contain API key fields
760 + */
761 + function setupAPIKeyRows() {
762 + // Find key rows by their field IDs
763 + const providerMap = {
764 + 'api_key': 'openai',
765 + 'xai_api_key': 'xai',
766 + 'claude_api_key': 'claude',
767 + 'deepseek_api_key': 'deepseek',
768 + 'voyage_api_key': 'voyage',
769 + 'gemini_api_key': 'gemini' // Added Gemini API key mapping
770 + };
771 +
772 + $.each(providerMap, function(fieldId, provider) {
773 + const $field = $('#' + fieldId);
774 + if ($field.length) {
775 + const $row = $field.closest('tr');
776 + $row.addClass('mxchat-setting-row');
777 + $row.attr('data-provider', provider);
778 + }
779 + });
780 + }
781 +
782 +/**
783 + * Updates the visibility of API key fields based on current model selections
784 + */
785 +function updateApiKeyVisibility() {
786 + const chatModel = $chatModelSelect.val();
787 + const embeddingModel = $embeddingModelSelect.val();
788 +
789 + // Determine which providers are needed
790 + const isOpenAIChat = chatModel && chatModel.startsWith('gpt-');
791 + const isXAI = chatModel && chatModel.startsWith('grok-');
792 + const isClaude = chatModel && chatModel.startsWith('claude-');
793 + const isDeepSeek = chatModel && chatModel.startsWith('deepseek-');
794 + const isGemini = chatModel && chatModel.startsWith('gemini-'); // Added Gemini detection
795 +
796 + const isOpenAIEmbedding = embeddingModel && embeddingModel.startsWith('text-embedding-');
797 + const isVoyage = embeddingModel && embeddingModel.startsWith('voyage-');
798 + const isGeminiEmbedding = embeddingModel && embeddingModel.startsWith('gemini-embedding-');
799 +
800 + // Update API key visibility for each provider
801 + updateWrapperVisibility('openai', isOpenAIChat || isOpenAIEmbedding);
802 + updateWrapperVisibility('xai', isXAI);
803 + updateWrapperVisibility('claude', isClaude);
804 + updateWrapperVisibility('deepseek', isDeepSeek);
805 + updateWrapperVisibility('voyage', isVoyage);
806 + updateWrapperVisibility('gemini', isGemini || isGeminiEmbedding); // Updated Gemini visibility for both chat and embedding
807 +
808 + // Update provider-specific notices for OpenAI
809 + if (isOpenAIChat && isOpenAIEmbedding) {
810 + $('div[data-provider="openai"] .api-key-notice').text(
811 + 'Required for your selected chat model and embedding model. Important: You must add credits before use.'
812 + );
813 + } else if (isOpenAIChat) {
814 + $('div[data-provider="openai"] .api-key-notice').text(
815 + 'Required for your selected chat model. Important: You must add credits before use.'
816 + );
817 + } else if (isOpenAIEmbedding) {
818 + $('div[data-provider="openai"] .api-key-notice').text(
819 + 'Required for your selected embedding model. Important: You must add credits before use.'
820 + );
821 + }
822 +
823 + // Update provider-specific notices for Gemini
824 + if (isGemini && isGeminiEmbedding) {
825 + $('div[data-provider="gemini"] .api-key-notice').text(
826 + 'Required for your selected chat model and embedding model.'
827 + );
828 + } else if (isGemini) {
829 + $('div[data-provider="gemini"] .api-key-notice').text(
830 + 'Required for your selected chat model.'
831 + );
832 + } else if (isGeminiEmbedding) {
833 + $('div[data-provider="gemini"] .api-key-notice').text(
834 + 'Required for your selected embedding model.'
835 + );
836 + }
837 +}
838 +
839 + /**
840 + * Updates visibility of a specific provider's API key wrapper
841 + */
842 + function updateWrapperVisibility(provider, isVisible) {
843 + const $row = $('tr.mxchat-setting-row[data-provider="' + provider + '"]');
844 +
845 + if (!$row.length) {
846 + //console.warn('API key row not found for provider: ' + provider);
847 + return;
848 + }
849 +
850 + if (isVisible) {
851 + $row.show();
852 + if (!$row.hasClass('highlighted')) {
853 + $row.addClass('highlighted');
854 + setTimeout(() => {
855 + $row.removeClass('highlighted');
856 + }, 1500);
857 + }
858 + } else {
859 + $row.hide();
860 + }
861 + }
862 +}
863 +
864 +function setupMxChatModelSelector() {
865 + const $modelSelect = $('#model');
866 + const $modelSelectorButton = $('<button>', {
867 + type: 'button',
868 + id: 'mxchat_model_selector_btn',
869 + class: 'button-primary mxchat-model-selector-btn',
870 + text: 'Select AI Model'
871 + });
872 +
873 + // Replace the select dropdown with a button
874 + $modelSelect.hide().after($modelSelectorButton);
875 +
876 + // Update button text to show currently selected model
877 + function updateButtonText() {
878 + const selectedModel = $modelSelect.val();
879 +
880 + // Check if OpenRouter is selected
881 + if (selectedModel === 'openrouter') {
882 + const openrouterModelId = $('#openrouter_selected_model').val();
883 + const openrouterModelName = $('#openrouter_selected_model_name').val();
260 884
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('#', '');
885 + if (openrouterModelName && openrouterModelName.trim() !== '') {
886 + $modelSelectorButton.text('OpenRouter: ' + openrouterModelName);
887 + } else if (openrouterModelId && openrouterModelId.trim() !== '') {
888 + $modelSelectorButton.text('OpenRouter: ' + openrouterModelId);
267 889 } else {
268 - tabId = $this.data('tab') || 'chatbot';
890 + $modelSelectorButton.text('OpenRouter - Select Model');
269 891 }
270 -
271 - // Safety check for empty tabId
272 - if (!tabId) {
273 - console.warn('No tab identifier found');
274 - return;
892 + } else {
893 + const selectedModelText = $modelSelect.find('option:selected').text();
894 + $modelSelectorButton.text(selectedModelText);
895 + }
896 + }
897 +
898 + // Initialize button text
899 + updateButtonText();
900 +
901 + // Create and append modal HTML
902 + const modelSelectorModal = `
903 + <div id="mxchat_model_selector_modal" class="mxchat-model-selector-modal">
904 + <div class="mxchat-model-selector-modal-content">
905 + <div class="mxchat-model-selector-modal-header">
906 + <h3>Select AI Model</h3>
907 + <span class="mxchat-model-selector-modal-close">&times;</span>
908 + </div>
909 + <div class="mxchat-model-selector-modal-body">
910 + <div class="mxchat-model-selector-search-container">
911 + <input type="text" id="mxchat_model_search_input" class="mxchat-model-search-input" placeholder="Search models...">
912 + </div>
913 + <div class="mxchat-model-selector-categories">
914 + <button class="mxchat-model-category-btn active" data-category="all">All</button>
915 + <button class="mxchat-model-category-btn" data-category="openrouter">OpenRouter</button>
916 + <button class="mxchat-model-category-btn" data-category="gemini">Google Gemini</button>
917 + <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
918 + <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
919 + <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
920 + <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
921 + </div>
922 + <div class="mxchat-model-selector-grid" id="mxchat_models_grid"></div>
923 + </div>
924 + <div class="mxchat-model-selector-modal-footer">
925 + <button id="mxchat_cancel_model_selection" class="button mxchat-model-cancel-btn">Cancel</button>
926 + </div>
927 + </div>
928 + </div>
929 + `;
930 +
931 + $('body').append(modelSelectorModal);
932 +
933 + // MOVE THIS OUTSIDE - Make it a property of the window object so it's accessible globally
934 + window.populateModelsGrid = function(filter = '', category = 'all') {
935 + const $grid = $('#mxchat_models_grid');
936 + $grid.empty();
937 +
938 + const models = {
939 + openrouter: [
940 + { value: 'openrouter', label: 'OpenRouter', description: 'Access 100+ models from multiple providers (add API key to browse)' }
941 + ],
942 + gemini: [
943 + { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', description: 'Next-Gen features, speed & multimodal generation' },
944 + { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash-Lite', description: 'Cost-efficient with low latency' },
945 + { value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', description: 'Complex reasoning tasks requiring more intelligence' },
946 + { value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', description: 'Fast and versatile performance' },
947 + ],
948 + openai: [
949 + { value: 'gpt-5', label: 'GPT-5', description: 'Flagship for coding, reasoning, and agentic tasks across domains' },
950 + { value: 'gpt-5-mini', label: 'GPT-5 Mini', description: 'Faster, more cost-efficient for well-defined tasks and precise prompts' },
951 + { value: 'gpt-5-nano', label: 'GPT-5 Nano', description: 'Fastest and cheapest; ideal for summarization and classification' },
952 + { value: 'gpt-4.1-2025-04-14', label: 'GPT-4.1', description: 'Flagship model for complex tasks' },
953 + { value: 'gpt-4o', label: 'GPT-4o', description: 'Recommended for most use cases' },
954 + { value: 'gpt-4o-mini', label: 'GPT-4o Mini', description: 'Fast and lightweight' },
955 + { value: 'gpt-4-turbo', label: 'GPT-4 Turbo', description: 'High-performance model' },
956 + { value: 'gpt-4', label: 'GPT-4', description: 'High intelligence model' },
957 + { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', description: 'Affordable and fast' },
958 + ],
959 + claude: [
960 + { value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5', description: 'Best for complex agents and coding' },
961 + { value: 'claude-opus-4-1-20250805', label: 'Claude Opus 4.1', description: 'Exceptional for specialized complex tasks' },
962 + { value: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', description: 'Fastest and most intelligent Haiku' },
963 + { value: 'claude-opus-4-20250514', label: 'Claude 4 Opus', description: 'Most capable Claude model' },
964 + { value: 'claude-sonnet-4-20250514', label: 'Claude 4 Sonnet', description: 'High performance' },
965 + { value: 'claude-3-7-sonnet-20250219', label: 'Claude 3.7 Sonnet', description: 'High intelligence' },
966 + { value: 'claude-3-opus-20240229', label: 'Claude 3 Opus', description: 'Highly complex tasks' },
967 + { value: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', description: 'Balanced performance' },
968 + { value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', description: 'Fastest Claude model' },
969 + ],
970 + xai: [
971 + { value: 'grok-4-0709', label: 'Grok 4', description: 'Latest flagship model - unparalleled performance in natural language, math and reasoning' },
972 + { value: 'grok-3-beta', label: 'Grok-3', description: 'Powerful model with 131K context' },
973 + { value: 'grok-3-fast-beta', label: 'Grok-3 Fast', description: 'High performance with faster responses' },
974 + { value: 'grok-3-mini-beta', label: 'Grok-3 Mini', description: 'Affordable model with good performance' },
975 + { value: 'grok-3-mini-fast-beta', label: 'Grok-3 Mini Fast', description: 'Quick and cost-effective' },
976 + { value: 'grok-2', label: 'Grok 2', description: 'Latest X.AI model' },
977 + ],
978 + deepseek: [
979 + { value: 'deepseek-chat', label: 'DeepSeek-V3', description: 'Advanced AI assistant' },
980 + ],
981 + };
982 +
983 + let allModels = [];
984 + Object.keys(models).forEach(key => {
985 + if (category === 'all' || category === key) {
986 + allModels = allModels.concat(models[key]);
275 987 }
988 + });
989 +
990 + // Filter by search term if present
991 + if (filter) {
992 + const lowerFilter = filter.toLowerCase();
993 + allModels = allModels.filter(model =>
994 + model.label.toLowerCase().includes(lowerFilter) ||
995 + model.description.toLowerCase().includes(lowerFilter)
996 + );
997 + }
998 +
999 + // Create model cards
1000 + allModels.forEach(model => {
1001 + const isSelected = $modelSelect.val() === model.value;
1002 + const $modelCard = $(`
1003 + <div class="mxchat-model-selector-card ${isSelected ? 'mxchat-model-selected' : ''}" data-value="${model.value}">
1004 + <div class="mxchat-model-selector-icon">${getModelIcon(model.value)}</div>
1005 + <div class="mxchat-model-selector-info">
1006 + <h4 class="mxchat-model-selector-title">${model.label}</h4>
1007 + <p class="mxchat-model-selector-description">${model.description}</p>
1008 + </div>
1009 + ${isSelected ? '<div class="mxchat-model-selector-checkmark">✓</div>' : ''}
1010 + </div>
1011 + `);
1012 + $grid.append($modelCard);
1013 + });
1014 + };
1015 +
1016 + // Helper function to get icon for each model
1017 + function getModelIcon(modelValue) {
1018 + if (modelValue === 'openrouter') return '<span class="dashicons dashicons-networking" style="font-size: 24px; color: #6750A4;"></span>';
1019 + 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>';
1020 + 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>';
1021 + 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>';
1022 + 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>';
1023 + 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>';
1024 + return '<span class="dashicons dashicons-admin-generic mxchat-model-icon-generic"></span>';
1025 + }
1026 +
1027 + // Event handlers
1028 + $modelSelectorButton.on('click', function() {
1029 + $('#mxchat_model_selector_modal').show();
1030 + window.populateModelsGrid('', 'all');
1031 + });
1032 +
1033 + $('.mxchat-model-selector-modal-close, #mxchat_cancel_model_selection').on('click', function() {
1034 + $('#mxchat_model_selector_modal').hide();
1035 + });
1036 +
1037 + $('.mxchat-model-category-btn').on('click', function() {
1038 + $('.mxchat-model-category-btn').removeClass('active');
1039 + $(this).addClass('active');
1040 + const category = $(this).data('category');
1041 + const searchTerm = $('#mxchat_model_search_input').val();
1042 + window.populateModelsGrid(searchTerm, category);
1043 + });
1044 +
1045 + $('#mxchat_model_search_input').on('input', function() {
1046 + const searchTerm = $(this).val();
1047 + const activeCategory = $('.mxchat-model-category-btn.active').data('category');
1048 + window.populateModelsGrid(searchTerm, activeCategory);
1049 + });
1050 +
1051 +$(document).on('click', '.mxchat-model-selector-card', function() {
1052 + const modelValue = $(this).data('value');
1053 + const $modelSelect = $('#model');
1054 + const $clickedCard = $(this);
1055 +
1056 + // Check if OpenRouter was selected
1057 + if (modelValue === 'openrouter') {
1058 + // Load OpenRouter models instead of closing
1059 + loadOpenRouterModels();
1060 + } else {
1061 + // Remove selection from all other cards
1062 + $('.mxchat-model-selector-card').removeClass('mxchat-model-selected').find('.mxchat-model-selector-checkmark').remove();
1063 +
1064 + // Add selection to clicked card immediately for instant feedback
1065 + $clickedCard.addClass('mxchat-model-selected');
1066 + if ($clickedCard.find('.mxchat-model-selector-checkmark').length === 0) {
1067 + $clickedCard.append('<div class="mxchat-model-selector-checkmark">✓</div>');
1068 + }
1069 +
1070 + // Brief delay to show the selection, then start saving
1071 + setTimeout(function() {
1072 + // Normal model selection
1073 + $modelSelect.val(modelValue).trigger('change');
1074 +
1075 + // Show loading state on the card
1076 + $clickedCard.css('pointer-events', 'none');
1077 + const originalContent = $clickedCard.find('.mxchat-model-selector-title').html();
1078 + $clickedCard.find('.mxchat-model-selector-title').html(
1079 + '<span class="spinner is-active" style="float: none; margin: 0 5px 0 0;"></span> Saving...'
1080 + );
1081 +
1082 + // Manually save the model via AJAX
1083 + jQuery.ajax({
1084 + url: mxchatAdmin.ajax_url,
1085 + type: 'POST',
1086 + data: {
1087 + action: 'mxchat_save_setting',
1088 + name: 'model',
1089 + value: modelValue,
1090 + _ajax_nonce: mxchatAdmin.setting_nonce
1091 + },
1092 + success: function(response) {
1093 + if (response.success) {
1094 + // Show success state
1095 + $clickedCard.find('.mxchat-model-selector-title').html(
1096 + '<span class="dashicons dashicons-yes" style="color: #46b450; margin-top: 3px;"></span> Saved!'
1097 + );
1098 +
1099 + // Update button text after successful save
1100 + const selectedModelText = $modelSelect.find('option:selected').text();
1101 + $('#mxchat_model_selector_btn').text(selectedModelText);
1102 +
1103 + // Close modal after a short delay to show the success message
1104 + setTimeout(function() {
1105 + $('#mxchat_model_selector_modal').hide();
1106 + // Restore original content and remove selection for next time
1107 + $clickedCard.find('.mxchat-model-selector-title').html(originalContent);
1108 + $clickedCard.removeClass('mxchat-model-selected').find('.mxchat-model-selector-checkmark').remove();
1109 + $clickedCard.css('pointer-events', 'auto');
1110 + }, 600);
1111 + } else {
1112 + // Show error state
1113 + $clickedCard.find('.mxchat-model-selector-title').html(
1114 + '<span class="dashicons dashicons-no" style="color: #dc3232;"></span> Error!'
1115 + );
1116 +
1117 + setTimeout(function() {
1118 + $clickedCard.find('.mxchat-model-selector-title').html(originalContent);
1119 + $clickedCard.removeClass('mxchat-model-selected').find('.mxchat-model-selector-checkmark').remove();
1120 + $clickedCard.css('pointer-events', 'auto');
1121 + }, 1500);
1122 + }
1123 + },
1124 + error: function() {
1125 + // Show error state
1126 + $clickedCard.find('.mxchat-model-selector-title').html(
1127 + '<span class="dashicons dashicons-no" style="color: #dc3232;"></span> Error!'
1128 + );
1129 +
1130 + setTimeout(function() {
1131 + $clickedCard.find('.mxchat-model-selector-title').html(originalContent);
1132 + $clickedCard.removeClass('mxchat-model-selected').find('.mxchat-model-selector-checkmark').remove();
1133 + $clickedCard.css('pointer-events', 'auto');
1134 + }, 1500);
1135 + }
1136 + });
1137 + }, 300); // 300ms delay to show the selection before starting to save
1138 + }
1139 +});
1140 +
1141 + // Close modal when clicking outside
1142 + $(window).on('click', function(event) {
1143 + if ($(event.target).is('#mxchat_model_selector_modal')) {
1144 + $('#mxchat_model_selector_modal').hide();
1145 + }
1146 + });
1147 +}
1148 +
1149 +function loadOpenRouterModels() {
1150 + const apiKey = $('#openrouter_api_key').val(); // This line already re-checks the field
1151 + const $modal = $('#mxchat_model_selector_modal');
1152 + const $modalBody = $modal.find('.mxchat-model-selector-modal-body');
1153 +
1154 + if (!apiKey || apiKey.trim() === '') {
1155 + // Show error message in modal
1156 + $modalBody.html(`
1157 + <div style="text-align: center; padding: 40px;">
1158 + <span class="dashicons dashicons-warning" style="font-size: 48px; color: #d63638; margin-bottom: 20px;"></span>
1159 + <h3>OpenRouter API Key Required</h3>
1160 + <p>Please enter your OpenRouter API key in the settings before selecting a model. If you're seeing this message and recently entered API key, try refreshing.</p>
1161 + <button class="button button-primary" id="mxchat_back_to_models">Back to Models</button>
1162 + </div>
1163 + `);
1164 +
1165 + $('#mxchat_back_to_models').on('click', function(e) {
1166 + e.preventDefault();
1167 + // CHANGE THIS: Instead of reloading, restore the original modal content
1168 + $modalBody.html(`
1169 + <div class="mxchat-model-selector-search-container">
1170 + <input type="text" id="mxchat_model_search_input" class="mxchat-model-search-input" placeholder="Search models...">
1171 + </div>
1172 + <div class="mxchat-model-selector-categories">
1173 + <button class="mxchat-model-category-btn active" data-category="all">All</button>
1174 + <button class="mxchat-model-category-btn" data-category="openrouter">OpenRouter</button>
1175 + <button class="mxchat-model-category-btn" data-category="gemini">Google Gemini</button>
1176 + <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
1177 + <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
1178 + <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
1179 + <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
1180 + </div>
1181 + <div class="mxchat-model-selector-grid" id="mxchat_models_grid"></div>
1182 + `);
276 1183
277 - // Update tabs
278 - $('.mxchat-nav-tab').removeClass('mxchat-nav-tab-active');
279 - $this.addClass('mxchat-nav-tab-active');
1184 + // Re-populate the grid
1185 + populateModelsGrid('', 'all');
280 1186
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();
1187 + // Re-bind event handlers
1188 + rebindModalEventHandlers();
1189 + });
1190 + return;
1191 + }
1192 +
1193 + // Show loading state
1194 + $modalBody.html(`
1195 + <div style="text-align: center; padding: 60px 20px;">
1196 + <div class="spinner is-active" style="float: none; margin: 0 auto 20px;"></div>
1197 + <h3>Loading OpenRouter Models...</h3>
1198 + <p>Fetching available models from OpenRouter</p>
1199 + </div>
1200 + `);
1201 +
1202 + // Fetch models from OpenRouter
1203 + jQuery.ajax({
1204 + url: mxchatAdmin.ajax_url,
1205 + type: 'POST',
1206 + data: {
1207 + action: 'mxchat_fetch_openrouter_models',
1208 + api_key: apiKey,
1209 + nonce: mxchatAdmin.fetch_openrouter_models_nonce
1210 + },
1211 + success: function(response) {
1212 + if (response.success && response.data.models) {
1213 + displayOpenRouterModels(response.data.models);
1214 + } else {
1215 + $modalBody.html(`
1216 + <div style="text-align: center; padding: 40px;">
1217 + <span class="dashicons dashicons-warning" style="font-size: 48px; color: #d63638; margin-bottom: 20px;"></span>
1218 + <h3>Error Loading Models</h3>
1219 + <p>${response.data.message || 'Failed to load models from OpenRouter'}</p>
1220 + <button class="button button-primary" id="mxchat_back_to_models">Back to Models</button>
1221 + </div>
1222 + `);
286 1223
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');
1224 + $('#mxchat_back_to_models').on('click', function(e) {
1225 + e.preventDefault();
1226 + // CHANGE THIS: Restore original content instead of reloading
1227 + restoreOriginalModalContent();
1228 + });
295 1229 }
1230 + },
1231 + error: function() {
1232 + $modalBody.html(`
1233 + <div style="text-align: center; padding: 40px;">
1234 + <span class="dashicons dashicons-warning" style="font-size: 48px; color: #d63638; margin-bottom: 20px;"></span>
1235 + <h3>Connection Error</h3>
1236 + <p>Failed to connect to OpenRouter. Please check your API key and try again.</p>
1237 + <button class="button button-primary" id="mxchat_back_to_models">Back to Models</button>
1238 + </div>
1239 + `);
1240 +
1241 + $('#mxchat_back_to_models').on('click', function(e) {
1242 + e.preventDefault();
1243 + // CHANGE THIS: Restore original content instead of reloading
1244 + restoreOriginalModalContent();
1245 + });
1246 + }
1247 + });
1248 +}
1249 +function restoreOriginalModalContent() {
1250 + const $modalBody = $('#mxchat_model_selector_modal').find('.mxchat-model-selector-modal-body');
1251 +
1252 + $modalBody.html(`
1253 + <div class="mxchat-model-selector-search-container">
1254 + <input type="text" id="mxchat_model_search_input" class="mxchat-model-search-input" placeholder="Search models...">
1255 + </div>
1256 + <div class="mxchat-model-selector-categories">
1257 + <button class="mxchat-model-category-btn active" data-category="all">All</button>
1258 + <button class="mxchat-model-category-btn" data-category="openrouter">OpenRouter</button>
1259 + <button class="mxchat-model-category-btn" data-category="gemini">Google Gemini</button>
1260 + <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
1261 + <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
1262 + <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
1263 + <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
1264 + </div>
1265 + <div class="mxchat-model-selector-grid" id="mxchat_models_grid"></div>
1266 + `);
1267 +
1268 + // Re-populate the grid
1269 + window.populateModelsGrid('', 'all');
1270 +
1271 + // Re-bind event handlers
1272 + rebindModalEventHandlers();
1273 +}
1274 +function rebindModalEventHandlers() {
1275 + const $modal = $('#mxchat_model_selector_modal');
1276 +
1277 + // Re-bind category button clicks
1278 + $('.mxchat-model-category-btn').off('click').on('click', function() {
1279 + $('.mxchat-model-category-btn').removeClass('active');
1280 + $(this).addClass('active');
1281 + const category = $(this).data('category');
1282 + const searchTerm = $('#mxchat_model_search_input').val();
1283 + window.populateModelsGrid(searchTerm, category);
1284 + });
1285 +
1286 + // Re-bind search input
1287 + $('#mxchat_model_search_input').off('input').on('input', function() {
1288 + const searchTerm = $(this).val();
1289 + const activeCategory = $('.mxchat-model-category-btn.active').data('category');
1290 + populateModelsGrid(searchTerm, activeCategory);
1291 + });
1292 +}
1293 +
1294 +function restoreDefaultModalFooter() {
1295 + const $modalFooter = $('#mxchat_model_selector_modal').find('.mxchat-model-selector-modal-footer');
1296 +
1297 + // Restore default footer buttons
1298 + $modalFooter.html(`
1299 + <button id="mxchat_cancel_model_selection" class="button mxchat-model-cancel-btn">Cancel</button>
1300 + `);
1301 +
1302 + // Re-bind cancel button
1303 + $('#mxchat_cancel_model_selection').on('click', function() {
1304 + $('#mxchat_model_selector_modal').hide();
1305 + });
1306 +}
1307 +
1308 +function displayOpenRouterModels(models) {
1309 + const $modal = $('#mxchat_model_selector_modal');
1310 + const $modalBody = $modal.find('.mxchat-model-selector-modal-body');
1311 + const $modalFooter = $modal.find('.mxchat-model-selector-modal-footer');
1312 + const currentSelected = $('#openrouter_selected_model').val();
1313 +
1314 + // Variable to store the currently selected model (in the UI, not yet saved)
1315 + let pendingSelection = {
1316 + modelId: currentSelected || null,
1317 + modelName: $('#openrouter_selected_model_name').val() || null
1318 + };
1319 +
1320 + // Build new modal content with search and models
1321 + const newContent = `
1322 + <div class="mxchat-model-selector-search-container">
1323 + <input type="text" id="mxchat_openrouter_search" class="mxchat-model-search-input" placeholder="Search OpenRouter models...">
1324 + <p style="margin: 10px 0; color: #666; font-size: 13px;">
1325 + <strong>${models.length} models available</strong> ·
1326 + <a href="#" id="mxchat_back_to_provider_select" style="color: #2271b1;">← Back to providers</a>
1327 + </p>
1328 + </div>
1329 + <div class="mxchat-model-selector-grid" id="mxchat_openrouter_models_grid"></div>
1330 + `;
1331 +
1332 + // Update footer with Save button for OpenRouter
1333 + const footerContent = `
1334 + <button id="mxchat_back_to_models_footer" class="button mxchat-model-cancel-btn">Back to Providers</button>
1335 + <button id="mxchat_save_openrouter_model" class="button button-primary" disabled>
1336 + <span class="dashicons dashicons-saved" style="margin-top: 3px;"></span> Save Selected Model
1337 + </button>
1338 + `;
1339 +
1340 + $modalBody.html(newContent);
1341 + $modalFooter.html(footerContent);
1342 +
1343 + // Function to render models
1344 + function renderOpenRouterModels(filterText = '') {
1345 + const $grid = $('#mxchat_openrouter_models_grid');
1346 + $grid.empty();
1347 +
1348 + let filteredModels = models;
1349 + if (filterText) {
1350 + const lowerFilter = filterText.toLowerCase();
1351 + filteredModels = models.filter(m =>
1352 + m.id.toLowerCase().includes(lowerFilter) ||
1353 + m.name.toLowerCase().includes(lowerFilter) ||
1354 + (m.description && m.description.toLowerCase().includes(lowerFilter))
1355 + );
1356 + }
1357 +
1358 + filteredModels.forEach(model => {
1359 + const isSelected = pendingSelection.modelId === model.id;
1360 + const contextLength = model.context_length ? `${(model.context_length / 1000).toFixed(0)}K` : '';
1361 + const promptPrice = model.pricing.prompt ? `$${(model.pricing.prompt * 1000000).toFixed(2)}/1M` : '';
1362 +
1363 + const $card = jQuery(`
1364 + <div class="mxchat-openrouter-card ${isSelected ? 'mxchat-model-selected' : ''}" data-model-id="${model.id}" data-model-name="${model.name}">
1365 + <div class="mxchat-model-selector-icon">
1366 + ${getOpenRouterIcon(model.id)}
1367 + </div>
1368 + <div class="mxchat-model-selector-info">
1369 + <h4 class="mxchat-model-selector-title">${model.name}</h4>
1370 + <div style="font-size: 12px; color: #666; margin-top: 5px;">
1371 + ${contextLength ? '<span style="margin-right: 12px;">📄 ' + contextLength + '</span>' : ''}
1372 + ${promptPrice ? '<span>💰 ' + promptPrice + '</span>' : ''}
1373 + </div>
1374 + </div>
1375 + ${isSelected ? '<div class="mxchat-model-selector-checkmark">✓</div>' : ''}
1376 + </div>
1377 + `);
1378 +
1379 + $grid.append($card);
296 1380 });
297 1381 }
298 1382
299 - // Initialize tabs and handle events
300 - initTabs();
301 - $(document).on('widget-added widget-updated postbox-toggled', initTabs);
1383 + // Helper to get icon
1384 + function getOpenRouterIcon(modelId) {
1385 + if (modelId.includes('gpt') || modelId.includes('openai')) {
1386 + 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>';
1387 + } else if (modelId.includes('claude') || modelId.includes('anthropic')) {
1388 + 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>';
1389 + } else if (modelId.includes('gemini') || modelId.includes('google')) {
1390 + 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><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>';
1391 + }
1392 + return '<span class="dashicons dashicons-cloud" style="font-size: 24px; color: #6750A4;"></span>';
1393 + }
302 1394
303 - // Activate initial tab
304 - try {
305 - var savedTab = localStorage.getItem('mxchat_active_tab');
306 - if (savedTab && $('#' + savedTab).length > 0) {
307 - $('.mxchat-nav-tab[href="#' + savedTab + '"]').trigger('click.mxchat');
308 - } else {
309 - $('.mxchat-nav-tab').first().trigger('click.mxchat');
1395 + // Initial render
1396 + renderOpenRouterModels();
1397 +
1398 + // Search handler
1399 + $('#mxchat_openrouter_search').on('input', function() {
1400 + renderOpenRouterModels($(this).val());
1401 + });
1402 +
1403 + $('#mxchat_back_to_provider_select').on('click', function(e) {
1404 + e.preventDefault();
1405 + // Restore footer to default state
1406 + restoreDefaultModalFooter();
1407 + // Instead of location.reload(), restore original content
1408 + restoreOriginalModalContent();
1409 + });
1410 +
1411 + // Back to providers footer button
1412 + $('#mxchat_back_to_models_footer').on('click', function(e) {
1413 + e.preventDefault();
1414 + // Restore footer to default state
1415 + restoreDefaultModalFooter();
1416 + // Restore original content
1417 + restoreOriginalModalContent();
1418 + });
1419 +
1420 + // Model selection - just highlight, don't save yet
1421 + $(document).on('click', '.mxchat-openrouter-card', function(e) {
1422 + e.stopPropagation(); // Prevent triggering the regular model card handler
1423 +
1424 + const modelId = $(this).data('model-id');
1425 + const modelName = $(this).data('model-name');
1426 +
1427 + // Remove selection from all cards
1428 + $('.mxchat-openrouter-card').removeClass('mxchat-model-selected').find('.mxchat-model-selector-checkmark').remove();
1429 +
1430 + // Add selection to clicked card
1431 + $(this).addClass('mxchat-model-selected');
1432 + if ($(this).find('.mxchat-model-selector-checkmark').length === 0) {
1433 + $(this).append('<div class="mxchat-model-selector-checkmark">✓</div>');
310 1434 }
311 - } catch (e) {
312 - $('.mxchat-nav-tab').first().trigger('click.mxchat');
1435 +
1436 + // Update pending selection
1437 + pendingSelection.modelId = modelId;
1438 + pendingSelection.modelName = modelName;
1439 +
1440 + // Enable the save button
1441 + $('#mxchat_save_openrouter_model').prop('disabled', false);
1442 + });
1443 +
1444 + // Save button handler
1445 + $('#mxchat_save_openrouter_model').on('click', function() {
1446 + const $saveButton = $(this);
1447 +
1448 + if (!pendingSelection.modelId) {
1449 + return;
1450 + }
1451 +
1452 + // Disable button and show loading state
1453 + $saveButton.prop('disabled', true).html('<span class="spinner is-active" style="float: none; margin: 0 5px 0 0;"></span> Saving...');
1454 +
1455 + // First, save that we're using OpenRouter
1456 + jQuery.ajax({
1457 + url: mxchatAdmin.ajax_url,
1458 + type: 'POST',
1459 + data: {
1460 + action: 'mxchat_save_setting',
1461 + name: 'model',
1462 + value: 'openrouter',
1463 + _ajax_nonce: mxchatAdmin.setting_nonce
1464 + },
1465 + success: function() {
1466 + // After model is set, save the model ID
1467 + jQuery.ajax({
1468 + url: mxchatAdmin.ajax_url,
1469 + type: 'POST',
1470 + data: {
1471 + action: 'mxchat_save_setting',
1472 + name: 'openrouter_selected_model',
1473 + value: pendingSelection.modelId,
1474 + _ajax_nonce: mxchatAdmin.setting_nonce
1475 + },
1476 + success: function() {
1477 + // Update DOM immediately
1478 + $('#openrouter_selected_model').val(pendingSelection.modelId);
1479 +
1480 + // After model ID is saved, save the display name
1481 + jQuery.ajax({
1482 + url: mxchatAdmin.ajax_url,
1483 + type: 'POST',
1484 + data: {
1485 + action: 'mxchat_save_setting',
1486 + name: 'openrouter_selected_model_name',
1487 + value: pendingSelection.modelName,
1488 + _ajax_nonce: mxchatAdmin.setting_nonce
1489 + },
1490 + success: function() {
1491 + // Update DOM immediately
1492 + $('#openrouter_selected_model_name').val(pendingSelection.modelName);
1493 +
1494 + // Update button text
1495 + $('#mxchat_model_selector_btn').text('OpenRouter: ' + pendingSelection.modelName);
1496 +
1497 + // Update the "Currently using" message
1498 + const $currentSelection = $('#openrouter-current-selection');
1499 + if ($currentSelection.length) {
1500 + $currentSelection.html(
1501 + '<span class="dashicons dashicons-yes" style="font-size: 16px; vertical-align: middle;"></span> ' +
1502 + 'Currently using: <strong>' + pendingSelection.modelName + '</strong>'
1503 + ).show();
1504 + }
1505 +
1506 + // Show success state briefly
1507 + $saveButton.html('<span class="dashicons dashicons-yes" style="color: #46b450; margin-top: 3px;"></span> Saved!');
1508 +
1509 + // Close modal after short delay
1510 + setTimeout(function() {
1511 + // Restore footer to default state
1512 + restoreDefaultModalFooter();
1513 + $modal.hide();
1514 + }, 800);
1515 + },
1516 + error: function() {
1517 + $saveButton.prop('disabled', false).html('<span class="dashicons dashicons-saved" style="margin-top: 3px;"></span> Save Selected Model');
1518 + alert('Failed to save model name. Please try again.');
1519 + }
1520 + });
1521 + },
1522 + error: function() {
1523 + $saveButton.prop('disabled', false).html('<span class="dashicons dashicons-saved" style="margin-top: 3px;"></span> Save Selected Model');
1524 + alert('Failed to save model ID. Please try again.');
1525 + }
1526 + });
1527 + },
1528 + error: function() {
1529 + $saveButton.prop('disabled', false).html('<span class="dashicons dashicons-saved" style="margin-top: 3px;"></span> Save Selected Model');
1530 + alert('Failed to save OpenRouter selection. Please try again.');
1531 + }
1532 + });
1533 + });
1534 +
1535 +}
1536 +
1537 +// Embedding model selector - completely separate from chat model selector
1538 +function setupMxChatEmbeddingModelSelector() {
1539 + const $embeddingModelSelect = $('#embedding_model');
1540 +
1541 + // Skip if the element doesn't exist on the page
1542 + if ($embeddingModelSelect.length === 0) {
1543 + return;
313 1544 }
314 1545
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);
1546 + const $embeddingModelSelectorButton = $('<button>', {
1547 + type: 'button',
1548 + id: 'mxchat_embedding_model_selector_btn',
1549 + class: 'button-primary mxchat-embedding-model-selector-btn', // Changed class name to be more specific
1550 + text: 'Select Embedding Model'
320 1551 });
321 1552
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');
1553 + // Replace the select dropdown with a button
1554 + $embeddingModelSelect.hide().after($embeddingModelSelectorButton);
1555 +
1556 + // Update button text to show currently selected model
1557 + function updateButtonText() {
1558 + const selectedModel = $embeddingModelSelect.val();
1559 + const selectedModelText = $embeddingModelSelect.find('option:selected').text();
1560 + $embeddingModelSelectorButton.text(selectedModelText);
1561 + }
1562 +
1563 + // Initialize button text
1564 + updateButtonText();
1565 +
1566 + // Create a unique ID for the modal to avoid conflicts
1567 + const embeddingModalId = 'mxchat_embedding_model_selector_modal';
1568 +
1569 + // Create and append modal HTML with unique IDs
1570 + const embeddingModelSelectorModal = `
1571 + <div id="${embeddingModalId}" class="mxchat-embedding-model-selector-modal">
1572 + <div class="mxchat-embedding-model-selector-modal-content">
1573 + <div class="mxchat-embedding-model-selector-modal-header">
1574 + <h3>Select Embedding Model</h3>
1575 + <span class="mxchat-embedding-model-selector-modal-close">&times;</span>
1576 + </div>
1577 + <div class="mxchat-embedding-model-selector-modal-body">
1578 + <div class="mxchat-embedding-model-selector-search-container">
1579 + <input type="text" id="mxchat_embedding_model_search_input" class="mxchat-embedding-model-search-input" placeholder="Search models...">
1580 + </div>
1581 + <div class="mxchat-embedding-model-selector-categories">
1582 + <button class="mxchat-embedding-model-category-btn active" data-category="all">All</button>
1583 + <button class="mxchat-embedding-model-category-btn" data-category="openai">OpenAI</button>
1584 + <button class="mxchat-embedding-model-category-btn" data-category="voyage">Voyage AI</button>
1585 + <button class="mxchat-embedding-model-category-btn" data-category="gemini">Google Gemini</button>
1586 + </div>
1587 + <div class="mxchat-embedding-model-selector-grid" id="mxchat_embedding_models_grid"></div>
1588 + </div>
1589 + <div class="mxchat-embedding-model-selector-modal-footer">
1590 + <button id="mxchat_cancel_embedding_model_selection" class="button mxchat-embedding-model-cancel-btn">Cancel</button>
1591 + </div>
1592 + </div>
1593 + </div>
1594 + `;
1595 +
1596 + // Use jQuery's append to ensure it doesn't clash with existing modals
1597 + $('body').append(embeddingModelSelectorModal);
1598 +
1599 + // Populate models grid
1600 + function populateEmbeddingModelsGrid(filter = '', category = 'all') {
1601 + const $grid = $('#mxchat_embedding_models_grid');
1602 + $grid.empty();
1603 +
1604 + // Define embedding models with descriptions and context lengths
1605 + const models = {
1606 + openai: [
1607 + {
1608 + value: 'text-embedding-3-small',
1609 + label: 'TE3 Small',
1610 + description: 'Fast and cost-effective embeddings (1536 dimensions, 8K context)'
1611 + },
1612 + {
1613 + value: 'text-embedding-ada-002',
1614 + label: 'Ada 2',
1615 + description: 'Balanced performance embeddings (1536 dimensions, 8K context)'
1616 + },
1617 + {
1618 + value: 'text-embedding-3-large',
1619 + label: 'TE3 Large',
1620 + description: 'High-performance embeddings (3072 dimensions, 8K context)'
1621 + }
1622 + ],
1623 + voyage: [
1624 + {
1625 + value: 'voyage-3-large',
1626 + label: 'Voyage-3 Large',
1627 + description: 'Advanced semantic search embeddings (2048 dimensions, 32K context)'
1628 + }
1629 + ],
1630 + gemini: [
1631 + {
1632 + value: 'gemini-embedding-exp-03-07',
1633 + label: 'Gemini Embedding',
1634 + description: 'Experimental SOTA embeddings (1536 dimensions, 8K context)'
1635 + }
1636 + ]
1637 + };
1638 +
1639 + let allModels = [];
1640 + Object.keys(models).forEach(key => {
1641 + if (category === 'all' || category === key) {
1642 + allModels = allModels.concat(models[key]);
1643 + }
1644 + });
1645 +
1646 + // Filter by search term if present
1647 + if (filter) {
1648 + const lowerFilter = filter.toLowerCase();
1649 + allModels = allModels.filter(model =>
1650 + model.label.toLowerCase().includes(lowerFilter) ||
1651 + model.description.toLowerCase().includes(lowerFilter)
1652 + );
1653 + }
1654 +
1655 + // Create model cards
1656 + allModels.forEach(model => {
1657 + const isSelected = $embeddingModelSelect.val() === model.value;
1658 + let providerClass = 'mxchat-embedding-model-provider-openai';
1659 +
1660 + if (model.value.startsWith('voyage-')) {
1661 + providerClass = 'mxchat-embedding-model-provider-voyage';
1662 + } else if (model.value.startsWith('gemini-embedding-')) {
1663 + providerClass = 'mxchat-embedding-model-provider-gemini';
1664 + }
1665 +
1666 + let iconHTML = '';
1667 + if (model.value.startsWith('voyage-')) {
1668 + iconHTML = '<span class="dashicons dashicons-chart-line mxchat-embedding-model-icon-voyage"></span>';
1669 + } else if (model.value.startsWith('gemini-embedding-')) {
1670 + iconHTML = '<span class="dashicons dashicons-google mxchat-embedding-model-icon-gemini"></span>';
329 1671 } else {
330 - inputField.attr('type', 'password');
331 - $(this).text('Show');
1672 + 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 1673 }
1674 +
1675 + const $modelCard = $(`
1676 + <div class="mxchat-embedding-model-selector-card ${isSelected ? 'mxchat-embedding-model-selected' : ''} ${providerClass}" data-value="${model.value}">
1677 + <div class="mxchat-embedding-model-selector-icon">
1678 + ${iconHTML}
1679 + </div>
1680 + <div class="mxchat-embedding-model-selector-info">
1681 + <h4 class="mxchat-embedding-model-selector-title">${model.label}</h4>
1682 + <p class="mxchat-embedding-model-selector-description">${model.description}</p>
1683 + </div>
1684 + ${isSelected ? '<div class="mxchat-embedding-model-selector-checkmark">✓</div>' : ''}
1685 + </div>
1686 + `);
1687 +
1688 + $grid.append($modelCard);
333 1689 });
334 1690 }
335 1691
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);
1692 + // Event handlers - use namespaced events to avoid conflicts
1693 + $embeddingModelSelectorButton.on('click.embeddingModelSelector', function(e) {
1694 + e.stopPropagation(); // Prevent event bubbling
1695 + $('#' + embeddingModalId).show();
1696 + populateEmbeddingModelsGrid('', 'all');
1697 + });
349 1698
1699 + $('.mxchat-embedding-model-selector-modal-close, #mxchat_cancel_embedding_model_selection').on('click.embeddingModelSelector', function(e) {
1700 + e.stopPropagation(); // Prevent event bubbling
1701 + $('#' + embeddingModalId).hide();
1702 + });
1703 +
1704 + $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').on('click.embeddingModelSelector', function(e) {
1705 + e.stopPropagation(); // Prevent event bubbling
1706 + $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').removeClass('active');
1707 + $(this).addClass('active');
1708 + const category = $(this).data('category');
1709 + const searchTerm = $('#mxchat_embedding_model_search_input').val();
1710 + populateEmbeddingModelsGrid(searchTerm, category);
1711 + });
1712 +
1713 + $('#mxchat_embedding_model_search_input').on('input.embeddingModelSelector', function() {
1714 + const searchTerm = $(this).val();
1715 + const activeCategory = $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn.active').data('category');
1716 + populateEmbeddingModelsGrid(searchTerm, activeCategory);
1717 + });
1718 +
1719 + // Use a direct selector to avoid conflicts with other card elements
1720 + $(document).on('click.embeddingModelSelector', '.mxchat-embedding-model-selector-grid .mxchat-embedding-model-selector-card', function(e) {
1721 + e.stopPropagation(); // Prevent event bubbling
1722 + const modelValue = $(this).data('value');
1723 +
1724 + // Important: Only update this specific select element
1725 + $embeddingModelSelect.val(modelValue);
1726 +
1727 + // Manually trigger change only on this element
1728 + const changeEvent = new Event('change', { bubbles: true });
1729 + $embeddingModelSelect[0].dispatchEvent(changeEvent);
1730 +
1731 + // Update button text
1732 + updateButtonText();
1733 +
1734 + // Hide modal
1735 + $('#' + embeddingModalId).hide();
1736 + });
1737 +
1738 + // Close modal when clicking outside - use namespaced events
1739 + $(window).on('click.embeddingModelSelector', function(event) {
1740 + if ($(event.target).is('#' + embeddingModalId)) {
1741 + $('#' + embeddingModalId).hide();
1742 + }
1743 + });
1744 +}
1745 +
1746 +// Call this function after the DOM is fully loaded
1747 +$(document).ready(function() {
1748 + setupMxChatModelSelector();
1749 + setupMxChatEmbeddingModelSelector();
1750 +});
1751 +
1752 + // Initialize API key visibility
1753 + setupAPIKeyVisibility();
1754 +
350 1755 // Add Intent Form Submission
351 1756 $('#mxchat-add-intent-form').on('submit', function(event) {
352 1757 $('#mxchat-intent-loading').show();
353 1758 $('#mxchat-intent-loading-text').show();
@@ -363,97 +1768,60 @@
363 1768 row.find('.save-button').show();
364 1769 });
365 1770
366 1771 // 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();
1772 +// Save button handler
1773 +$('.save-button').on('click', function() {
1774 + var button = $(this);
1775 + var row = button.closest('tr');
1776 + var id = button.data('id');
1777 + var nonce = button.data('nonce'); // Get nonce from button data attribute
1778 + var newContent = row.find('.content-edit').val();
1779 + var newUrl = row.find('.url-edit').val();
1780 +
1781 + //console.log('Nonce from button:', nonce); // Debug
1782 +
1783 + button.prop('disabled', true);
1784 + button.text('Saving...');
1785 +
1786 + $.ajax({
1787 + url: mxchatAdmin.ajax_url,
1788 + type: 'POST',
1789 + data: {
1790 + action: 'mxchat_save_inline_prompt',
1791 + id: id,
1792 + article_content: newContent,
1793 + article_url: newUrl,
1794 + _ajax_nonce: nonce // Use nonce from button
1795 + },
1796 + success: function(response) {
1797 + button.prop('disabled', false);
1798 + button.text('Save');
1799 +
1800 + if (response.success) {
1801 + row.find('.content-view').html(newContent.replace(/\n/g, "<br>"));
1802 + if (newUrl) {
1803 + row.find('.url-view').html('<a href="' + newUrl + '" target="_blank">' + newUrl + '</a>');
403 1804 } else {
404 - alert('Error saving content: ' + (response.data?.message || 'Unknown error'));
1805 + row.find('.url-view').html('N/A');
405 1806 }
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();
1807 +
1808 + row.find('.content-edit, .url-edit').hide();
1809 + row.find('.content-view, .url-view').show();
1810 + row.find('.save-button').hide();
1811 + row.find('.edit-button').show();
428 1812 } else {
429 - licenseStatus.text('Inactive');
430 - alert(response.data || 'Activation failed. Please check your input.');
431 - submitButton.prop('disabled', false);
1813 + alert('Error saving content: ' + (response.data?.message || 'Unknown error'));
432 1814 }
1815 + },
1816 + error: function() {
1817 + button.prop('disabled', false);
1818 + button.text('Save');
1819 + alert('An error occurred while saving.');
433 1820 }
1821 + });
1822 +});
434 1823
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 1824
457 1825 // Questions handling
458 1826 $('.mxchat-add-question').on('click', function () {
459 1827 const container = $('#mxchat-additional-questions-container');
@@ -544,9 +1912,778 @@
544 1912 }
545 1913 });
546 1914 }
547 1915
1916 + // Function to adjust the textarea height to content
1917 + function adjustTextareaHeight() {
1918 + this.style.height = 'auto'; // Reset to auto to calculate scrollHeight
1919 + this.style.height = this.scrollHeight + 'px'; // Expand to content height
1920 + }
1921 +
1922 + // Function to reset the textarea height to initial
1923 + function resetTextareaHeight() {
1924 + this.style.height = ''; // Remove inline height, reverting to CSS default
1925 + }
1926 +
1927 + // Target the specific textarea by ID
1928 + var $textarea = $('#system_prompt_instructions');
1929 +
1930 + // Bind events
1931 + $textarea.on('focus input', adjustTextareaHeight) // Expand on focus and input
1932 + .on('blur', resetTextareaHeight); // Reset on blur
1933 +});
1934 +
1935 +
1936 +
1937 +document.addEventListener('DOMContentLoaded', function() {
1938 + // Check if we're on the correct page before initializing
1939 + const modal = document.getElementById('mxchat-action-modal');
548 1940
1941 + // Only initialize if the modal exists on this page
1942 + if (modal) {
1943 + //console.log('MXChat Action Modal JS Loaded');
1944 +
1945 + // Initialize the action modal functionality
1946 + initStepBasedActionModal();
1947 + }
1948 +
1949 + // Function to initialize the step-based action modal
1950 + function initStepBasedActionModal() {
1951 + // We already checked for modal existence above, so no need to check again
1952 +
1953 + const actionStep1 = document.getElementById('mxchat-action-step-1');
1954 + const actionStep2 = document.getElementById('mxchat-action-step-2');
1955 + const backToStep1Btn = document.getElementById('mxchat-back-to-step-1');
1956 + const searchInput = document.getElementById('action-type-search');
1957 + const categoryButtons = modal.querySelectorAll('.mxchat-category-button');
1958 + const actionCards = modal.querySelectorAll('.mxchat-action-type-card');
1959 + const actionForm = document.getElementById('mxchat-action-form');
1960 + const callbackInput = document.getElementById('callback_function');
1961 + const actionIdField = document.getElementById('edit_action_id');
1962 + const labelField = document.getElementById('intent_label');
1963 + const phrasesField = document.getElementById('action_phrases');
1964 + const formActionType = document.getElementById('form_action_type');
1965 + const nonceContainer = document.getElementById('action-nonce-container');
1966 + const thresholdSlider = document.getElementById('similarity_threshold');
1967 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
1968 +
1969 + // Rest of your initialization code remains the same...
1970 +
1971 + // Log the structure of one action card for debugging
1972 + if (actionCards.length > 0) {
1973 + //console.log('First action card data attributes:', actionCards[0].dataset);
1974 + //console.log('First action card HTML:', actionCards[0].outerHTML);
1975 + }
1976 +
1977 + // Add click event listeners to category buttons
1978 + categoryButtons.forEach(button => {
1979 + button.addEventListener('click', function() {
1980 + //console.log('Category button clicked:', this.dataset.category);
1981 +
1982 + // Remove active class from all buttons
1983 + categoryButtons.forEach(btn => btn.classList.remove('active'));
1984 +
1985 + // Add active class to clicked button
1986 + this.classList.add('active');
1987 +
1988 + // Get selected category
1989 + const category = this.dataset.category;
1990 +
1991 + // Filter action cards
1992 + filterActionCards(category, searchInput.value);
1993 + });
1994 + });
1995 +
1996 + // Add search functionality
1997 + if (searchInput) {
1998 + searchInput.addEventListener('input', function() {
1999 + // Get active category
2000 + const activeCategory = modal.querySelector('.mxchat-category-button.active')?.dataset.category || 'all';
2001 + //console.log('Search input changed, active category:', activeCategory);
2002 +
2003 + // Filter action cards
2004 + filterActionCards(activeCategory, this.value);
2005 + });
2006 + }
2007 +
2008 + // Add click event listeners to action cards
2009 + actionCards.forEach(card => {
2010 + card.addEventListener('click', function() {
2011 + // Get the action data
2012 + const isPro = this.dataset.pro === 'true';
2013 + const isInstalled = this.dataset.installed === 'true';
2014 + const addonName = this.dataset.addon || '';
2015 + const actionValue = this.dataset.value;
2016 + const actionLabel = this.dataset.label;
2017 + const actionIcon = this.querySelector('.dashicons').getAttribute('class').replace('dashicons dashicons-', '');
2018 + const actionDescription = this.querySelector('p').textContent;
2019 +
2020 + // Pro check using the proper detection method
2021 + const proIsActivated = typeof mxchatAdmin !== 'undefined' &&
2022 + (mxchatAdmin.is_activated === '1' ||
2023 + mxchatAdmin.is_activated === 'true' ||
2024 + mxchatAdmin.is_activated === true);
2025 +
2026 + // Handle different states
2027 + if (isPro && !proIsActivated) {
2028 + // Pro feature but no Pro license
2029 + showProFeatureNotice();
2030 + return;
2031 + }
2032 +
2033 + if (addonName && !isInstalled) {
2034 + // Add-on required but not installed
2035 + const addonDisplayName = this.querySelector('.mxchat-addon-info')?.textContent?.replace('Requires ', '') || addonName + ' Add-on';
2036 + showAddonRequiredNotice(addonDisplayName);
2037 + return;
2038 + }
2039 +
2040 + // If we get here, the action is available - proceed as normal
2041 + callbackInput.value = actionValue;
2042 +
2043 + // Update the selected action display in step 2
2044 + document.getElementById('selected-action-title').textContent = actionLabel;
2045 + document.getElementById('selected-action-description').textContent = actionDescription;
2046 + document.getElementById('selected-action-icon').innerHTML =
2047 + `<span class="dashicons dashicons-${actionIcon}"></span>`;
2048 +
2049 + // Set a default label based on the action type (user can change it)
2050 + if (!labelField.value) {
2051 + labelField.value = actionLabel;
2052 + }
2053 +
2054 + // Move to step 2
2055 + actionStep1.classList.remove('active');
2056 + actionStep2.classList.add('active');
2057 +
2058 + // Update modal title
2059 + });
2060 + });
2061 +
2062 + // Back button functionality
2063 + if (backToStep1Btn) {
2064 + backToStep1Btn.addEventListener('click', function() {
2065 + //console.log('Back button clicked');
2066 + actionStep2.classList.remove('active');
2067 + actionStep1.classList.add('active');
2068 + });
2069 + }
2070 +
2071 + // Function to filter action cards by category and search term
2072 + function filterActionCards(category, searchTerm) {
2073 + searchTerm = searchTerm.toLowerCase().trim();
2074 + //console.log(`Filtering cards by category: "${category}", search: "${searchTerm}"`);
2075 +
2076 + let visibleCount = 0;
2077 +
2078 + // Show all cards initially with animation
2079 + actionCards.forEach((card, index) => {
2080 + // Reset animation
2081 + card.style.animation = 'none';
2082 + // Trigger reflow
2083 + void card.offsetWidth;
2084 +
2085 + // Determine if card should be visible based on category and search term
2086 + const cardCategory = card.dataset.category || '';
2087 + const matchesCategory = category === 'all' || cardCategory === category;
2088 +
2089 + const cardTitle = card.querySelector('h4')?.textContent?.toLowerCase() || '';
2090 + const cardDesc = card.querySelector('p')?.textContent?.toLowerCase() || '';
2091 + const matchesSearch = searchTerm === '' ||
2092 + cardTitle.includes(searchTerm) ||
2093 + cardDesc.includes(searchTerm);
2094 +
2095 + // Show/hide card with animation
2096 + if (matchesCategory && matchesSearch) {
2097 + card.style.display = 'flex';
2098 + // Staggered animation for cards
2099 + card.style.animation = `fadeIn 0.2s ease forwards ${index * 0.03}s`;
2100 + visibleCount++;
2101 + } else {
2102 + card.style.display = 'none';
2103 + }
2104 + });
2105 +
2106 + //console.log(`Filter results: ${visibleCount} cards visible out of ${actionCards.length}`);
2107 + }
2108 +
2109 + // Function to show notice for Pro features
2110 + function showProFeatureNotice() {
2111 + //console.log('Showing Pro feature notice');
2112 + // Check if we already have a notification container
2113 + let noticeContainer = document.querySelector('.mxchat-pro-notice');
2114 +
2115 + if (!noticeContainer) {
2116 + // Create the notice container
2117 + noticeContainer = document.createElement('div');
2118 + noticeContainer.className = 'mxchat-pro-notice';
2119 +
2120 + // Create content
2121 + noticeContainer.innerHTML = `
2122 + <div class="mxchat-pro-notice-content">
2123 + <h3>MxChat Pro Feature</h3>
2124 + <p>This action is available in the Pro version only.</p>
2125 + <div class="mxchat-pro-notice-buttons">
2126 + <button class="mxchat-button-secondary mxchat-pro-notice-close">Close</button>
2127 + <a href="https://mxchat.ai/" class="mxchat-button-primary">Upgrade to Pro</a>
2128 + </div>
2129 + </div>
2130 + `;
2131 +
2132 + // Append to body
2133 + document.body.appendChild(noticeContainer);
2134 +
2135 + // Add close functionality
2136 + const closeButton = noticeContainer.querySelector('.mxchat-pro-notice-close');
2137 + closeButton.addEventListener('click', function() {
2138 + noticeContainer.classList.remove('active');
2139 + setTimeout(() => {
2140 + noticeContainer.remove();
2141 + }, 300);
2142 + });
2143 +
2144 + // Click outside to close
2145 + noticeContainer.addEventListener('click', function(e) {
2146 + if (e.target === noticeContainer) {
2147 + closeButton.click();
2148 + }
2149 + });
2150 +
2151 + // Show with animation
2152 + setTimeout(() => {
2153 + noticeContainer.classList.add('active');
2154 + }, 10);
2155 + } else {
2156 + // If it already exists, just make it visible again
2157 + noticeContainer.classList.add('active');
2158 + }
2159 + }
2160 +
2161 + // Function to show notice for add-on requirements
2162 + function showAddonRequiredNotice(addonName) {
2163 + //console.log(`Showing add-on notice for: ${addonName}`);
2164 + // Check if we already have a notification container
2165 + let noticeContainer = document.querySelector('.mxchat-addon-notice');
2166 +
2167 + if (!noticeContainer) {
2168 + // Create the notice container
2169 + noticeContainer = document.createElement('div');
2170 + noticeContainer.className = 'mxchat-addon-notice';
2171 +
2172 + // Create content
2173 + noticeContainer.innerHTML = `
2174 + <div class="mxchat-addon-notice-content">
2175 + <span class="mxchat-addon-notice-icon">🧩</span>
2176 + <h3>Add-on Required</h3>
2177 + <p>This action requires the <strong>${addonName}</strong> add-on to be installed.</p>
2178 + <div class="mxchat-addon-notice-buttons">
2179 + <button class="mxchat-button-secondary mxchat-addon-notice-close">Close</button>
2180 + <a href="admin.php?page=mxchat-addons" class="mxchat-button-primary">Get Add-ons</a>
2181 + </div>
2182 + </div>
2183 + `;
2184 +
2185 + // Append to body
2186 + document.body.appendChild(noticeContainer);
2187 +
2188 + // Add close functionality
2189 + const closeButton = noticeContainer.querySelector('.mxchat-addon-notice-close');
2190 + closeButton.addEventListener('click', function() {
2191 + noticeContainer.classList.remove('active');
2192 + setTimeout(() => {
2193 + noticeContainer.remove();
2194 + }, 300);
2195 + });
2196 +
2197 + // Click outside to close
2198 + noticeContainer.addEventListener('click', function(e) {
2199 + if (e.target === noticeContainer) {
2200 + closeButton.click();
2201 + }
2202 + });
2203 +
2204 + // Show with animation
2205 + setTimeout(() => {
2206 + noticeContainer.classList.add('active');
2207 + }, 10);
2208 + } else {
2209 + // If it already exists, update the content
2210 + const addonNameElement = noticeContainer.querySelector('p strong');
2211 + if (addonNameElement) {
2212 + addonNameElement.textContent = addonName;
2213 + }
2214 +
2215 + // Make it visible again
2216 + noticeContainer.classList.add('active');
2217 + }
2218 + }
2219 +
2220 + // Form submission handling
2221 + if (actionForm) {
2222 + actionForm.addEventListener('submit', function() {
2223 + //console.log('Form submitted');
2224 + document.getElementById('mxchat-action-loading').style.display = 'flex';
2225 + this.querySelector('button[type="submit"]').disabled = true;
2226 + });
2227 + }
2228 + }
2229 +
2230 + // Setup add action buttons (only if we're on the correct page)
2231 + if (modal) {
2232 + // Update the modal open function to support the step-based flow
2233 + window.mxchatOpenActionModal = function(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '', enabledBots = null) {
2234 + //console.log('Modal opening, edit mode:', isEdit);
2235 +
2236 + // Get form fields
2237 + const actionIdField = document.getElementById('edit_action_id');
2238 + const labelField = document.getElementById('intent_label');
2239 + const phrasesField = document.getElementById('action_phrases');
2240 + const formActionType = document.getElementById('form_action_type');
2241 + const callbackInput = document.getElementById('callback_function');
2242 + const saveButton = document.getElementById('mxchat-save-action-btn');
2243 + const nonceContainer = document.getElementById('action-nonce-container');
2244 + const thresholdSlider = document.getElementById('similarity_threshold');
2245 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
2246 + const actionStep1 = document.getElementById('mxchat-action-step-1');
2247 + const actionStep2 = document.getElementById('mxchat-action-step-2');
2248 + const searchInput = document.getElementById('action-type-search');
2249 +
2250 + // Set up modal for edit or create
2251 + if (isEdit) {
2252 + saveButton.textContent = 'Update Action';
2253 + formActionType.value = 'mxchat_edit_intent';
2254 + actionIdField.value = actionId;
2255 + labelField.value = label;
2256 + phrasesField.value = phrases;
2257 + callbackInput.value = callbackFunction;
2258 + thresholdSlider.value = threshold; // Set the current threshold value
2259 + thresholdDisplay.textContent = threshold + '%'; // Update display
2260 +
2261 + // NEW: Handle bot selection checkboxes for editing
2262 + // First uncheck all bot checkboxes
2263 + document.querySelectorAll('input[name="enabled_bots[]"]').forEach(checkbox => {
2264 + checkbox.checked = false;
2265 + });
2266 +
2267 + // Then check the ones that should be enabled
2268 + if (enabledBots) {
2269 + let botsArray;
2270 + if (typeof enabledBots === 'string') {
2271 + try {
2272 + botsArray = JSON.parse(enabledBots);
2273 + } catch (e) {
2274 + console.warn('Failed to parse enabledBots:', enabledBots);
2275 + botsArray = ['default']; // fallback
2276 + }
2277 + } else if (Array.isArray(enabledBots)) {
2278 + botsArray = enabledBots;
2279 + } else {
2280 + botsArray = ['default']; // fallback
2281 + }
2282 +
2283 + botsArray.forEach(botId => {
2284 + const checkbox = document.querySelector(`input[name="enabled_bots[]"][value="${botId}"]`);
2285 + if (checkbox) {
2286 + checkbox.checked = true;
2287 + }
2288 + });
2289 + } else {
2290 + // Fallback to default if no bot data
2291 + const defaultCheckbox = document.querySelector('input[name="enabled_bots[]"][value="default"]');
2292 + if (defaultCheckbox) {
2293 + defaultCheckbox.checked = true;
2294 + }
2295 + }
2296 +
2297 + // Update the nonce field for editing
2298 + nonceContainer.innerHTML = ''; // Clear existing nonce
2299 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) {
2300 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.edit_intent_nonce}">`;
2301 + }
2302 +
2303 + // For editing, go directly to step 2 and update the selected action display
2304 + actionStep1.classList.remove('active');
2305 + actionStep2.classList.add('active');
2306 +
2307 + // Find the matching action card to get its details
2308 + const actionCards = document.querySelectorAll('.mxchat-action-type-card');
2309 + let foundCard = null;
2310 +
2311 + actionCards.forEach(card => {
2312 + if (card.dataset.value === callbackFunction) {
2313 + foundCard = card;
2314 + }
2315 + });
2316 +
2317 + if (foundCard) {
2318 + //console.log('Found matching action card for:', callbackFunction);
2319 + const actionLabel = foundCard.dataset.label || foundCard.querySelector('h4')?.textContent || '';
2320 + const actionIconElement = foundCard.querySelector('.dashicons');
2321 + const actionIcon = actionIconElement
2322 + ? actionIconElement.getAttribute('class').replace('dashicons dashicons-', '')
2323 + : 'admin-generic';
2324 + const actionDescription = foundCard.querySelector('p')?.textContent || '';
2325 +
2326 + document.getElementById('selected-action-title').textContent = actionLabel;
2327 + document.getElementById('selected-action-description').textContent = actionDescription;
2328 + document.getElementById('selected-action-icon').innerHTML =
2329 + `<span class="dashicons dashicons-${actionIcon}"></span>`;
2330 + } else {
2331 + //console.log('No matching action card found for:', callbackFunction);
2332 + // Fallback if we can't find the card
2333 + document.getElementById('selected-action-title').textContent = label;
2334 + document.getElementById('selected-action-description').textContent = 'Configure this action for your chatbot';
2335 + document.getElementById('selected-action-icon').innerHTML =
2336 + `<span class="dashicons dashicons-admin-generic"></span>`;
2337 + }
2338 + } else {
2339 + //console.log('Setting up create mode');
2340 + saveButton.textContent = 'Save Action';
2341 + formActionType.value = 'mxchat_add_intent';
2342 + actionIdField.value = '';
2343 + labelField.value = '';
2344 + phrasesField.value = '';
2345 + callbackInput.value = '';
2346 + thresholdSlider.value = 85; // Default value for new actions
2347 + thresholdDisplay.textContent = '85%'; // Default display
2348 +
2349 + // For new actions, ensure default is checked and others are unchecked
2350 + document.querySelectorAll('input[name="enabled_bots[]"]').forEach(checkbox => {
2351 + checkbox.checked = (checkbox.value === 'default');
2352 + });
2353 +
2354 + // Update the nonce field for adding
2355 + nonceContainer.innerHTML = ''; // Clear existing nonce
2356 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) {
2357 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.add_intent_nonce}">`;
2358 + }
2359 +
2360 + // For creating new, start at step 1
2361 + actionStep1.classList.add('active');
2362 + actionStep2.classList.remove('active');
2363 + }
2364 +
2365 + // Show modal with animation
2366 + modal.style.display = 'flex';
2367 + requestAnimationFrame(() => {
2368 + modal.classList.add('active');
2369 + });
2370 +
2371 + // Set up close handlers
2372 + const closeModal = () => {
2373 + //console.log('Closing modal');
2374 + modal.classList.remove('active');
2375 + setTimeout(() => {
2376 + modal.style.display = 'none';
2377 + }, 300); // Match the CSS transition time
2378 + };
2379 +
2380 + // Close button handler
2381 + const closeBtn = modal.querySelector('.mxchat-modal-close');
2382 + if (closeBtn) {
2383 + closeBtn.onclick = closeModal;
2384 + }
2385 +
2386 + // Cancel button handler
2387 + const cancelBtns = modal.querySelectorAll('.mxchat-modal-cancel');
2388 + if (cancelBtns) {
2389 + cancelBtns.forEach(btn => {
2390 + btn.onclick = closeModal;
2391 + });
2392 + }
2393 +
2394 + // Click outside modal to close
2395 + modal.onclick = (e) => {
2396 + if (e.target === modal) {
2397 + closeModal();
2398 + }
2399 + };
2400 +
2401 + // Escape key to close modal
2402 + document.addEventListener('keydown', function(e) {
2403 + if (e.key === 'Escape' && modal.classList.contains('active')) {
2404 + closeModal();
2405 + }
2406 + }, { once: true });
2407 +
2408 + // Focus appropriate field based on current step
2409 + if (isEdit || actionStep2.classList.contains('active')) {
2410 + if (labelField) labelField.focus();
2411 + } else {
2412 + if (searchInput) searchInput.focus();
2413 + }
2414 +
2415 + return closeModal; // Return close function for external use
2416 + };
2417 + // Setup add action buttons
2418 + const addActionBtn = document.getElementById('mxchat-add-action-btn');
2419 + if (addActionBtn) {
2420 + //console.log('Add action button found');
2421 + addActionBtn.onclick = () => window.mxchatOpenActionModal();
2422 + }
2423 +
2424 + const createFirstAction = document.getElementById('mxchat-create-first-action');
2425 + if (createFirstAction) {
2426 + //console.log('Create first action button found');
2427 + createFirstAction.onclick = () => window.mxchatOpenActionModal();
2428 + }
2429 +
2430 + // Setup edit buttons
2431 + const editButtons = document.querySelectorAll('.mxchat-action-card .mxchat-edit-button');
2432 + //console.log('Edit buttons found:', editButtons.length);
2433 + editButtons.forEach(button => {
2434 + button.onclick = () => {
2435 + const actionId = button.dataset.actionId;
2436 + const phrases = button.dataset.phrases;
2437 + const label = button.dataset.label;
2438 + const threshold = button.dataset.threshold || 85;
2439 + const callbackFunction = button.dataset.callbackFunction;
2440 + const enabledBots = button.dataset.enabledBots; // ADD THIS LINE
2441 +
2442 + window.mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction, enabledBots);
2443 + };
2444 + });
2445 + }
549 2446 });
550 2447
2448 +jQuery(document).ready(function($) {
2449 + // Toggle custom post types container
2450 + $('#mxchat-custom-post-types-toggle').on('click', function(e) {
2451 + e.preventDefault();
2452 +
2453 + $('#mxchat-custom-post-types-container').slideToggle(300);
2454 +
2455 + // Rotate the toggle icon
2456 + const $icon = $(this).find('.mxchat-accordion-icon');
2457 + if ($('#mxchat-custom-post-types-container').is(':visible')) {
2458 + $icon.css('transform', 'rotate(180deg)');
2459 + $(this).closest('.mxchat-settings-accordion').addClass('active');
2460 + } else {
2461 + $icon.css('transform', 'rotate(0deg)');
2462 + $(this).closest('.mxchat-settings-accordion').removeClass('active');
2463 + }
2464 + });
2465 +
2466 + // If there are any selections made, auto-expand the container
2467 + function autoExpandIfNeeded() {
2468 + // Check if any checkbox in the container is checked
2469 + const hasCheckedItems = $('#mxchat-custom-post-types-container input[type="checkbox"]:checked').length > 0;
2470 +
2471 + if (hasCheckedItems) {
2472 + $('#mxchat-custom-post-types-container').show();
2473 + $('#mxchat-custom-post-types-toggle .mxchat-accordion-icon').css('transform', 'rotate(180deg)');
2474 + $('.mxchat-settings-accordion').addClass('active');
2475 + }
2476 + }
2477 +
2478 + // Run on page load
2479 + autoExpandIfNeeded();
2480 +});
551 2481
2482 +document.addEventListener('DOMContentLoaded', function() {
2483 + var viewSampleBtn = document.getElementById('mxchatViewSampleBtn');
2484 + var modal = document.getElementById('mxchatSampleModal');
2485 + var modalClose = document.getElementById('mxchatModalClose');
2486 + var closeBtn = document.getElementById('mxchatCloseBtn');
2487 + var copyBtn = document.getElementById('mxchatCopyBtn');
2488 + var instructionsContent = document.querySelector('.mxchat-instructions-content');
2489 + var modalContent = document.querySelector('.mxchat-instructions-modal-content');
552 2490
2491 + if (!viewSampleBtn || !modal) {
2492 + return;
2493 + }
2494 +
2495 + // Open modal
2496 + viewSampleBtn.addEventListener('click', function(e) {
2497 + e.preventDefault();
2498 + e.stopPropagation();
2499 + modal.classList.add('mxchat-instructions-show');
2500 + });
2501 +
2502 + // Close modal function
2503 + function closeModal(e) {
2504 + if (e) {
2505 + e.preventDefault();
2506 + e.stopPropagation();
2507 + }
2508 + modal.classList.remove('mxchat-instructions-show');
2509 + }
2510 +
2511 + // Close modal events
2512 + if (modalClose) {
2513 + modalClose.addEventListener('click', function(e) {
2514 + closeModal(e);
2515 + });
2516 + }
2517 +
2518 + if (closeBtn) {
2519 + closeBtn.addEventListener('click', function(e) {
2520 + closeModal(e);
2521 + });
2522 + }
2523 +
2524 + // Close on backdrop click ONLY (not on hover)
2525 + modal.addEventListener('click', function(e) {
2526 + // Only close if clicking directly on the overlay, not on child elements
2527 + if (e.target === modal) {
2528 + closeModal(e);
2529 + }
2530 + });
2531 +
2532 + // Prevent modal content clicks from closing the modal
2533 + if (modalContent) {
2534 + modalContent.addEventListener('click', function(e) {
2535 + e.stopPropagation();
2536 + });
2537 + }
2538 +
2539 + // Close on escape key
2540 + document.addEventListener('keydown', function(e) {
2541 + if (e.key === 'Escape' && modal.classList.contains('mxchat-instructions-show')) {
2542 + closeModal();
2543 + }
2544 + });
2545 +
2546 + // Copy functionality
2547 + if (copyBtn && instructionsContent) {
2548 + copyBtn.addEventListener('click', function(e) {
2549 + e.preventDefault();
2550 + e.stopPropagation();
2551 +
2552 + var text = instructionsContent.textContent;
2553 +
2554 + if (navigator.clipboard) {
2555 + navigator.clipboard.writeText(text).then(function() {
2556 + showCopySuccess();
2557 + }).catch(function() {
2558 + fallbackCopy(text);
2559 + });
2560 + } else {
2561 + fallbackCopy(text);
2562 + }
2563 + });
2564 + }
2565 +
2566 + function fallbackCopy(text) {
2567 + var textArea = document.createElement('textarea');
2568 + textArea.value = text;
2569 + textArea.style.position = 'fixed';
2570 + textArea.style.left = '-999999px';
2571 + textArea.style.top = '-999999px';
2572 + document.body.appendChild(textArea);
2573 + textArea.select();
2574 + try {
2575 + document.execCommand('copy');
2576 + showCopySuccess();
2577 + } catch (err) {
2578 + console.error('Copy failed');
2579 + }
2580 + document.body.removeChild(textArea);
2581 + }
2582 +
2583 + function showCopySuccess() {
2584 + var originalText = copyBtn.innerHTML;
2585 + 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!';
2586 +
2587 + setTimeout(function() {
2588 + copyBtn.innerHTML = originalText;
2589 + }, 2000);
2590 + }
2591 +});
2592 +
2593 +jQuery(document).ready(function($) {
2594 + var emailDependentFields = [
2595 + '#email_blocker_header_content',
2596 + '#email_blocker_button_text',
2597 + '#enable_name_field',
2598 + '#name_field_placeholder'
2599 + ];
2600 +
2601 + // Add visual indicators
2602 + emailDependentFields.forEach(function(fieldId) {
2603 + var $row = $(fieldId).closest('tr');
2604 + $row.addClass('email-dependent-field');
2605 +
2606 + // Add an icon to show it's dependent
2607 + var $label = $row.find('th label, th');
2608 + $label.prepend('<span class="email-dependent-icon" style="color: #0073aa; margin-right: 5px;">↳</span>');
2609 + });
2610 +
2611 + // Hide initially with opacity for smoother transition
2612 + emailDependentFields.forEach(function(fieldId) {
2613 + $(fieldId).closest('tr').hide().css('opacity', '0');
2614 + });
2615 +
2616 + function toggleEmailFields() {
2617 + var emailEnabled = $('#enable_email_block').is(':checked');
2618 +
2619 + emailDependentFields.forEach(function(fieldId) {
2620 + var $row = $(fieldId).closest('tr');
2621 + if (emailEnabled) {
2622 + $row.slideDown(400).animate({opacity: 1}, 200);
2623 + } else {
2624 + $row.animate({opacity: 0}, 200).slideUp(400);
2625 + }
2626 + });
2627 + }
2628 +
2629 + toggleEmailFields();
2630 + $('#enable_email_block').on('change', toggleEmailFields);
2631 +});
2632 +
2633 +
2634 +
2635 +//Handle role restriction changes
2636 +jQuery(document).ready(function($) {
2637 + //Handle role restriction changes for both data sources
2638 + $(document).on('change', '.mxchat-role-select', function() {
2639 + const $select = $(this);
2640 + const entryId = $select.data('entry-id');
2641 + const dataSource = $select.data('data-source');
2642 + const roleRestriction = $select.val();
2643 + const nonce = $select.data('nonce');
2644 +
2645 + // Visual feedback
2646 + $select.prop('disabled', true).addClass('updating');
2647 +
2648 + $.ajax({
2649 + url: ajaxurl,
2650 + type: 'POST',
2651 + data: {
2652 + action: 'mxchat_update_role_restriction',
2653 + nonce: nonce,
2654 + entry_id: entryId,
2655 + data_source: dataSource, // NEW: Include data source
2656 + role_restriction: roleRestriction
2657 + },
2658 + success: function(response) {
2659 + if (response.success) {
2660 + // Show success feedback
2661 + $select.removeClass('updating').addClass('updated');
2662 + setTimeout(() => {
2663 + $select.removeClass('updated');
2664 + }, 2000);
2665 + } else {
2666 + alert('Failed to update role restriction: ' + response.data);
2667 + // Revert selection
2668 + $select.val($select.data('original-value'));
2669 + }
2670 + },
2671 + error: function() {
2672 + alert('Error updating role restriction');
2673 + // Revert selection
2674 + $select.val($select.data('original-value'));
2675 + },
2676 + complete: function() {
2677 + $select.prop('disabled', false);
2678 + }
2679 + });
2680 +
2681 + // Store original value for potential revert
2682 + $select.data('original-value', roleRestriction);
2683 + });
2684 +
2685 + // Store initial values
2686 + $('.mxchat-role-select').each(function() {
2687 + $(this).data('original-value', $(this).val());
2688 + });
2689 +});