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

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