PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.1.0
MxChat – AI Chatbot & Content Generation for WordPress v2.1.0
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.1.0, at js/mxchat-admin.js

561 lines 21.7 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-tab-button').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-tab-button').off('click.mxchat');
255
256 // Add new click handlers
257 $('.mxchat-tab-button').on('click.mxchat', function(e) {
258 e.preventDefault();
259 e.stopPropagation();
260
261 var $this = $(this);
262
263 // Get tab ID from data-tab attribute
264 var tabId = $this.data('tab') || 'chatbot';
265
266 // Safety check for empty tabId
267 if (!tabId) {
268 console.warn('No tab identifier found');
269 return;
270 }
271
272 // Update tab buttons
273 $('.mxchat-tab-button').removeClass('active');
274 $this.addClass('active');
275
276 // Update content areas - with safety check
277 $('.mxchat-tab-content').removeClass('active');
278 var $targetTab = $('#' + tabId);
279 if ($targetTab.length) {
280 $targetTab.addClass('active');
281
282 // Store active tab
283 try {
284 localStorage.setItem('mxchat_active_tab', tabId);
285 } catch (e) {
286 console.warn('LocalStorage not available:', e);
287 }
288 } else {
289 console.warn('Tab content #' + tabId + ' not found');
290 }
291 });
292 }
293
294 // Initialize tabs and handle events
295 initTabs();
296 $(document).on('widget-added widget-updated postbox-toggled', initTabs);
297
298 // Activate initial tab
299 try {
300 var savedTab = localStorage.getItem('mxchat_active_tab');
301 if (savedTab && $('#' + savedTab).length > 0) {
302 $('.mxchat-tab-button[data-tab="' + savedTab + '"]').trigger('click.mxchat');
303 } else {
304 $('.mxchat-tab-button').first().trigger('click.mxchat');
305 }
306 } catch (e) {
307 $('.mxchat-tab-button').first().trigger('click.mxchat');
308 }
309
310 // Attach edit modal event handler
311 $(document).on('click', '.mxchat-edit-button', function() {
312 const intentId = $(this).data('intent-id');
313 const phrases = $(this).data('phrases');
314 mxchatOpenEditModal(intentId, phrases);
315 });
316
317 // Toggle visibility handlers
318 function toggleVisibility(selector) {
319 $(selector).on('click', function() {
320 var inputField = $(this).prev('input');
321 if (inputField.attr('type') === 'password') {
322 inputField.attr('type', 'text');
323 $(this).text('Hide');
324 } else {
325 inputField.attr('type', 'password');
326 $(this).text('Show');
327 }
328 });
329 }
330
331 // Initialize all toggle visibility buttons
332 [
333 '#toggleApiKeyVisibility',
334 '#toggleWooCommerceSecretVisibility',
335 '#toggleVoyageAPIKeyVisibility',
336 '#toggleLoopsApiKeyVisibility',
337 '#toggleXaiApiKeyVisibility',
338 '#toggleClaudeApiKeyVisibility',
339 '#toggleBraveApiKeyVisibility',
340 '#toggleWebhookUrlVisibility',
341 '#toggleSecretKeyVisibility',
342 '#toggleBotTokenVisibility',
343 '#toggleDeepSeekApiKeyVisibility'
344 ].forEach(toggleVisibility);
345
346 // Add Intent Form Submission
347 $('#mxchat-add-intent-form').on('submit', function(event) {
348 $('#mxchat-intent-loading').show();
349 $('#mxchat-intent-loading-text').show();
350 $(this).find('button[type="submit"]').hide();
351 });
352
353 // Inline Edit Functionality
354 $('.edit-button').on('click', function() {
355 var row = $(this).closest('tr');
356 row.find('.content-view, .url-view').hide();
357 row.find('.content-edit, .url-edit').show();
358 row.find('.edit-button').hide();
359 row.find('.save-button').show();
360 });
361
362 // Save button handler
363 $('.save-button').on('click', function() {
364 var button = $(this);
365 var row = button.closest('tr');
366 var id = button.data('id');
367 var newContent = row.find('.content-edit').val();
368 var newUrl = row.find('.url-edit').val();
369
370 button.prop('disabled', true);
371 button.text('Saving...');
372
373 $.ajax({
374 url: mxchatAdmin.ajax_url,
375 type: 'POST',
376 data: {
377 action: 'mxchat_save_inline_prompt',
378 id: id,
379 article_content: newContent,
380 article_url: newUrl,
381 _ajax_nonce: mxchatAdmin.inline_edit_nonce
382 },
383 success: function(response) {
384 button.prop('disabled', false);
385 button.text('Save');
386
387 if (response.success) {
388 row.find('.content-view').html(newContent.replace(/\n/g, "<br>"));
389 if (newUrl) {
390 row.find('.url-view').html('<a href="' + newUrl + '" target="_blank">' + newUrl + '</a>');
391 } else {
392 row.find('.url-view').html('N/A');
393 }
394
395 row.find('.content-edit, .url-edit').hide();
396 row.find('.content-view, .url-view').show();
397 row.find('.save-button').hide();
398 row.find('.edit-button').show();
399 } else {
400 alert('Error saving content: ' + (response.data?.message || 'Unknown error'));
401 }
402 },
403 error: function() {
404 button.prop('disabled', false);
405 button.text('Save');
406 alert('An error occurred while saving.');
407 }
408 });
409 });
410
411 // Activation handling
412 const form = $('#mxchat-activation-form');
413 const spinner = $('#mxchat-activation-spinner');
414 const submitButton = $('#activate_license_button');
415 const licenseStatus = $('#mxchat-license-status');
416
417 if (form.length && licenseStatus.length && submitButton.length) {
418 function handleActivationResponse(response) {
419 spinner.hide();
420 if (response.success) {
421 licenseStatus.text('Active');
422 licenseStatus.removeClass('inactive').addClass('active');
423 form.hide();
424 } else {
425 licenseStatus.text('Inactive');
426 alert(response.data || 'Activation failed. Please check your input.');
427 submitButton.prop('disabled', false);
428 }
429 }
430
431 form.on('submit', function(event) {
432 event.preventDefault();
433 spinner.show();
434 submitButton.prop('disabled', true);
435
436 var formData = {
437 action: 'mxchat_activate_license',
438 mxchat_pro_email: $('#mxchat_pro_email').val(),
439 mxchat_activation_key: $('#mxchat_activation_key').val(),
440 security: mxchatAdmin.license_nonce
441 };
442
443 $.post(mxchatAdmin.ajax_url, formData, function(response) {
444 handleActivationResponse(response);
445 }).fail(function() {
446 alert('Server error. Please try again.');
447 spinner.hide();
448 submitButton.prop('disabled', false);
449 });
450 });
451 }
452
453 // Questions handling
454 $('.mxchat-add-question').on('click', function () {
455 const container = $('#mxchat-additional-questions-container');
456 const questionCount = container.find('.mxchat-question-row').length + 4;
457 const questionIndex = container.find('.mxchat-question-row').length;
458
459 const newQuestion = `
460 <div class="mxchat-question-row">
461 <input type="text"
462 name="additional_popular_questions[]"
463 placeholder="Enter Additional Popular Question ${questionCount}"
464 class="regular-text mxchat-question-input"
465 data-question-index="${questionIndex}" />
466 <button type="button" class="button mxchat-remove-question"
467 aria-label="Remove question">Remove</button>
468 </div>
469 `;
470 container.append(newQuestion);
471 });
472
473 $(document).on('click', '.mxchat-remove-question', function () {
474 $(this).closest('.mxchat-question-row').remove();
475 saveQuestions();
476 });
477
478 $(document).on('change', '.mxchat-question-input', function() {
479 saveQuestions();
480 });
481
482 function saveQuestions() {
483 const questions = [];
484 $('.mxchat-question-input').each(function() {
485 const value = $(this).val().trim();
486 if (value) {
487 questions.push(value);
488 }
489 });
490
491 const feedbackContainer = $('<div class="feedback-container"></div>');
492 const spinner = $('<div class="saving-spinner"></div>');
493 const successIcon = $('<div class="success-icon">✔</div>');
494
495 // Append feedback after the add button
496 $('.mxchat-add-question').after(feedbackContainer);
497 feedbackContainer.append(spinner);
498
499 // Save via AJAX
500 $.ajax({
501 url: mxchatAdmin.ajax_url,
502 type: 'POST',
503 data: {
504 action: 'mxchat_save_setting',
505 name: 'additional_popular_questions',
506 value: JSON.stringify(questions),
507 _ajax_nonce: mxchatAdmin.setting_nonce
508 },
509 success: function(response) {
510 if (response.success) {
511 spinner.fadeOut(200, function() {
512 feedbackContainer.append(successIcon);
513 successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
514 feedbackContainer.remove();
515 });
516 });
517 } else {
518 alert('Error saving questions: ' + (response.data?.message || 'Unknown error'));
519 feedbackContainer.remove();
520 }
521 },
522 error: function() {
523 alert('An error occurred while saving questions.');
524 feedbackContainer.remove();
525 }
526 });
527 }
528
529 // Live agent status handler
530 const statusToggle = document.getElementById('live_agent_status');
531 const statusText = statusToggle?.parentElement.nextElementSibling?.querySelector('.status-text');
532 if (statusToggle && statusText) {
533 statusToggle.addEventListener('change', function() {
534 // Update display text
535 statusText.textContent = this.checked ? 'Online' : 'Offline';
536
537 // Send the correct on/off value to the server
538 if (window.mxchatSaveSetting) {
539 window.mxchatSaveSetting('live_agent_status', this.checked ? 'on' : 'off');
540 }
541 });
542 }
543
544 // Function to adjust the textarea height to content
545 function adjustTextareaHeight() {
546 this.style.height = 'auto'; // Reset to auto to calculate scrollHeight
547 this.style.height = this.scrollHeight + 'px'; // Expand to content height
548 }
549
550 // Function to reset the textarea height to initial
551 function resetTextareaHeight() {
552 this.style.height = ''; // Remove inline height, reverting to CSS default
553 }
554
555 // Target the specific textarea by ID
556 var $textarea = $('#system_prompt_instructions');
557
558 // Bind events
559 $textarea.on('focus input', adjustTextareaHeight) // Expand on focus and input
560 .on('blur', resetTextareaHeight); // Reset on blur
561 });