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

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