PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.0.8
MxChat – AI Chatbot & Content Generation for WordPress v2.0.8
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / js / mxchat-admin.js

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

572 lines 21.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // Simple debounce function implementation
2 function debounce(func, wait) {
3 let timeout;
4 return function executedFunction(...args) {
5 const later = () => {
6 clearTimeout(timeout);
7 func(...args);
8 };
9 clearTimeout(timeout);
10 timeout = setTimeout(later, wait);
11 };
12 }
13
14 // Helper function to open edit modal
15 function mxchatOpenEditModal(intentId, phrases) {
16 const modal = document.getElementById('mxchat-edit-modal');
17 if (!modal) return;
18
19 // Get form fields
20 const intentIdField = document.getElementById('edit_intent_id');
21 const phrasesField = document.getElementById('edit_phrases');
22
23 // Set values
24 intentIdField.value = intentId;
25 phrasesField.value = phrases;
26
27 // Show modal with animation
28 modal.style.display = 'flex';
29 requestAnimationFrame(() => {
30 modal.classList.add('active');
31 });
32
33 // Set up close handlers
34 const closeModal = () => {
35 modal.classList.remove('active');
36 setTimeout(() => {
37 modal.style.display = 'none';
38 }, 300); // Match the CSS transition time
39 };
40
41 // Close button handler
42 const closeBtn = modal.querySelector('.mxchat-modal-close');
43 if (closeBtn) {
44 closeBtn.onclick = closeModal;
45 }
46
47 // Cancel button handler
48 const cancelBtn = modal.querySelector('.mxchat-modal-cancel');
49 if (cancelBtn) {
50 cancelBtn.onclick = closeModal;
51 }
52
53 // Click outside modal to close
54 modal.onclick = (e) => {
55 if (e.target === modal) {
56 closeModal();
57 }
58 };
59
60 // Focus the textarea
61 phrasesField.focus();
62 }
63
64 // Initialize event listeners
65 document.addEventListener('DOMContentLoaded', () => {
66 // Set up edit button handlers
67 document.querySelectorAll('.mxchat-edit-button').forEach(button => {
68 button.onclick = () => {
69 const intentId = button.dataset.intentId;
70 const phrases = button.dataset.phrases;
71 mxchatOpenEditModal(intentId, phrases);
72 };
73 });
74 });
75
76 jQuery(document).ready(function($) {
77 // Ensure we have a debounce function (use lodash if available, otherwise use our implementation)
78 const useDebounce = (window._ && window._.debounce) ? window._.debounce : debounce;
79
80 // --- AJAX Auto-Save ---
81 const $autosaveSections = $('.mxchat-autosave-section');
82
83 if ($autosaveSections.length) {
84 // Handle real-time range slider value updates
85 $autosaveSections.find('input[type="range"]').on('input', function() {
86 const value = $(this).val();
87 $('#threshold_value').text(value);
88 });
89
90 // 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 });
152 } else {
153 alert('Error saving: ' + (response.data?.message || 'Unknown error'));
154 if ($field.attr('type') === 'checkbox') {
155 $field.prop('checked', !$field.is(':checked'));
156 }
157 feedbackContainer.remove();
158 }
159 },
160 error: function() {
161 alert('An error occurred while saving.');
162 if ($field.attr('type') === 'checkbox') {
163 $field.prop('checked', !$field.is(':checked'));
164 }
165 feedbackContainer.remove();
166 }
167 });
168 });
169
170 // Initialize color pickers with debouncing
171 $autosaveSections.find('.my-color-field').each(function() {
172 const $colorField = $(this);
173
174 $(this).wpColorPicker({
175 change: useDebounce(function(event, ui) {
176 // Safety check - ensure we have a valid field and value
177 if (!$colorField || !$colorField.val()) {
178 console.warn('Color picker not ready');
179 return;
180 }
181
182 const name = $colorField.attr('name');
183 const value = $colorField.val();
184
185 if (!name || !value) {
186 console.warn('Missing required color picker values');
187 return;
188 }
189
190 // Create feedback container
191 const feedbackContainer = $('<div class="feedback-container"></div>');
192 const spinner = $('<div class="saving-spinner"></div>');
193 const successIcon = $('<div class="success-icon">✔</div>');
194
195 // Position feedback container
196 $colorField.closest('.wp-picker-container').after(feedbackContainer);
197 feedbackContainer.append(spinner);
198
199 // Determine AJAX action and nonce for color fields:
200 var ajaxAction, nonce;
201 if ( name.indexOf('mxchat_prompts_options') !== -1 ||
202 name === 'mxchat_auto_sync_posts' ||
203 name === 'mxchat_auto_sync_pages' ) {
204 ajaxAction = 'mxchat_save_prompts_setting';
205 nonce = mxchatPromptsAdmin.prompts_setting_nonce;
206 } else {
207 ajaxAction = 'mxchat_save_setting';
208 nonce = mxchatAdmin.setting_nonce;
209 }
210
211 // AJAX save request
212 $.ajax({
213 url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
214 type: 'POST',
215 data: {
216 action: ajaxAction,
217 name: name,
218 value: value,
219 _ajax_nonce: nonce
220 },
221 success: function(response) {
222 if (response.success) {
223 spinner.fadeOut(200, function() {
224 feedbackContainer.append(successIcon);
225 successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
226 feedbackContainer.remove();
227 });
228 });
229 } else {
230 alert('Error saving: ' + (response.data?.message || 'Unknown error'));
231 feedbackContainer.remove();
232 }
233 },
234 error: function() {
235 alert('An error occurred while saving.');
236 feedbackContainer.remove();
237 }
238 });
239 }, 500)
240 });
241 });
242
243 // Reinitialize color pickers when switching tabs
244 $('.mxchat-nav-tab').on('click.mxchat', function() {
245 setTimeout(function() {
246 $('.my-color-field:visible').wpColorPicker('close');
247 }, 100);
248 });
249 }
250
251 // Initialize tabs system
252 function initTabs() {
253 // Remove any existing handlers first
254 $('.mxchat-nav-tab').off('click.mxchat');
255
256 // Add new click handlers
257 $('.mxchat-nav-tab').on('click.mxchat', function(e) {
258 e.preventDefault();
259 e.stopPropagation();
260
261 var $this = $(this);
262
263 // Get tab ID - try href first, fallback to data-tab, then to default
264 var tabId = $this.attr('href');
265 if (tabId) {
266 tabId = tabId.replace('#', '');
267 } else {
268 tabId = $this.data('tab') || 'chatbot';
269 }
270
271 // Safety check for empty tabId
272 if (!tabId) {
273 console.warn('No tab identifier found');
274 return;
275 }
276
277 // Update tabs
278 $('.mxchat-nav-tab').removeClass('mxchat-nav-tab-active');
279 $this.addClass('mxchat-nav-tab-active');
280
281 // Update content areas - with safety check
282 $('.mxchat-tab-content').removeClass('active').hide();
283 var $targetTab = $('#' + tabId);
284 if ($targetTab.length) {
285 $targetTab.addClass('active').show();
286
287 // Store active tab
288 try {
289 localStorage.setItem('mxchat_active_tab', tabId);
290 } catch (e) {
291 console.warn('LocalStorage not available:', e);
292 }
293 } else {
294 console.warn('Tab content #' + tabId + ' not found');
295 }
296 });
297 }
298
299 // Initialize tabs and handle events
300 initTabs();
301 $(document).on('widget-added widget-updated postbox-toggled', initTabs);
302
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');
310 }
311 } catch (e) {
312 $('.mxchat-nav-tab').first().trigger('click.mxchat');
313 }
314
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);
320 });
321
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');
329 } else {
330 inputField.attr('type', 'password');
331 $(this).text('Show');
332 }
333 });
334 }
335
336 // Initialize all toggle visibility buttons
337 [
338 '#toggleApiKeyVisibility',
339 '#toggleWooCommerceSecretVisibility',
340 '#toggleVoyageAPIKeyVisibility',
341 '#toggleLoopsApiKeyVisibility',
342 '#toggleXaiApiKeyVisibility',
343 '#toggleClaudeApiKeyVisibility',
344 '#toggleBraveApiKeyVisibility',
345 '#toggleWebhookUrlVisibility',
346 '#toggleSecretKeyVisibility',
347 '#toggleBotTokenVisibility',
348 '#toggleDeepSeekApiKeyVisibility'
349 ].forEach(toggleVisibility);
350
351 // Add Intent Form Submission
352 $('#mxchat-add-intent-form').on('submit', function(event) {
353 $('#mxchat-intent-loading').show();
354 $('#mxchat-intent-loading-text').show();
355 $(this).find('button[type="submit"]').hide();
356 });
357
358 // Inline Edit Functionality
359 $('.edit-button').on('click', function() {
360 var row = $(this).closest('tr');
361 row.find('.content-view, .url-view').hide();
362 row.find('.content-edit, .url-edit').show();
363 row.find('.edit-button').hide();
364 row.find('.save-button').show();
365 });
366
367 // Save button handler
368 $('.save-button').on('click', function() {
369 var button = $(this);
370 var row = button.closest('tr');
371 var id = button.data('id');
372 var newContent = row.find('.content-edit').val();
373 var newUrl = row.find('.url-edit').val();
374
375 button.prop('disabled', true);
376 button.text('Saving...');
377
378 $.ajax({
379 url: mxchatAdmin.ajax_url,
380 type: 'POST',
381 data: {
382 action: 'mxchat_save_inline_prompt',
383 id: id,
384 article_content: newContent,
385 article_url: newUrl,
386 _ajax_nonce: mxchatAdmin.inline_edit_nonce
387 },
388 success: function(response) {
389 button.prop('disabled', false);
390 button.text('Save');
391
392 if (response.success) {
393 row.find('.content-view').html(newContent.replace(/\n/g, "<br>"));
394 if (newUrl) {
395 row.find('.url-view').html('<a href="' + newUrl + '" target="_blank">' + newUrl + '</a>');
396 } else {
397 row.find('.url-view').html('N/A');
398 }
399
400 row.find('.content-edit, .url-edit').hide();
401 row.find('.content-view, .url-view').show();
402 row.find('.save-button').hide();
403 row.find('.edit-button').show();
404 } else {
405 alert('Error saving content: ' + (response.data?.message || 'Unknown error'));
406 }
407 },
408 error: function() {
409 button.prop('disabled', false);
410 button.text('Save');
411 alert('An error occurred while saving.');
412 }
413 });
414 });
415
416 // Activation handling
417 const form = $('#mxchat-activation-form');
418 const spinner = $('#mxchat-activation-spinner');
419 const submitButton = $('#activate_license_button');
420 const licenseStatus = $('#mxchat-license-status');
421
422 if (form.length && licenseStatus.length && submitButton.length) {
423 function handleActivationResponse(response) {
424 spinner.hide();
425 if (response.success) {
426 licenseStatus.text('Active');
427 licenseStatus.removeClass('inactive').addClass('active');
428 form.hide();
429 } else {
430 licenseStatus.text('Inactive');
431 alert(response.data || 'Activation failed. Please check your input.');
432 submitButton.prop('disabled', false);
433 }
434 }
435
436 form.on('submit', function(event) {
437 event.preventDefault();
438 spinner.show();
439 submitButton.prop('disabled', true);
440
441 var formData = {
442 action: 'mxchat_activate_license',
443 mxchat_pro_email: $('#mxchat_pro_email').val(),
444 mxchat_activation_key: $('#mxchat_activation_key').val(),
445 security: mxchatAdmin.license_nonce
446 };
447
448 $.post(mxchatAdmin.ajax_url, formData, function(response) {
449 handleActivationResponse(response);
450 }).fail(function() {
451 alert('Server error. Please try again.');
452 spinner.hide();
453 submitButton.prop('disabled', false);
454 });
455 });
456 }
457
458 // Questions handling
459 $('.mxchat-add-question').on('click', function () {
460 const container = $('#mxchat-additional-questions-container');
461 const questionCount = container.find('.mxchat-question-row').length + 4;
462 const questionIndex = container.find('.mxchat-question-row').length;
463
464 const newQuestion = `
465 <div class="mxchat-question-row">
466 <input type="text"
467 name="additional_popular_questions[]"
468 placeholder="Enter Additional Popular Question ${questionCount}"
469 class="regular-text mxchat-question-input"
470 data-question-index="${questionIndex}" />
471 <button type="button" class="button mxchat-remove-question"
472 aria-label="Remove question">Remove</button>
473 </div>
474 `;
475 container.append(newQuestion);
476 });
477
478 $(document).on('click', '.mxchat-remove-question', function () {
479 $(this).closest('.mxchat-question-row').remove();
480 saveQuestions();
481 });
482
483 $(document).on('change', '.mxchat-question-input', function() {
484 saveQuestions();
485 });
486
487 function saveQuestions() {
488 const questions = [];
489 $('.mxchat-question-input').each(function() {
490 const value = $(this).val().trim();
491 if (value) {
492 questions.push(value);
493 }
494 });
495
496 const feedbackContainer = $('<div class="feedback-container"></div>');
497 const spinner = $('<div class="saving-spinner"></div>');
498 const successIcon = $('<div class="success-icon">✔</div>');
499
500 // Append feedback after the add button
501 $('.mxchat-add-question').after(feedbackContainer);
502 feedbackContainer.append(spinner);
503
504 // Save via AJAX
505 $.ajax({
506 url: mxchatAdmin.ajax_url,
507 type: 'POST',
508 data: {
509 action: 'mxchat_save_setting',
510 name: 'additional_popular_questions',
511 value: JSON.stringify(questions),
512 _ajax_nonce: mxchatAdmin.setting_nonce
513 },
514 success: function(response) {
515 if (response.success) {
516 spinner.fadeOut(200, function() {
517 feedbackContainer.append(successIcon);
518 successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
519 feedbackContainer.remove();
520 });
521 });
522 } else {
523 alert('Error saving questions: ' + (response.data?.message || 'Unknown error'));
524 feedbackContainer.remove();
525 }
526 },
527 error: function() {
528 alert('An error occurred while saving questions.');
529 feedbackContainer.remove();
530 }
531 });
532 }
533
534 // Live agent status handler
535 const statusToggle = document.getElementById('live_agent_status');
536 const statusText = statusToggle?.parentElement.nextElementSibling?.querySelector('.status-text');
537 if (statusToggle && statusText) {
538 statusToggle.addEventListener('change', function() {
539 // Update display text
540 statusText.textContent = this.checked ? 'Online' : 'Offline';
541
542 // Send the correct on/off value to the server
543 if (window.mxchatSaveSetting) {
544 window.mxchatSaveSetting('live_agent_status', this.checked ? 'on' : 'off');
545 }
546 });
547 }
548
549 // Function to adjust the textarea height to content
550 function adjustTextareaHeight() {
551 this.style.height = 'auto'; // Reset to auto to calculate scrollHeight
552 this.style.height = this.scrollHeight + 'px'; // Expand to content height
553 }
554
555 // Function to reset the textarea height to initial
556 function resetTextareaHeight() {
557 this.style.height = ''; // Remove inline height, reverting to CSS default
558 }
559
560 // Target the specific textarea by ID
561 var $textarea = $('#system_prompt_instructions');
562
563 // Bind events
564 $textarea.on('focus input', adjustTextareaHeight) // Expand on focus and input
565 .on('blur', resetTextareaHeight); // Reset on blur
566
567
568 });
569
570
571
572