PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.14
MxChat – AI Chatbot & Content Generation for WordPress v3.2.14
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.14, at js/content-selector.js

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