PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.8
MxChat – AI Chatbot & Content Generation for WordPress v3.2.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 / content-selector.js

content-selector.js in MxChat – AI Chatbot & Content Generation for WordPress 3.2.8, at js/content-selector.js

979 lines 40.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 jQuery(document).ready(function($) {
2 // Modal elements
3 const $modal = $('#mxchat-kb-content-selector-modal');
4 const $openButton = $('#mxchat-open-content-selector');
5 const $closeButtons = $('.mxchat-kb-modal-close');
6 const $contentList = $('.mxchat-kb-content-list');
7 const $loading = $('.mxchat-kb-loading');
8 const $pagination = $('.mxchat-kb-pagination');
9 const $processButton = $('#mxchat-kb-process-selected');
10 const $selectAll = $('#mxchat-kb-select-all');
11 const $selectionCount = $('.mxchat-kb-selection-count');
12 const $acfPdfExtractCheckbox = $('#mxchat-kb-acf-pdf-extract');
13
14 // Restore last-used ACF→PDF extract preference from the server-localized default.
15 if (typeof mxchatSelector !== 'undefined' && mxchatSelector.acfPdfExtractDefault) {
16 $acfPdfExtractCheckbox.prop('checked', true);
17 }
18
19 // Filter elements
20 const $searchInput = $('#mxchat-kb-content-search');1
21 const $typeFilter = $('#mxchat-kb-content-type-filter');
22 const $statusFilter = $('#mxchat-kb-content-status-filter');
23 const $processedFilter = $('#mxchat-kb-processed-filter');
24
25 // Current state - using let for variables that change
26 let currentPage = 1;
27 let totalPages = 1;
28 let selectedItems = new Set();
29 let allItems = [];
30
31 // Open modal when WordPress import button is clicked
32 $openButton.on('click', function() {
33 $modal.show();
34 // Reset to first page when opening the modal
35 currentPage = 1;
36 loadContent();
37 });
38
39 // Close modal
40 $closeButtons.on('click', function() {
41 $modal.hide();
42 });
43 // Handle import option box clicks (for non-WordPress options)
44 $('.mxchat-import-box').on('click', function() {
45 const $box = $(this);
46 const option = $box.data('option');
47
48 // Skip if this is the WordPress option (it has its own handler)
49 if (option === 'wordpress') {
50 return;
51 }
52
53 // Update active state
54 $('.mxchat-import-box').removeClass('active');
55 $box.addClass('active');
56
57 // Hide all input areas
58 $('#mxchat-url-input-area, #mxchat-content-input-area, #mxchat-pdf-upload-area').hide();
59
60 // Hide sitemap-specific sections (but NOT for sitemap option - let detection logic handle it)
61 if (option !== 'sitemap') {
62 $('#mxchat-detected-sitemaps, #mxchat-no-sitemaps, #mxchat-sitemaps-loading').hide();
63 }
64
65 // Handle different import options
66 switch (option) {
67 case 'pdf-url':
68 case 'sitemap':
69 case 'url':
70 // Show URL input area with appropriate placeholder
71 $('#mxchat-url-input-area').show();
72 $('#sitemap_url').attr('placeholder', $box.data('placeholder'));
73 $('#import_type').val(option === 'pdf-url' ? 'pdf' : option);
74
75 // UPDATED: Add or update bot_id hidden field for URL forms
76 updateBotIdInForm('#mxchat-url-form');
77
78 // Update the description text based on the import type
79 let descriptionText = '';
80 if (option === 'pdf-url') {
81 descriptionText = 'Import a PDF document by entering its URL above. PDFs are processed via cron job. If processing does not start, you can manually process batch 5 pages at a time.';
82 } else if (option === 'sitemap') {
83 descriptionText = 'Enter a content-specific sub-sitemap URL, not the sitemap index. Sitemaps are processed via cron job. If processing does not start, you can manually process batch 5 pages at a time.';
84 // Re-show sitemap sections if they were previously loaded
85 const $sitemapsList = $('#mxchat-sitemaps-list');
86 if ($sitemapsList.children().length > 0) {
87 // Sitemaps were already loaded, just show the container
88 $('#mxchat-detected-sitemaps').show();
89 } else if ($('#mxchat-no-sitemaps').data('was-shown')) {
90 // No sitemaps message was shown before
91 $('#mxchat-no-sitemaps').show();
92 }
93 // Note: If neither condition is true, initSitemapDetection will show loading state
94 } else if (option === 'url') {
95 descriptionText = 'Import content from any webpage by entering its URL.';
96 }
97 $('#url-description-text').text(descriptionText);
98 break;
99
100 case 'content':
101 // Show content input area
102 $('#mxchat-content-input-area').show();
103
104 // UPDATED: Add or update bot_id hidden field for content forms
105 updateBotIdInForm('#mxchat-content-form');
106 break;
107
108 case 'pdf-upload':
109 // Show PDF file upload area
110 $('#mxchat-pdf-upload-area').show();
111
112 // Add or update bot_id hidden field for PDF upload form
113 updateBotIdInForm('#mxchat-pdf-upload-form');
114 break;
115 }
116 });
117
118 // Helper function to add/update bot_id hidden field in forms
119 function updateBotIdInForm(formSelector) {
120 const $form = $(formSelector);
121 if ($form.length === 0) return;
122
123 // Get current bot_id from the bot selector dropdown
124 const currentBotId = $('#mxchat-bot-selector').val();
125
126 // Only add bot_id field if multi-bot is active and bot is not 'default'
127 if (currentBotId && currentBotId !== 'default') {
128 // Remove existing bot_id field if it exists
129 $form.find('input[name="bot_id"]').remove();
130
131 // Add new bot_id field
132 $form.append('<input type="hidden" name="bot_id" value="' + currentBotId + '">');
133
134 console.log('Updated bot_id in form ' + formSelector + ' to: ' + currentBotId);
135 } else {
136 // Remove bot_id field if bot is default
137 $form.find('input[name="bot_id"]').remove();
138 }
139 }
140
141 // Load content via AJAX
142 function loadContent() {
143 $loading.show();
144 $contentList.find('.mxchat-kb-content-item').remove();
145
146 const data = {
147 action: 'mxchat_get_content_list',
148 nonce: mxchatSelector.nonce,
149 page: currentPage,
150 per_page: 100,
151 search: $searchInput.val(),
152 post_type: $typeFilter.val(),
153 post_status: $statusFilter.val(),
154 processed_filter: $processedFilter.val()
155 };
156
157 //console.log('Loading content for page', currentPage, 'with filters:', data);
158
159 $.ajax({
160 url: mxchatSelector.ajaxurl,
161 data: data,
162 method: 'GET',
163 dataType: 'json',
164 success: function(response) {
165 $loading.hide();
166
167 if (response.success && response.data.items && response.data.items.length > 0) {
168 // Store the items directly
169 let items = response.data.items;
170
171 if (items.length > 0) {
172 renderContentItems(items);
173 renderPagination(parseInt(response.data.current_page), parseInt(response.data.total_pages));
174
175 // Update state
176 allItems = items;
177 totalPages = parseInt(response.data.total_pages);
178 currentPage = parseInt(response.data.current_page);
179
180 // Update select all checkbox based on current selection
181 updateSelectAllState();
182 } else {
183 displayNoResults($processedFilter.val());
184 }
185 } else {
186 displayNoResults($processedFilter.val());
187 }
188 },
189 error: function(xhr, status, error) {
190 $loading.hide();
191 console.error('AJAX Error:', status, error);
192 $contentList.html('<div class="mxchat-kb-error">Error loading content. Please try again.</div>');
193 // Clear pagination on error
194 $pagination.empty();
195 }
196 });
197 }
198
199 // Helper function to display appropriate "no results" message
200 function displayNoResults(processedStatus) {
201 let message = 'No content found matching your criteria.';
202
203 if (processedStatus === 'processed') {
204 message = 'No content found in knowledge base.';
205 } else if (processedStatus === 'unprocessed') {
206 message = 'All content is already in knowledge base.';
207 }
208
209 $contentList.html('<div class="mxchat-kb-no-results">' + message + '</div>');
210 // Clear pagination when no results
211 $pagination.empty();
212 }
213
214 // Render content items
215 function renderContentItems(items) {
216 let html = '';
217
218 items.forEach(function(item) {
219 const isSelected = selectedItems.has(item.id);
220 const isProcessed = item.already_processed;
221 const chunkCount = item.chunk_count || 0;
222
223 // Updated badge text - include chunk count if > 1
224 let badgeText = 'Not In Knowledge Base';
225 if (isProcessed) {
226 badgeText = chunkCount > 1 ? `In Knowledge Base (${chunkCount} chunks)` : 'In Knowledge Base';
227 }
228 const badgeClass = isProcessed ? 'mxchat-kb-processed-badge' : 'mxchat-kb-unprocessed-badge';
229
230
231 html += `
232 <div class="mxchat-kb-content-item ${isProcessed ? 'processed' : ''}" data-id="${item.id}">
233 <div class="mxchat-kb-content-checkbox">
234 <input type="checkbox" id="content-${item.id}" ${isSelected ? 'checked' : ''}>
235 </div>
236 <div class="mxchat-kb-content-details">
237 <div class="mxchat-kb-content-title">
238 <a href="${item.permalink}" target="_blank">${item.title}</a>
239 <span class="${badgeClass}">${badgeText}</span>
240 ${isProcessed ? '<span class="mxchat-kb-last-updated">Last updated: ' + item.processed_date + '</span>' : ''}
241 </div>
242 <div class="mxchat-kb-content-meta">
243 <span class="mxchat-kb-content-type">${item.type}</span>
244 <span class="mxchat-kb-content-date">${item.date}</span>
245 <span class="mxchat-kb-content-words">${item.word_count} words</span>
246 </div>
247 <div class="mxchat-kb-content-excerpt">${item.excerpt}</div>
248 </div>
249 </div>
250 `;
251 });
252
253 $contentList.html(html);
254
255 // Add event listeners for checkboxes using delegation for better performance
256 $contentList.off('change', 'input[type="checkbox"]').on('change', 'input[type="checkbox"]', function() {
257 const $checkbox = $(this);
258 const itemId = parseInt($checkbox.closest('.mxchat-kb-content-item').data('id'));
259
260 if ($checkbox.is(':checked')) {
261 selectedItems.add(itemId);
262 } else {
263 selectedItems.delete(itemId);
264 }
265
266 updateSelection();
267 });
268 }
269
270 // Render pagination - FIXED VERSION
271 function renderPagination(currentPage, totalPages) {
272 // Clear existing pagination first
273 $pagination.empty();
274
275 // Don't render pagination if only one page
276 if (totalPages <= 1) {
277 return;
278 }
279
280 let html = '<div class="mxchat-kb-pagination-links">';
281
282 // Previous button
283 if (currentPage > 1) {
284 html += '<a href="#" class="mxchat-kb-page-link prev" data-page="' + (currentPage - 1) + '">&laquo; Previous</a>';
285 }
286
287 // Page numbers
288 const startPage = Math.max(1, currentPage - 2);
289 const endPage = Math.min(totalPages, startPage + 4);
290
291 for (let i = startPage; i <= endPage; i++) {
292 if (i === currentPage) {
293 html += '<span class="mxchat-kb-page-current">' + i + '</span>';
294 } else {
295 html += '<a href="#" class="mxchat-kb-page-link" data-page="' + i + '">' + i + '</a>';
296 }
297 }
298
299 // Next button
300 if (currentPage < totalPages) {
301 html += '<a href="#" class="mxchat-kb-page-link next" data-page="' + (currentPage + 1) + '">Next &raquo;</a>';
302 }
303
304 html += '</div>';
305
306 $pagination.html(html);
307 }
308
309 // Handle pagination clicks directly on the document
310 $(document).on('click', '.mxchat-kb-page-link', function(e) {
311 e.preventDefault();
312 const newPage = parseInt($(this).data('page'));
313 //console.log('Pagination clicked: changing from page', currentPage, 'to', newPage);
314
315 // Only reload if the page actually changed
316 if (currentPage !== newPage) {
317 currentPage = newPage;
318 loadContent();
319 }
320 });
321
322 // Update selection counts and button state
323 function updateSelection() {
324 const selectedCount = selectedItems.size;
325 $selectionCount.text(selectedCount + ' ' + (selectedCount === 1 ? 'selected' : 'selected'));
326 $('.mxchat-kb-selected-count').text('(' + selectedCount + ')');
327
328 // Show/hide clear all selections link
329 let $clearAllLink = $('.mxchat-kb-clear-all-selections');
330 if (selectedCount > 0) {
331 if ($clearAllLink.length === 0) {
332 $clearAllLink = $('<a href="#" class="mxchat-kb-clear-all-selections" style="margin-left: 10px; font-size: 12px; color: var(--mxch-error, #dc2626);">Clear all</a>');
333 $selectionCount.after($clearAllLink);
334 $clearAllLink.on('click', function(e) {
335 e.preventDefault();
336 selectedItems.clear();
337 $('.mxchat-kb-content-item input[type="checkbox"]').prop('checked', false);
338 updateSelection();
339 });
340 }
341 $clearAllLink.show();
342 } else {
343 $clearAllLink.hide();
344 }
345
346 // Determine if any selected items are already processed
347 const hasProcessedItems = Array.from(selectedItems).some(id => {
348 const item = allItems.find(item => item.id === id);
349 return item && item.already_processed;
350 });
351
352 if (selectedCount > 0) {
353 $processButton.prop('disabled', false);
354
355 // Update button text based on selection
356 if (hasProcessedItems && selectedCount === 1) {
357 $processButton.text('Update Selected Content (1)').addClass('update-mode');
358 } else if (hasProcessedItems && selectedCount > 1) {
359 $processButton.text('Process/Update Selected (' + selectedCount + ')').addClass('mixed-mode');
360 } else {
361 $processButton.text('Process Selected Content (' + selectedCount + ')').removeClass('update-mode mixed-mode');
362 }
363 } else {
364 $processButton.prop('disabled', true);
365 $processButton.text('Process Selected Content').removeClass('update-mode mixed-mode');
366 $('.mxchat-kb-selected-count').text('(0)');
367 }
368
369 updateSelectAllState();
370 }
371
372 // Update "Select All" checkbox state
373 function updateSelectAllState() {
374 const availableItems = allItems.length;
375 const selectedAvailableItems = allItems.filter(item => selectedItems.has(item.id)).length;
376
377 if (availableItems === 0) {
378 $selectAll.prop('checked', false);
379 $selectAll.prop('disabled', true);
380 } else if (selectedAvailableItems === availableItems) {
381 $selectAll.prop('checked', true);
382 } else {
383 $selectAll.prop('checked', false);
384 }
385 }
386
387 // Handle Select All checkbox
388 $selectAll.on('change', function() {
389 const isChecked = $(this).is(':checked');
390
391 $contentList.find('.mxchat-kb-content-item input[type="checkbox"]').each(function() {
392 const $checkbox = $(this);
393 const $item = $checkbox.closest('.mxchat-kb-content-item');
394 const itemId = parseInt($item.data('id'));
395
396 $checkbox.prop('checked', isChecked);
397
398 if (isChecked) {
399 selectedItems.add(itemId);
400 } else {
401 selectedItems.delete(itemId);
402 }
403 });
404
405 updateSelection();
406 });
407
408 // Handle search input
409 let searchTimer;
410 $searchInput.on('keyup', function() {
411 clearTimeout(searchTimer);
412 searchTimer = setTimeout(function() {
413 currentPage = 1; // Reset to first page on new search
414 loadContent();
415 }, 500);
416 });
417
418 // Handle filter changes
419 $typeFilter.add($statusFilter).add($processedFilter).on('change', function() {
420 currentPage = 1; // Reset to first page on filter change
421 selectedItems.clear(); // Clear selection when filter changes
422 loadContent();
423 });
424
425 // Process selected content
426 $processButton.on('click', function() {
427 if (selectedItems.size === 0) {
428 return;
429 }
430
431 const $button = $(this);
432 $button.prop('disabled', true);
433
434 // Update button text based on mode
435 if ($button.hasClass('update-mode')) {
436 $button.text('Updating...');
437 } else if ($button.hasClass('mixed-mode')) {
438 $button.text('Processing/Updating...');
439 } else {
440 $button.text('Processing...');
441 }
442
443 // Convert selected items to array
444 const selectedPostIds = Array.from(selectedItems);
445 const totalToProcess = selectedPostIds.length;
446 let processed = 0;
447 let updated = 0;
448 let failed = 0;
449 const results = {
450 success: [],
451 updated: [],
452 failed: []
453 };
454
455 // UPDATED: Get current bot_id for WordPress content processing
456 const currentBotId = $('#mxchat-bot-selector').val();
457
458 // Flag to track if processing should be aborted
459 let abortProcessing = false;
460 let currentXHR = null;
461
462 // Create a modal to show progress with stop button
463 const $progressModal = $('<div class="mxchat-kb-processing-overlay">' +
464 '<div class="mxchat-kb-processing-content">' +
465 '<h3>Processing Content</h3>' +
466 '<p class="mxchat-kb-processing-status">Processing 1 of ' + totalToProcess + '...</p>' +
467 '<div class="mxchat-kb-progress-bar"><div class="mxchat-kb-progress-fill" style="width: 0%"></div></div>' +
468 '<p class="mxchat-kb-current-item"></p>' +
469 '<button type="button" class="mxchat-kb-stop-processing mxch-btn mxch-btn-secondary" style="margin-top: 15px;">' +
470 '<span class="dashicons dashicons-controls-pause" style="margin-right: 5px;"></span>Stop Processing</button>' +
471 '</div>' +
472 '</div>');
473
474 $('body').append($progressModal);
475
476 // Handle stop button click
477 $progressModal.find('.mxchat-kb-stop-processing').on('click', function() {
478 abortProcessing = true;
479 $(this).prop('disabled', true).html('<span class="dashicons dashicons-update spin" style="margin-right: 5px;"></span>Stopping...');
480 if (currentXHR) {
481 currentXHR.abort();
482 }
483 });
484
485 // Process posts one by one
486 function processNext(index) {
487 // Check if processing was aborted
488 if (abortProcessing) {
489 finishProcessing(true); // Pass true to indicate abort
490 return;
491 }
492
493 if (index >= selectedPostIds.length) {
494 // All done
495 finishProcessing();
496 return;
497 }
498
499 const postId = selectedPostIds[index];
500 const percent = Math.round((index / totalToProcess) * 100);
501 const item = allItems.find(item => item.id === postId);
502 const isUpdate = item && item.already_processed;
503
504 // Update progress UI
505 $progressModal.find('.mxchat-kb-processing-status')
506 .text((isUpdate ? 'Updating' : 'Processing') + ' ' + (index + 1) + ' of ' + totalToProcess + '...');
507 $progressModal.find('.mxchat-kb-progress-fill').css('width', percent + '%');
508
509 // UPDATED: Prepare AJAX data with bot_id
510 const extractAcfPdfs = $acfPdfExtractCheckbox.prop('checked') ? 1 : 0;
511 const ajaxData = {
512 action: 'mxchat_process_selected_content',
513 nonce: mxchatSelector.nonce,
514 post_ids: [postId],
515 is_update: isUpdate,
516 extract_acf_pdfs: extractAcfPdfs
517 };
518
519 // Add bot_id if multi-bot is active and not default
520 if (currentBotId && currentBotId !== 'default') {
521 ajaxData.bot_id = currentBotId;
522 }
523
524 // Make AJAX request for this post (store reference for potential abort)
525 currentXHR = $.ajax({
526 url: mxchatSelector.ajaxurl,
527 method: 'POST',
528 data: ajaxData,
529 dataType: 'json',
530 success: function(response) {
531 if (response.success) {
532 if (isUpdate) {
533 updated++;
534 results.updated.push({
535 id: postId,
536 title: response.data.title || ('ID: ' + postId)
537 });
538 } else {
539 processed++;
540 results.success.push({
541 id: postId,
542 title: response.data.title || ('ID: ' + postId)
543 });
544 }
545
546 let successText = 'Successfully ' + (isUpdate ? 'updated' : 'processed') + ': ' + response.data.title;
547 const pdfCount = parseInt(response.data.pdf_extracted_count, 10) || 0;
548 if (pdfCount > 0) {
549 const suffixTpl = (mxchatSelector.i18n && mxchatSelector.i18n.pdfExtractedSuffix) || ' (%d PDF(s) extracted)';
550 successText += suffixTpl.replace('%d', pdfCount);
551 }
552 $progressModal.find('.mxchat-kb-current-item').text(successText);
553 } else {
554 failed++;
555 results.failed.push({
556 id: postId,
557 error: response.data || 'Unknown error'
558 });
559
560 $progressModal.find('.mxchat-kb-current-item')
561 .text('Failed to ' + (isUpdate ? 'update' : 'process') + ' ID: ' + postId);
562 }
563
564 // Process next post
565 setTimeout(function() {
566 processNext(index + 1);
567 }, 500); // Small delay between requests
568 },
569 error: function(xhr, status, error) {
570 failed++;
571 results.failed.push({
572 id: postId,
573 error: error || 'Server error'
574 });
575
576 $progressModal.find('.mxchat-kb-current-item')
577 .text('Error ' + (isUpdate ? 'updating' : 'processing') + ' ID: ' + postId);
578
579 // Process next post
580 setTimeout(function() {
581 processNext(index + 1);
582 }, 500);
583 }
584 });
585 }
586
587 // Function to finish processing and show results
588 function finishProcessing(wasAborted) {
589 // Remove progress modal
590 $progressModal.remove();
591
592 // Determine notification type based on results
593 let notificationClass = 'success';
594 if (wasAborted) {
595 notificationClass = processed > 0 || updated > 0 ? 'warning' : 'info';
596 } else if (failed > 0) {
597 notificationClass = processed > 0 || updated > 0 ? 'warning' : 'error';
598 }
599
600 // Create summary message
601 let resultHTML = '<div class="mxchat-kb-notification ' + notificationClass + '">' +
602 '<h4>';
603
604 if (wasAborted) {
605 resultHTML += 'Processing stopped. ';
606 if (processed > 0 || updated > 0) {
607 resultHTML += 'Completed ' + (processed + updated) + ' of ' + totalToProcess + ' items before stopping';
608 } else {
609 resultHTML += 'No items were processed before stopping';
610 }
611 } else if (processed > 0 && updated > 0) {
612 resultHTML += 'Processed ' + processed + ' new items and updated ' + updated + ' existing items';
613 } else if (processed > 0) {
614 resultHTML += 'Processed ' + processed + ' items successfully';
615 } else if (updated > 0) {
616 resultHTML += 'Updated ' + updated + ' items successfully';
617 } else {
618 resultHTML += 'No items were processed successfully';
619 }
620
621 if (failed > 0 && !wasAborted) {
622 resultHTML += ' with ' + failed + ' failures';
623 }
624
625 resultHTML += '</h4>';
626
627 // Add details if there were failures
628 if (failed > 0) {
629 resultHTML += '<div class="mxchat-kb-results-details">';
630 resultHTML += '<h5>Failed Items:</h5><ul>';
631
632 results.failed.forEach(function(item) {
633 resultHTML += '<li><strong>ID: ' + item.id + '</strong>: ' + item.error + '</li>';
634 });
635
636 resultHTML += '</ul></div>';
637 }
638
639 resultHTML += '</div>';
640
641 // Show results in modal
642 $modal.find('.mxchat-kb-modal-content').prepend($(resultHTML));
643
644 // Clear selection
645 selectedItems.clear();
646 updateSelection();
647
648 // Enable button
649 $button.prop('disabled', false)
650 .text('Process Selected Content')
651 .removeClass('update-mode mixed-mode');
652 $('.mxchat-kb-selected-count').text('(0)');
653
654 // Only reload if there were successful operations
655 if (processed > 0 || updated > 0) {
656 // Refresh the knowledge base table with properly grouped entries
657 if (typeof window.refreshKnowledgeBaseTable === 'function') {
658 window.refreshKnowledgeBaseTable();
659 }
660
661 // Reload content list to update "already processed" status
662 setTimeout(function() {
663 loadContent();
664 }, 1000);
665 }
666 }
667
668 // Start processing the first post
669 processNext(0);
670 });
671
672 // Initialize - Set WordPress as the active option by default
673 $('.mxchat-import-box[data-option="wordpress"]').addClass('active');
674 });
675
676 // Navigation functionality for Knowledge Base page
677 jQuery(document).ready(function($) {
678
679 // Hook into the new navigation system using .mxch-nav-link
680 $(document).on('click', '.mxch-nav-link[data-target], .mxch-mobile-nav-link[data-target]', function() {
681 var target = $(this).data('target');
682
683 // Initialize Pinecone functionality when Pinecone section is activated
684 if (target === 'pinecone') {
685 setTimeout(function() {
686 if (typeof initPineconeFeatures === 'function') {
687 initPineconeFeatures();
688 }
689 }, 100);
690 }
691
692 // Initialize OpenAI Vector Store functionality when Vector Store section is activated
693 if (target === 'openai-vectorstore') {
694 setTimeout(function() {
695 if (typeof initVectorStoreFeatures === 'function') {
696 initVectorStoreFeatures();
697 }
698 }, 100);
699 }
700
701 // Check if Pinecone was changed and we're going to import section
702 if (target === 'import' && sessionStorage.getItem('mxchat_pinecone_changed') === 'true') {
703 sessionStorage.removeItem('mxchat_pinecone_changed');
704
705 // Show refresh notice
706 var $knowledgeCard = $('#import .mxch-card').eq(1);
707 if ($knowledgeCard.length > 0 && $knowledgeCard.find('.notice-warning').length === 0) {
708 var refreshNotice = $('<div class="notice notice-warning" style="margin: 15px 0; padding: 10px 15px;">' +
709 '<p style="margin: 0;">' +
710 '<span class="dashicons dashicons-info" style="color: #f0ad4e; margin-right: 5px;"></span>' +
711 'Database settings have changed. ' +
712 '<a href="#" onclick="location.reload(); return false;" style="font-weight: bold;">Click here to refresh</a> to see the updated knowledge base.' +
713 '</p></div>');
714
715 $knowledgeCard.prepend(refreshNotice);
716 }
717 }
718 });
719
720 // Also check on page load if we're already on one of these sections
721 setTimeout(function() {
722 if ($('#pinecone').is(':visible') || $('#pinecone.active').length > 0) {
723 if (typeof initPineconeFeatures === 'function') {
724 initPineconeFeatures();
725 }
726 }
727
728 if ($('#openai-vectorstore').is(':visible') || $('#openai-vectorstore.active').length > 0) {
729 if (typeof initVectorStoreFeatures === 'function') {
730 initVectorStoreFeatures();
731 }
732 }
733 }, 200);
734 });
735
736 // Pinecone and Vector Store functionality - global functions
737 var initPineconeFeatures, initVectorStoreFeatures;
738
739 (function($) {
740
741 // Helper function to update sidebar badge when integration is toggled
742 function updateSidebarBadge(section, isActive) {
743 // Find the nav item for this section (both desktop and mobile)
744 var $desktopNavItem = $('.mxch-nav-item[data-section="' + section + '"] .mxch-nav-link');
745 var $mobileNavItem = $('.mxch-mobile-nav-link[data-target="' + section + '"]');
746
747 // Remove existing badge if any
748 $desktopNavItem.find('.mxch-active-badge').remove();
749 $mobileNavItem.find('.mxch-active-badge').remove();
750
751 // Add badge if active
752 if (isActive) {
753 var badgeHtml = '<span class="mxch-nav-link-badge mxch-active-badge">Active</span>';
754 $desktopNavItem.append(badgeHtml);
755 $mobileNavItem.append(badgeHtml);
756 }
757 }
758
759 // Pinecone functionality
760 initPineconeFeatures = function() {
761 // Check for either old or new section ID
762 if ($('#pinecone').length === 0 && $('#mxchat-kb-tab-pinecone').length === 0) {
763 return;
764 }
765
766 initPineconeToggle();
767 initPineconeConnectionTest();
768 checkPineconeCompatibility();
769 };
770
771 function initPineconeToggle() {
772 // Remove any existing handlers to prevent duplicates
773 var $toggleInput = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]');
774 $toggleInput.off('change.pineconeToggle');
775
776 // Ensure the success notice exists in the settings div (add if not present)
777 // Check for both the JS-added class and any existing PHP-rendered success notice
778 var settingsDiv = $('.mxchat-pinecone-settings');
779 if (settingsDiv.length > 0 && settingsDiv.find('.mxch-notice-success').length === 0) {
780 var successNotice = $('<div class="mxch-notice mxch-notice-success mxchat-pinecone-enabled-notice" style="margin-bottom: 20px; display: none;">' +
781 '<svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>' +
782 '<span>Pinecone is enabled. All new knowledge base content will be stored in Pinecone.</span>' +
783 '</div>');
784 settingsDiv.prepend(successNotice);
785 }
786
787 // Add the toggle handler for UI only (auto-save will handle the actual saving)
788 $toggleInput.on('change.pineconeToggle', function() {
789 var $checkbox = $(this);
790 var isChecked = $checkbox.is(':checked');
791 var settingsDiv = $('.mxchat-pinecone-settings');
792 var enabledNotice = settingsDiv.find('.mxchat-pinecone-enabled-notice, .mxch-notice-success');
793
794 // Update the UI immediately
795 if (isChecked) {
796 settingsDiv.slideDown(300);
797 enabledNotice.slideDown(300);
798 } else {
799 enabledNotice.slideUp(300);
800 settingsDiv.slideUp(300);
801 }
802
803 // Update sidebar badge for Pinecone
804 updateSidebarBadge('pinecone', isChecked);
805 });
806
807 // Set initial state based on current checkbox value
808 var currentToggle = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]');
809 if (currentToggle.length > 0) {
810 var settingsDiv = $('.mxchat-pinecone-settings');
811 var enabledNotice = settingsDiv.find('.mxchat-pinecone-enabled-notice, .mxch-notice-success');
812 if (currentToggle.is(':checked')) {
813 settingsDiv.show();
814 enabledNotice.show();
815 } else {
816 settingsDiv.hide();
817 enabledNotice.hide();
818 }
819 }
820 }
821
822 function initPineconeConnectionTest() {
823 $('#test-pinecone-connection').off('click.pinecone');
824
825 $('#test-pinecone-connection').on('click.pinecone', function() {
826 var button = $(this);
827 var resultDiv = $('#connection-test-result');
828
829 var apiKey = $('#mxchat_pinecone_api_key').val();
830 var host = $('#mxchat_pinecone_host').val();
831 var index = $('#mxchat_pinecone_index').val();
832
833 if (!apiKey || !host || !index) {
834 resultDiv.html('<div class="notice notice-error"><p>Please fill in all required fields first.</p></div>').show();
835 return;
836 }
837
838 button.prop('disabled', true).text('Testing...');
839 resultDiv.hide();
840
841 var ajaxUrl = (typeof mxchatPromptsAdmin !== 'undefined') ? mxchatPromptsAdmin.ajax_url : ajaxurl;
842 var nonce = (typeof mxchatPromptsAdmin !== 'undefined') ? mxchatPromptsAdmin.prompts_setting_nonce :
843 (typeof mxchatAdmin !== 'undefined') ? mxchatAdmin.setting_nonce : '';
844
845 $.ajax({
846 url: ajaxUrl,
847 type: 'POST',
848 data: {
849 action: 'mxchat_test_pinecone_connection',
850 _ajax_nonce: nonce,
851 api_key: apiKey,
852 host: host,
853 index_name: index
854 },
855 success: function(response) {
856 if (response.success) {
857 resultDiv.html('<div class="notice notice-success"><p><span class="dashicons dashicons-yes-alt"></span> ' + response.data.message + '</p></div>');
858 } else {
859 resultDiv.html('<div class="notice notice-error"><p><span class="dashicons dashicons-warning"></span> ' + response.data.message + '</p></div>');
860 }
861 resultDiv.show();
862 },
863 error: function() {
864 resultDiv.html('<div class="notice notice-error"><p>Connection test failed. Please check your settings.</p></div>').show();
865 },
866 complete: function() {
867 button.prop('disabled', false).text('Test Connection');
868 }
869 });
870 });
871 }
872
873 function checkPineconeCompatibility() {
874 if ($('.mxchat-pinecone-compatibility-notice').length > 0) {
875 return;
876 }
877
878 var hasOldAddon = $('body').hasClass('mxchat-pinecone-addon-active') ||
879 $('.pcm-card').length > 0;
880
881 if (hasOldAddon) {
882 var compatibilityNotice = $(`
883 <div class="notice notice-info mxchat-pinecone-compatibility-notice">
884 <p><strong>Pinecone Integration Notice:</strong> We've detected you have the Pinecone add-on installed.
885 Pinecone functionality is now built into the core plugin. You can safely deactivate the separate
886 Pinecone add-on after confirming your settings are migrated below.</p>
887 </div>
888 `);
889
890 $('#mxchat-kb-tab-pinecone .mxchat-card').prepend(compatibilityNotice);
891
892 migratePineconeSettings();
893 }
894 }
895
896 function migratePineconeSettings() {
897 if (typeof ajaxurl !== 'undefined') {
898 $.ajax({
899 url: ajaxurl,
900 type: 'POST',
901 data: {
902 action: 'mxchat_migrate_pinecone_settings',
903 _ajax_nonce: (typeof mxchatAdmin !== 'undefined') ? mxchatAdmin.setting_nonce : ''
904 },
905 success: function(response) {
906 if (response.success && response.data.migrated) {
907 location.reload();
908 }
909 },
910 error: function() {
911 //console.log('Pinecone settings migration not available');
912 }
913 });
914 }
915 }
916
917 // ============================================
918 // OpenAI Vector Store functionality
919 // ============================================
920 initVectorStoreFeatures = function() {
921 if ($('#openai-vectorstore').length === 0) {
922 return;
923 }
924
925 initVectorStoreToggle();
926 };
927
928 function initVectorStoreToggle() {
929 // Remove any existing handlers to prevent duplicates
930 var $toggleInput = $('input[name="mxchat_openai_vectorstore_options[mxchat_use_openai_vectorstore]"]');
931 $toggleInput.off('change.vectorstoreToggle');
932
933 // Ensure the success notice exists in the settings div (add if not present)
934 // Check for both the JS-added class and any existing PHP-rendered success notice
935 var settingsDiv = $('.mxchat-vectorstore-settings');
936 if (settingsDiv.length > 0 && settingsDiv.find('.mxch-notice-success').length === 0) {
937 var successNotice = $('<div class="mxch-notice mxch-notice-success mxchat-vectorstore-enabled-notice" style="margin-bottom: 20px; display: none;">' +
938 '<svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>' +
939 '<span>OpenAI Vector Store is enabled. Queries will search your Vector Store for relevant content.</span>' +
940 '</div>');
941 settingsDiv.prepend(successNotice);
942 }
943
944 // Add the toggle handler for UI only (form submit will handle the actual saving)
945 $toggleInput.on('change.vectorstoreToggle', function() {
946 var $checkbox = $(this);
947 var isChecked = $checkbox.is(':checked');
948 var settingsDiv = $('.mxchat-vectorstore-settings');
949 var enabledNotice = settingsDiv.find('.mxchat-vectorstore-enabled-notice, .mxch-notice-success');
950
951 // Update the UI immediately
952 if (isChecked) {
953 settingsDiv.slideDown(300);
954 enabledNotice.slideDown(300);
955 } else {
956 enabledNotice.slideUp(300);
957 settingsDiv.slideUp(300);
958 }
959
960 // Update sidebar badge for OpenAI Vector Store
961 updateSidebarBadge('openai-vectorstore', isChecked);
962 });
963
964 // Set initial state based on current checkbox value
965 var currentToggle = $('input[name="mxchat_openai_vectorstore_options[mxchat_use_openai_vectorstore]"]');
966 if (currentToggle.length > 0) {
967 var settingsDiv = $('.mxchat-vectorstore-settings');
968 var enabledNotice = settingsDiv.find('.mxchat-vectorstore-enabled-notice, .mxch-notice-success');
969 if (currentToggle.is(':checked')) {
970 settingsDiv.show();
971 enabledNotice.show();
972 } else {
973 settingsDiv.hide();
974 enabledNotice.hide();
975 }
976 }
977 }
978
979 })(jQuery);