PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.22
MxChat – AI Chatbot & Content Generation for WordPress v3.2.22
3.2.25 3.2.24 3.2.23 3.2.22 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 All 156 releases
mxchat-basic / js / content-selector.js

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

1,238 lines 54.3 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_label || item.type}</span>
272 <span class="mxchat-kb-content-date">${item.date}</span>
273 <span class="mxchat-kb-content-words">${item.size_label ? item.size_label : 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 updateMediaNote();
451 loadContent();
452 });
453
454 // The Media note explains what that list deliberately leaves out; it is
455 // meaningless next to any other type, so it only exists while Media is picked.
456 function updateMediaNote() {
457 const $note = $('#mxchat-kb-media-note');
458 if (!$note.length) {
459 return;
460 }
461 $note.prop('hidden', $typeFilter.val() !== 'attachment');
462 }
463 updateMediaNote();
464
465 // Process selected content
466 $processButton.on('click', function() {
467 if (selectedItems.size === 0) {
468 return;
469 }
470
471 const $button = $(this);
472 $button.prop('disabled', true);
473
474 // Update button text based on mode
475 if ($button.hasClass('update-mode')) {
476 $button.text('Updating...');
477 } else if ($button.hasClass('mixed-mode')) {
478 $button.text('Processing/Updating...');
479 } else {
480 $button.text('Processing...');
481 }
482
483 // Convert selected items to array
484 const selectedPostIds = Array.from(selectedItems);
485 const totalToProcess = selectedPostIds.length;
486 let processed = 0;
487 let updated = 0;
488 let failed = 0;
489 const results = {
490 success: [],
491 updated: [],
492 failed: []
493 };
494
495 // UPDATED: Get current bot_id for WordPress content processing
496 const currentBotId = $('#mxchat-bot-selector').val();
497
498 // Flag to track if processing should be aborted
499 let abortProcessing = false;
500 let currentXHR = null;
501
502 // Create a modal to show progress with stop button
503 const $progressModal = $('<div class="mxchat-kb-processing-overlay">' +
504 '<div class="mxchat-kb-processing-content">' +
505 '<h3>Processing Content</h3>' +
506 '<p class="mxchat-kb-processing-status">Processing 1 of ' + totalToProcess + '...</p>' +
507 '<div class="mxchat-kb-progress-bar"><div class="mxchat-kb-progress-fill" style="width: 0%"></div></div>' +
508 '<p class="mxchat-kb-current-item"></p>' +
509 '<button type="button" class="mxchat-kb-stop-processing mxch-btn mxch-btn-secondary" style="margin-top: 15px;">' +
510 '<span class="dashicons dashicons-controls-pause" style="margin-right: 5px;"></span>Stop Processing</button>' +
511 '</div>' +
512 '</div>');
513
514 $('body').append($progressModal);
515
516 // Handle stop button click
517 $progressModal.find('.mxchat-kb-stop-processing').on('click', function() {
518 abortProcessing = true;
519 $(this).prop('disabled', true).html('<span class="dashicons dashicons-update spin" style="margin-right: 5px;"></span>Stopping...');
520 if (currentXHR) {
521 currentXHR.abort();
522 }
523 });
524
525 // Process posts one by one
526 function processNext(index) {
527 // Check if processing was aborted
528 if (abortProcessing) {
529 finishProcessing(true); // Pass true to indicate abort
530 return;
531 }
532
533 if (index >= selectedPostIds.length) {
534 // All done
535 finishProcessing();
536 return;
537 }
538
539 const postId = selectedPostIds[index];
540 const percent = Math.round((index / totalToProcess) * 100);
541 const item = allItems.find(item => item.id === postId);
542 const isUpdate = item && item.already_processed;
543
544 // Update progress UI
545 $progressModal.find('.mxchat-kb-processing-status')
546 .text((isUpdate ? 'Updating' : 'Processing') + ' ' + (index + 1) + ' of ' + totalToProcess + '...');
547 $progressModal.find('.mxchat-kb-progress-fill').css('width', percent + '%');
548
549 // UPDATED: Prepare AJAX data with bot_id
550 const ajaxData = {
551 action: 'mxchat_process_selected_content',
552 nonce: mxchatSelector.nonce,
553 post_ids: [postId],
554 is_update: isUpdate
555 };
556
557 // Add bot_id if multi-bot is active and not default
558 if (currentBotId && currentBotId !== 'default') {
559 ajaxData.bot_id = currentBotId;
560 }
561
562 // Make AJAX request for this post (store reference for potential abort)
563 currentXHR = $.ajax({
564 url: mxchatSelector.ajaxurl,
565 method: 'POST',
566 data: ajaxData,
567 dataType: 'json',
568 success: function(response) {
569 if (response.success) {
570 if (isUpdate) {
571 updated++;
572 results.updated.push({
573 id: postId,
574 title: response.data.title || ('ID: ' + postId)
575 });
576 } else {
577 processed++;
578 results.success.push({
579 id: postId,
580 title: response.data.title || ('ID: ' + postId)
581 });
582 }
583
584 let successText = 'Successfully ' + (isUpdate ? 'updated' : 'processed') + ': ' + response.data.title;
585 const pdfCount = parseInt(response.data.pdf_extracted_count, 10) || 0;
586 if (pdfCount > 0) {
587 const suffixTpl = (mxchatSelector.i18n && mxchatSelector.i18n.pdfExtractedSuffix) || ' (%d PDF(s) extracted)';
588 successText += suffixTpl.replace('%d', pdfCount);
589 }
590 $progressModal.find('.mxchat-kb-current-item').text(successText);
591 } else {
592 failed++;
593 results.failed.push({
594 id: postId,
595 error: response.data || 'Unknown error'
596 });
597
598 $progressModal.find('.mxchat-kb-current-item')
599 .text('Failed to ' + (isUpdate ? 'update' : 'process') + ' ID: ' + postId);
600 }
601
602 // Process next post
603 setTimeout(function() {
604 processNext(index + 1);
605 }, 500); // Small delay between requests
606 },
607 error: function(xhr, status, error) {
608 failed++;
609 results.failed.push({
610 id: postId,
611 error: error || 'Server error'
612 });
613
614 $progressModal.find('.mxchat-kb-current-item')
615 .text('Error ' + (isUpdate ? 'updating' : 'processing') + ' ID: ' + postId);
616
617 // Process next post
618 setTimeout(function() {
619 processNext(index + 1);
620 }, 500);
621 }
622 });
623 }
624
625 // Function to finish processing and show results
626 function finishProcessing(wasAborted) {
627 // Remove progress modal
628 $progressModal.remove();
629
630 // Determine notification type based on results
631 let notificationClass = 'success';
632 if (wasAborted) {
633 notificationClass = processed > 0 || updated > 0 ? 'warning' : 'info';
634 } else if (failed > 0) {
635 notificationClass = processed > 0 || updated > 0 ? 'warning' : 'error';
636 }
637
638 // Create summary message
639 let resultHTML = '<div class="mxchat-kb-notification ' + notificationClass + '">' +
640 '<h4>';
641
642 if (wasAborted) {
643 resultHTML += 'Processing stopped. ';
644 if (processed > 0 || updated > 0) {
645 resultHTML += 'Completed ' + (processed + updated) + ' of ' + totalToProcess + ' items before stopping';
646 } else {
647 resultHTML += 'No items were processed before stopping';
648 }
649 } else if (processed > 0 && updated > 0) {
650 resultHTML += 'Processed ' + processed + ' new items and updated ' + updated + ' existing items';
651 } else if (processed > 0) {
652 resultHTML += 'Processed ' + processed + ' items successfully';
653 } else if (updated > 0) {
654 resultHTML += 'Updated ' + updated + ' items successfully';
655 } else {
656 resultHTML += 'No items were processed successfully';
657 }
658
659 if (failed > 0 && !wasAborted) {
660 resultHTML += ' with ' + failed + ' failures';
661 }
662
663 resultHTML += '</h4>';
664
665 // Add details if there were failures
666 if (failed > 0) {
667 resultHTML += '<div class="mxchat-kb-results-details">';
668 resultHTML += '<h5>Failed Items:</h5><ul>';
669
670 results.failed.forEach(function(item) {
671 resultHTML += '<li><strong>ID: ' + item.id + '</strong>: ' + item.error + '</li>';
672 });
673
674 resultHTML += '</ul></div>';
675 }
676
677 resultHTML += '</div>';
678
679 // Show results in modal
680 $modal.find('.mxchat-kb-modal-content').prepend($(resultHTML));
681
682 // Clear selection
683 selectedItems.clear();
684 updateSelection();
685
686 // Enable button
687 $button.prop('disabled', false)
688 .text('Process Selected Content')
689 .removeClass('update-mode mixed-mode');
690 $('.mxchat-kb-selected-count').text('(0)');
691
692 // Only reload if there were successful operations
693 if (processed > 0 || updated > 0) {
694 // Refresh the knowledge base table with properly grouped entries
695 if (typeof window.refreshKnowledgeBaseTable === 'function') {
696 window.refreshKnowledgeBaseTable();
697 }
698
699 // Reload content list to update "already processed" status
700 setTimeout(function() {
701 loadContent();
702 }, 1000);
703 }
704 }
705
706 // Start processing the first post
707 processNext(0);
708 });
709
710 // Initialize - Set WordPress as the active option by default
711 $('.mxchat-import-box[data-option="wordpress"]').addClass('active');
712 });
713
714 // Navigation functionality for Knowledge Base page
715 jQuery(document).ready(function($) {
716
717 // Hook into the new navigation system using .mxch-nav-link
718 $(document).on('click', '.mxch-nav-link[data-target], .mxch-mobile-nav-link[data-target]', function() {
719 var target = $(this).data('target');
720
721 // Initialize Pinecone functionality when Pinecone section is activated
722 if (target === 'pinecone') {
723 setTimeout(function() {
724 if (typeof initPineconeFeatures === 'function') {
725 initPineconeFeatures();
726 }
727 }, 100);
728 }
729
730 // Initialize OpenAI Vector Store functionality when Vector Store section is activated
731 if (target === 'openai-vectorstore') {
732 setTimeout(function() {
733 if (typeof initVectorStoreFeatures === 'function') {
734 initVectorStoreFeatures();
735 }
736 }, 100);
737 }
738
739 // Check if Pinecone was changed and we're going to import section
740 if (target === 'import' && sessionStorage.getItem('mxchat_pinecone_changed') === 'true') {
741 sessionStorage.removeItem('mxchat_pinecone_changed');
742
743 // Show refresh notice
744 var $knowledgeCard = $('#import .mxch-card').eq(1);
745 if ($knowledgeCard.length > 0 && $knowledgeCard.find('.notice-warning').length === 0) {
746 var refreshNotice = $('<div class="notice notice-warning" style="margin: 15px 0; padding: 10px 15px;">' +
747 '<p style="margin: 0;">' +
748 '<span class="dashicons dashicons-info" style="color: #f0ad4e; margin-right: 5px;"></span>' +
749 'Database settings have changed. ' +
750 '<a href="#" onclick="location.reload(); return false;" style="font-weight: bold;">Click here to refresh</a> to see the updated knowledge base.' +
751 '</p></div>');
752
753 $knowledgeCard.prepend(refreshNotice);
754 }
755 }
756 });
757
758 // Also check on page load if we're already on one of these sections
759 setTimeout(function() {
760 if ($('#pinecone').is(':visible') || $('#pinecone.active').length > 0) {
761 if (typeof initPineconeFeatures === 'function') {
762 initPineconeFeatures();
763 }
764 }
765
766 if ($('#openai-vectorstore').is(':visible') || $('#openai-vectorstore.active').length > 0) {
767 if (typeof initVectorStoreFeatures === 'function') {
768 initVectorStoreFeatures();
769 }
770 }
771 }, 200);
772 });
773
774 // Pinecone and Vector Store functionality - global functions
775 var initPineconeFeatures, initVectorStoreFeatures;
776
777 (function($) {
778
779 // Helper function to update sidebar badge when integration is toggled
780 function updateSidebarBadge(section, isActive) {
781 // Find the nav item for this section (both desktop and mobile)
782 var $desktopNavItem = $('.mxch-nav-item[data-section="' + section + '"] .mxch-nav-link');
783 var $mobileNavItem = $('.mxch-mobile-nav-link[data-target="' + section + '"]');
784
785 // Remove existing badge if any
786 $desktopNavItem.find('.mxch-active-badge').remove();
787 $mobileNavItem.find('.mxch-active-badge').remove();
788
789 // Add badge if active
790 if (isActive) {
791 var badgeHtml = '<span class="mxch-nav-link-badge mxch-active-badge">Active</span>';
792 $desktopNavItem.append(badgeHtml);
793 $mobileNavItem.append(badgeHtml);
794 }
795 }
796
797 // Pinecone functionality
798 initPineconeFeatures = function() {
799 // Check for either old or new section ID
800 if ($('#pinecone').length === 0 && $('#mxchat-kb-tab-pinecone').length === 0) {
801 return;
802 }
803
804 initPineconeToggle();
805 initPineconeIndexType();
806 initPineconeConnectionTest();
807 checkPineconeCompatibility();
808 };
809
810 // Index type (plan 362c31): the document-index controls on the Pinecone
811 // card — Create index for me, Check index, Migrate, Delete old index —
812 // plus the save gate (a document host must be checked before it saves).
813 function initPineconeIndexType() {
814 var $type = $('input.mxchat-pinecone-index-type');
815 if ($type.length === 0) {
816 return;
817 }
818 var $docs = $('.mxchat-pinecone-docs-settings');
819 var $form = $type.closest('form');
820 var $submit = $form.find('input[type="submit"], button[type="submit"]');
821 var $result = $('#mxchat-pinecone-docs-result');
822 var stateEl = document.getElementById('mxchat-pinecone-docs-state');
823 var state = {};
824 try { state = stateEl ? JSON.parse(stateEl.textContent || '{}') : {}; } catch (e) { state = {}; }
825 var verifiedHost = String(state.verified_host || '').toLowerCase();
826 var ajaxUrl = (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.ajax_url) ? mxchatAdmin.ajax_url : ajaxurl;
827 var nonce = (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.settings_nonce) ? mxchatAdmin.settings_nonce :
828 ((typeof mxchatPromptsAdmin !== 'undefined' && mxchatPromptsAdmin.prompts_setting_nonce) ? mxchatPromptsAdmin.prompts_setting_nonce : '');
829
830 function escapeText(text) { return $('<div>').text(String(text == null ? '' : text)).html(); }
831 function notice(kind, text) {
832 $result.html('<div class="mxch-notice mxch-notice-' + kind + '" style="margin: 12px 0;"><span>' + escapeText(text) + '</span></div>').show();
833 }
834 function setStatus(ok, text) {
835 $('#mxchat-pinecone-docs-status').removeClass('mxch-notice-success mxch-notice-warning').addClass(ok ? 'mxch-notice-success' : 'mxch-notice-warning');
836 $('#mxchat-pinecone-docs-status-text').text(text);
837 }
838 function currentType() { return $type.filter(':checked').val() || 'vector'; }
839 function hostValue() {
840 return $.trim($('#mxchat_pinecone_host').val() || '').replace(/^https?:\/\//i, '').replace(/\/+$/, '').toLowerCase();
841 }
842 function refreshGate() {
843 var isDoc = currentType() === 'document';
844 $docs.toggle(isDoc);
845 var blocked = isDoc && (hostValue() === '' || hostValue() !== verifiedHost);
846 $submit.prop('disabled', blocked);
847 $('#mxchat-pinecone-docs-gate').toggle(blocked);
848 }
849 $type.off('change.pineconeDocs').on('change.pineconeDocs', refreshGate);
850 $('#mxchat_pinecone_host').off('input.pineconeDocs change.pineconeDocs').on('input.pineconeDocs change.pineconeDocs', refreshGate);
851 refreshGate();
852
853 function post(data) {
854 data.nonce = nonce;
855 return $.post(ajaxUrl, data);
856 }
857 function failMessage(response, fallback) {
858 if (response && response.data) {
859 if (typeof response.data === 'string') { return response.data; }
860 if (response.data.message) { return response.data.message; }
861 }
862 return fallback;
863 }
864
865 // ---- Create index for me
866 $('#mxchat-pinecone-docs-create').off('click.pineconeDocs').on('click.pineconeDocs', function () {
867 var $btn = $(this);
868 var name = $.trim($('#mxchat_pinecone_index').val() || '');
869 if (!name) {
870 notice('error', 'Enter an Index Name first — the document index is created under that name.');
871 return;
872 }
873 $btn.prop('disabled', true).text('Creating…');
874 $result.hide();
875 post({
876 action: 'mxchat_pinecone_docs_create_index',
877 index_name: name,
878 api_key: $('#mxchat_pinecone_api_key').val(),
879 cloud: $('#mxchat_pinecone_docs_cloud').val(),
880 region: $('#mxchat_pinecone_docs_region').val(),
881 language: $('#mxchat_pinecone_docs_language').val()
882 }).done(function (response) {
883 if (response && response.success) {
884 $('#mxchat_pinecone_host').val(response.data.host);
885 $('#mxchat_pinecone_index').val(response.data.name);
886 if (response.data.vector_host && !$.trim($('#mxchat_pinecone_vector_host').val() || '')) {
887 $('#mxchat_pinecone_vector_host').val(response.data.vector_host);
888 }
889 verifiedHost = String(response.data.host || '').toLowerCase();
890 setStatus(true, 'Document index checked: ' + response.data.host + ' (' + response.data.dimension + ' dimensions).');
891 notice(response.data.ready ? 'success' : 'info', response.data.message);
892 refreshGate();
893 } else {
894 notice('error', failMessage(response, 'Could not create the index.'));
895 }
896 }).fail(function () {
897 notice('error', 'Could not reach the server to create the index.');
898 }).always(function () {
899 $btn.prop('disabled', false).text('Create index for me');
900 });
901 });
902
903 // ---- Check index
904 $('#mxchat-pinecone-docs-check').off('click.pineconeDocs').on('click.pineconeDocs', function () {
905 var $btn = $(this);
906 var host = hostValue();
907 if (!host) {
908 notice('error', 'Enter the Pinecone Host of the document index first.');
909 return;
910 }
911 $btn.prop('disabled', true).text('Checking…');
912 $result.hide();
913 post({
914 action: 'mxchat_pinecone_docs_check_index',
915 host: host,
916 api_key: $('#mxchat_pinecone_api_key').val()
917 }).done(function (response) {
918 if (response && response.success) {
919 verifiedHost = String(response.data.host || host).toLowerCase();
920 if (response.data.name) { $('#mxchat_pinecone_index').val(response.data.name); }
921 if (response.data.vector_host && !$.trim($('#mxchat_pinecone_vector_host').val() || '')) {
922 $('#mxchat_pinecone_vector_host').val(response.data.vector_host);
923 }
924 setStatus(true, 'Document index checked: ' + response.data.host + ' (' + response.data.dimension + ' dimensions).');
925 notice('success', response.data.message);
926 } else {
927 setStatus(false, 'Not checked: this host cannot be used as a document index.');
928 notice('error', failMessage(response, 'Check failed.'));
929 }
930 refreshGate();
931 }).fail(function () {
932 notice('error', 'Could not reach the server to check the index.');
933 }).always(function () {
934 $btn.prop('disabled', false).text('Check index');
935 });
936 });
937
938 // ---- Migrate (copy, batch by batch, resumable)
939 var $migrateBtn = $('#mxchat-pinecone-docs-migrate');
940 function renderMigration(st) {
941 var $progress = $('#mxchat-pinecone-docs-migrate-progress');
942 if (!st || !st.status) {
943 $progress.hide();
944 $('#mxchat-pinecone-docs-migrate-counts').text('');
945 $('#mxchat-pinecone-docs-delete-old').hide();
946 return;
947 }
948 var total = parseInt(st.total, 10) || 0;
949 var copied = parseInt(st.copied, 10) || 0;
950 var pct = total > 0 ? Math.min(100, Math.round(copied * 100 / total)) : (st.status === 'done' ? 100 : 0);
951 var suffix = st.status === 'done' ? ' — done' : (st.status === 'source_deleted' ? ' — old index deleted' : (st.status === 'running' ? ' — copying…' : ''));
952 $progress.show().find('.mxch-progress-bar-fill').css('width', pct + '%');
953 $progress.find('.mxch-progress-label').text(copied + ' of ' + total + ' records copied (' + pct + '%)' + suffix);
954 $('#mxchat-pinecone-docs-migrate-counts').text(st.source_name ? ('Source: ' + st.source_name + ' (' + total + ' records)') : '');
955 $('#mxchat-pinecone-docs-delete-old').toggle(st.status === 'done' && total > 0 && copied >= total);
956 }
957 function migrateStep() {
958 post({ action: 'mxchat_pinecone_docs_migrate', mode: 'step' }).done(function (response) {
959 if (response && response.success) {
960 renderMigration(response.data.state);
961 if (response.data.state && response.data.state.status === 'running') {
962 setTimeout(migrateStep, 150);
963 } else {
964 $migrateBtn.prop('disabled', false).text('Migrate');
965 notice('success', 'Copy finished: ' + response.data.state.copied + ' of ' + response.data.state.total + ' records are in the document index.');
966 }
967 } else {
968 if (response && response.data && response.data.state) { renderMigration(response.data.state); }
969 notice('error', failMessage(response, 'The copy stopped.') + ' Click Migrate to resume.');
970 $migrateBtn.prop('disabled', false).text('Migrate');
971 }
972 }).fail(function () {
973 notice('error', 'Could not reach the server. Click Migrate to resume.');
974 $migrateBtn.prop('disabled', false).text('Migrate');
975 });
976 }
977 $migrateBtn.off('click.pineconeDocs').on('click.pineconeDocs', function () {
978 var source = $.trim($('#mxchat_pinecone_vector_host').val() || '');
979 if (!source) {
980 notice('error', 'Enter the host of the vector index to copy from.');
981 return;
982 }
983 $migrateBtn.prop('disabled', true).text('Copying…');
984 $result.hide();
985 post({ action: 'mxchat_pinecone_docs_migrate', mode: 'start', source_host: source }).done(function (response) {
986 if (response && response.success) {
987 renderMigration(response.data.state);
988 if (response.data.state && response.data.state.status === 'running') {
989 migrateStep();
990 } else {
991 $migrateBtn.prop('disabled', false).text('Migrate');
992 }
993 } else {
994 notice('error', failMessage(response, 'The copy could not start.'));
995 $migrateBtn.prop('disabled', false).text('Migrate');
996 }
997 }).fail(function () {
998 notice('error', 'Could not reach the server to start the copy.');
999 $migrateBtn.prop('disabled', false).text('Migrate');
1000 });
1001 });
1002 renderMigration(state.migration || null);
1003
1004 // ---- Delete old index (typed confirmation)
1005 $('#mxchat-pinecone-docs-delete-old-btn').off('click.pineconeDocs').on('click.pineconeDocs', function () {
1006 var $btn = $(this);
1007 var confirmName = $.trim($('#mxchat_pinecone_docs_delete_confirm').val() || '');
1008 if (!confirmName) {
1009 notice('error', 'Type the old index name to confirm.');
1010 return;
1011 }
1012 $btn.prop('disabled', true).text('Deleting…');
1013 post({ action: 'mxchat_pinecone_docs_delete_old_index', confirm_name: confirmName }).done(function (response) {
1014 if (response && response.success) {
1015 notice('success', response.data.message);
1016 $('#mxchat_pinecone_vector_host').val('');
1017 $('#mxchat_pinecone_docs_delete_confirm').val('');
1018 renderMigration(response.data.state);
1019 } else {
1020 notice('error', failMessage(response, 'The old index was not deleted.'));
1021 }
1022 }).fail(function () {
1023 notice('error', 'Could not reach the server.');
1024 }).always(function () {
1025 $btn.prop('disabled', false).text('Delete old index');
1026 });
1027 });
1028 }
1029
1030 function initPineconeToggle() {
1031 // Remove any existing handlers to prevent duplicates
1032 var $toggleInput = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]');
1033 $toggleInput.off('change.pineconeToggle');
1034
1035 // Ensure the success notice exists in the settings div (add if not present)
1036 // Check for both the JS-added class and any existing PHP-rendered success notice
1037 var settingsDiv = $('.mxchat-pinecone-settings');
1038 if (settingsDiv.length > 0 && settingsDiv.find('.mxch-notice-success').length === 0) {
1039 var successNotice = $('<div class="mxch-notice mxch-notice-success mxchat-pinecone-enabled-notice" style="margin-bottom: 20px; display: none;">' +
1040 '<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>' +
1041 '<span>Pinecone is enabled. All new knowledge base content will be stored in Pinecone.</span>' +
1042 '</div>');
1043 settingsDiv.prepend(successNotice);
1044 }
1045
1046 // Add the toggle handler for UI only (auto-save will handle the actual saving)
1047 $toggleInput.on('change.pineconeToggle', function() {
1048 var $checkbox = $(this);
1049 var isChecked = $checkbox.is(':checked');
1050 var settingsDiv = $('.mxchat-pinecone-settings');
1051 var enabledNotice = settingsDiv.find('.mxchat-pinecone-enabled-notice, .mxch-notice-success');
1052
1053 // Update the UI immediately
1054 if (isChecked) {
1055 settingsDiv.slideDown(300);
1056 enabledNotice.slideDown(300);
1057 } else {
1058 enabledNotice.slideUp(300);
1059 settingsDiv.slideUp(300);
1060 }
1061
1062 // Update sidebar badge for Pinecone
1063 updateSidebarBadge('pinecone', isChecked);
1064 });
1065
1066 // Set initial state based on current checkbox value
1067 var currentToggle = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]');
1068 if (currentToggle.length > 0) {
1069 var settingsDiv = $('.mxchat-pinecone-settings');
1070 var enabledNotice = settingsDiv.find('.mxchat-pinecone-enabled-notice, .mxch-notice-success');
1071 if (currentToggle.is(':checked')) {
1072 settingsDiv.show();
1073 enabledNotice.show();
1074 } else {
1075 settingsDiv.hide();
1076 enabledNotice.hide();
1077 }
1078 }
1079 }
1080
1081 function initPineconeConnectionTest() {
1082 $('#test-pinecone-connection').off('click.pinecone');
1083
1084 $('#test-pinecone-connection').on('click.pinecone', function() {
1085 var button = $(this);
1086 var resultDiv = $('#connection-test-result');
1087
1088 var apiKey = $('#mxchat_pinecone_api_key').val();
1089 var host = $('#mxchat_pinecone_host').val();
1090 var index = $('#mxchat_pinecone_index').val();
1091
1092 if (!apiKey || !host || !index) {
1093 resultDiv.html('<div class="notice notice-error"><p>Please fill in all required fields first.</p></div>').show();
1094 return;
1095 }
1096
1097 button.prop('disabled', true).text('Testing...');
1098 resultDiv.hide();
1099
1100 var ajaxUrl = (typeof mxchatPromptsAdmin !== 'undefined') ? mxchatPromptsAdmin.ajax_url : ajaxurl;
1101 var nonce = (typeof mxchatPromptsAdmin !== 'undefined') ? mxchatPromptsAdmin.prompts_setting_nonce :
1102 (typeof mxchatAdmin !== 'undefined') ? mxchatAdmin.setting_nonce : '';
1103
1104 $.ajax({
1105 url: ajaxUrl,
1106 type: 'POST',
1107 data: {
1108 action: 'mxchat_test_pinecone_connection',
1109 _ajax_nonce: nonce,
1110 api_key: apiKey,
1111 host: host,
1112 index_name: index
1113 },
1114 success: function(response) {
1115 if (response.success) {
1116 resultDiv.html('<div class="notice notice-success"><p><span class="dashicons dashicons-yes-alt"></span> ' + response.data.message + '</p></div>');
1117 } else {
1118 resultDiv.html('<div class="notice notice-error"><p><span class="dashicons dashicons-warning"></span> ' + response.data.message + '</p></div>');
1119 }
1120 resultDiv.show();
1121 },
1122 error: function() {
1123 resultDiv.html('<div class="notice notice-error"><p>Connection test failed. Please check your settings.</p></div>').show();
1124 },
1125 complete: function() {
1126 button.prop('disabled', false).text('Test Connection');
1127 }
1128 });
1129 });
1130 }
1131
1132 function checkPineconeCompatibility() {
1133 if ($('.mxchat-pinecone-compatibility-notice').length > 0) {
1134 return;
1135 }
1136
1137 var hasOldAddon = $('body').hasClass('mxchat-pinecone-addon-active') ||
1138 $('.pcm-card').length > 0;
1139
1140 if (hasOldAddon) {
1141 var compatibilityNotice = $(`
1142 <div class="notice notice-info mxchat-pinecone-compatibility-notice">
1143 <p><strong>Pinecone Integration Notice:</strong> We've detected you have the Pinecone add-on installed.
1144 Pinecone functionality is now built into the core plugin. You can safely deactivate the separate
1145 Pinecone add-on after confirming your settings are migrated below.</p>
1146 </div>
1147 `);
1148
1149 $('#mxchat-kb-tab-pinecone .mxchat-card').prepend(compatibilityNotice);
1150
1151 migratePineconeSettings();
1152 }
1153 }
1154
1155 function migratePineconeSettings() {
1156 if (typeof ajaxurl !== 'undefined') {
1157 $.ajax({
1158 url: ajaxurl,
1159 type: 'POST',
1160 data: {
1161 action: 'mxchat_migrate_pinecone_settings',
1162 _ajax_nonce: (typeof mxchatAdmin !== 'undefined') ? mxchatAdmin.setting_nonce : ''
1163 },
1164 success: function(response) {
1165 if (response.success && response.data.migrated) {
1166 location.reload();
1167 }
1168 },
1169 error: function() {
1170 //console.log('Pinecone settings migration not available');
1171 }
1172 });
1173 }
1174 }
1175
1176 // ============================================
1177 // OpenAI Vector Store functionality
1178 // ============================================
1179 initVectorStoreFeatures = function() {
1180 if ($('#openai-vectorstore').length === 0) {
1181 return;
1182 }
1183
1184 initVectorStoreToggle();
1185 };
1186
1187 function initVectorStoreToggle() {
1188 // Remove any existing handlers to prevent duplicates
1189 var $toggleInput = $('input[name="mxchat_openai_vectorstore_options[mxchat_use_openai_vectorstore]"]');
1190 $toggleInput.off('change.vectorstoreToggle');
1191
1192 // Ensure the success notice exists in the settings div (add if not present)
1193 // Check for both the JS-added class and any existing PHP-rendered success notice
1194 var settingsDiv = $('.mxchat-vectorstore-settings');
1195 if (settingsDiv.length > 0 && settingsDiv.find('.mxch-notice-success').length === 0) {
1196 var successNotice = $('<div class="mxch-notice mxch-notice-success mxchat-vectorstore-enabled-notice" style="margin-bottom: 20px; display: none;">' +
1197 '<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>' +
1198 '<span>OpenAI Vector Store is enabled. Queries will search your Vector Store for relevant content.</span>' +
1199 '</div>');
1200 settingsDiv.prepend(successNotice);
1201 }
1202
1203 // Add the toggle handler for UI only (form submit will handle the actual saving)
1204 $toggleInput.on('change.vectorstoreToggle', function() {
1205 var $checkbox = $(this);
1206 var isChecked = $checkbox.is(':checked');
1207 var settingsDiv = $('.mxchat-vectorstore-settings');
1208 var enabledNotice = settingsDiv.find('.mxchat-vectorstore-enabled-notice, .mxch-notice-success');
1209
1210 // Update the UI immediately
1211 if (isChecked) {
1212 settingsDiv.slideDown(300);
1213 enabledNotice.slideDown(300);
1214 } else {
1215 enabledNotice.slideUp(300);
1216 settingsDiv.slideUp(300);
1217 }
1218
1219 // Update sidebar badge for OpenAI Vector Store
1220 updateSidebarBadge('openai-vectorstore', isChecked);
1221 });
1222
1223 // Set initial state based on current checkbox value
1224 var currentToggle = $('input[name="mxchat_openai_vectorstore_options[mxchat_use_openai_vectorstore]"]');
1225 if (currentToggle.length > 0) {
1226 var settingsDiv = $('.mxchat-vectorstore-settings');
1227 var enabledNotice = settingsDiv.find('.mxchat-vectorstore-enabled-notice, .mxch-notice-success');
1228 if (currentToggle.is(':checked')) {
1229 settingsDiv.show();
1230 enabledNotice.show();
1231 } else {
1232 settingsDiv.hide();
1233 enabledNotice.hide();
1234 }
1235 }
1236 }
1237
1238 })(jQuery);