jQuery(document).ready(function($) {
// Modal elements
const $modal = $('#mxchat-kb-content-selector-modal');
const $openButton = $('#mxchat-open-content-selector');
const $closeButtons = $('.mxchat-kb-modal-close');
const $contentList = $('.mxchat-kb-content-list');
const $loading = $('.mxchat-kb-loading');
const $pagination = $('.mxchat-kb-pagination');
const $processButton = $('#mxchat-kb-process-selected');
const $selectAll = $('#mxchat-kb-select-all');
const $selectionCount = $('.mxchat-kb-selection-count');
// ACF→PDF extraction is an install-level setting (Knowledge → ACF Fields);
// the server reads the stored option — nothing to collect in this modal.
// Filter elements
const $searchInput = $('#mxchat-kb-content-search');
const $typeFilter = $('#mxchat-kb-content-type-filter');
const $statusFilter = $('#mxchat-kb-content-status-filter');
const $processedFilter = $('#mxchat-kb-processed-filter');
// Current state - using let for variables that change
let currentPage = 1;
let totalPages = 1;
let selectedItems = new Set();
let allItems = [];
// Open modal when WordPress import button is clicked
$openButton.on('click', function() {
$modal.show();
// Reset to first page when opening the modal
currentPage = 1;
loadContent();
});
// Close modal
$closeButtons.on('click', function() {
$modal.hide();
});
// Handle import option box clicks (for non-WordPress options)
$('.mxchat-import-box').on('click', function() {
const $box = $(this);
const option = $box.data('option');
// Skip if this is the WordPress option (it has its own handler)
if (option === 'wordpress') {
return;
}
// Update active state
$('.mxchat-import-box').removeClass('active');
$box.addClass('active');
// Hide all input areas
$('#mxchat-url-input-area, #mxchat-content-input-area, #mxchat-pdf-upload-area, #mxchat-document-upload-area, #mxchat-youtube-input-area').hide();
// Hide sitemap-specific sections (but NOT for sitemap option - let detection logic handle it)
if (option !== 'sitemap') {
$('#mxchat-detected-sitemaps, #mxchat-no-sitemaps, #mxchat-sitemaps-loading').hide();
}
// Handle different import options
switch (option) {
case 'pdf-url':
case 'sitemap':
case 'url':
// Show URL input area with appropriate placeholder
$('#mxchat-url-input-area').show();
$('#sitemap_url').attr('placeholder', $box.data('placeholder'));
$('#import_type').val(option === 'pdf-url' ? 'pdf' : option);
// UPDATED: Add or update bot_id hidden field for URL forms
updateBotIdInForm('#mxchat-url-form');
// Update the description text based on the import type
let descriptionText = '';
if (option === 'pdf-url') {
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.';
} else if (option === 'sitemap') {
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.';
// Re-show sitemap sections if they were previously loaded
const $sitemapsList = $('#mxchat-sitemaps-list');
if ($sitemapsList.children().length > 0) {
// Sitemaps were already loaded, just show the container
$('#mxchat-detected-sitemaps').show();
} else if ($('#mxchat-no-sitemaps').data('was-shown')) {
// No sitemaps message was shown before
$('#mxchat-no-sitemaps').show();
}
// Note: If neither condition is true, initSitemapDetection will show loading state
} else if (option === 'url') {
descriptionText = 'Import content from any webpage by entering its URL.';
}
$('#url-description-text').text(descriptionText);
break;
case 'content':
// Show content input area
$('#mxchat-content-input-area').show();
// UPDATED: Add or update bot_id hidden field for content forms
updateBotIdInForm('#mxchat-content-form');
break;
case 'pdf-upload':
// Show PDF file upload area
$('#mxchat-pdf-upload-area').show();
// Add or update bot_id hidden field for PDF upload form
updateBotIdInForm('#mxchat-pdf-upload-form');
break;
case 'document-upload':
// Show document (.docx/.txt/.md) upload area
$('#mxchat-document-upload-area').show();
// Add or update bot_id hidden field for the document upload form
updateBotIdInForm('#mxchat-document-upload-form');
break;
case 'youtube':
// Show YouTube import area
$('#mxchat-youtube-input-area').show();
// Add or update bot_id hidden field for YouTube form
updateBotIdInForm('#mxchat-youtube-form');
break;
}
});
// YouTube import: toggle the manual-description box with the mode radios, and
// only require the textarea when "Write my own" is selected.
$(document).on('change', 'input[name="youtube_description_mode"]', function() {
const manual = $('input[name="youtube_description_mode"]:checked').val() === 'manual';
$('#mxchat-youtube-description-field').toggle(manual);
$('#mxchat-youtube-description').prop('required', manual);
});
// If the server bounced back with prefill state (auto-import found no
// transcript), reopen the YouTube form in manual mode ready to augment.
if ($('#mxchat-youtube-form').data('yt-prefill')) {
$('.mxchat-import-box[data-option="youtube"]').trigger('click');
$('input[name="youtube_description_mode"]').trigger('change');
$('#mxchat-youtube-description').trigger('focus');
}
// Helper function to add/update bot_id hidden field in forms
function updateBotIdInForm(formSelector) {
const $form = $(formSelector);
if ($form.length === 0) return;
// Get current bot_id from the bot selector dropdown
const currentBotId = $('#mxchat-bot-selector').val();
// Only add bot_id field if multi-bot is active and bot is not 'default'
if (currentBotId && currentBotId !== 'default') {
// Remove existing bot_id field if it exists
$form.find('input[name="bot_id"]').remove();
// Add new bot_id field
$form.append('');
console.log('Updated bot_id in form ' + formSelector + ' to: ' + currentBotId);
} else {
// Remove bot_id field if bot is default
$form.find('input[name="bot_id"]').remove();
}
}
// Load content via AJAX
function loadContent() {
$loading.show();
$contentList.find('.mxchat-kb-content-item').remove();
const data = {
action: 'mxchat_get_content_list',
nonce: mxchatSelector.nonce,
page: currentPage,
per_page: 100,
search: $searchInput.val(),
post_type: $typeFilter.val(),
post_status: $statusFilter.val(),
processed_filter: $processedFilter.val()
};
//console.log('Loading content for page', currentPage, 'with filters:', data);
$.ajax({
url: mxchatSelector.ajaxurl,
data: data,
method: 'GET',
dataType: 'json',
success: function(response) {
$loading.hide();
if (response.success && response.data.items && response.data.items.length > 0) {
// Store the items directly
let items = response.data.items;
if (items.length > 0) {
renderContentItems(items);
renderPagination(parseInt(response.data.current_page), parseInt(response.data.total_pages));
// Update state
allItems = items;
totalPages = parseInt(response.data.total_pages);
currentPage = parseInt(response.data.current_page);
// Update select all checkbox based on current selection
updateSelectAllState();
} else {
displayNoResults($processedFilter.val());
}
} else {
displayNoResults($processedFilter.val());
}
},
error: function(xhr, status, error) {
$loading.hide();
console.error('AJAX Error:', status, error);
$contentList.html('
Error loading content. Please try again.
');
// Clear pagination on error
$pagination.empty();
}
});
}
// Helper function to display appropriate "no results" message
function displayNoResults(processedStatus) {
let message = 'No content found matching your criteria.';
if (processedStatus === 'processed') {
message = 'No content found in knowledge base.';
} else if (processedStatus === 'unprocessed') {
message = 'All content is already in knowledge base.';
}
$contentList.html('
' + message + '
');
// Clear pagination when no results
$pagination.empty();
}
// Render content items
function renderContentItems(items) {
let html = '';
items.forEach(function(item) {
const isSelected = selectedItems.has(item.id);
const isProcessed = item.already_processed;
const chunkCount = item.chunk_count || 0;
// Updated badge text - include chunk count if > 1
let badgeText = 'Not In Knowledge Base';
if (isProcessed) {
badgeText = chunkCount > 1 ? `In Knowledge Base (${chunkCount} chunks)` : 'In Knowledge Base';
}
const badgeClass = isProcessed ? 'mxchat-kb-processed-badge' : 'mxchat-kb-unprocessed-badge';
html += `
';
// Show results in modal
$modal.find('.mxchat-kb-modal-content').prepend($(resultHTML));
// Clear selection
selectedItems.clear();
updateSelection();
// Enable button
$button.prop('disabled', false)
.text('Process Selected Content')
.removeClass('update-mode mixed-mode');
$('.mxchat-kb-selected-count').text('(0)');
// Only reload if there were successful operations
if (processed > 0 || updated > 0) {
// Refresh the knowledge base table with properly grouped entries
if (typeof window.refreshKnowledgeBaseTable === 'function') {
window.refreshKnowledgeBaseTable();
}
// Reload content list to update "already processed" status
setTimeout(function() {
loadContent();
}, 1000);
}
}
// Start processing the first post
processNext(0);
});
// Initialize - Set WordPress as the active option by default
$('.mxchat-import-box[data-option="wordpress"]').addClass('active');
});
// Navigation functionality for Knowledge Base page
jQuery(document).ready(function($) {
// Hook into the new navigation system using .mxch-nav-link
$(document).on('click', '.mxch-nav-link[data-target], .mxch-mobile-nav-link[data-target]', function() {
var target = $(this).data('target');
// Initialize Pinecone functionality when Pinecone section is activated
if (target === 'pinecone') {
setTimeout(function() {
if (typeof initPineconeFeatures === 'function') {
initPineconeFeatures();
}
}, 100);
}
// Initialize OpenAI Vector Store functionality when Vector Store section is activated
if (target === 'openai-vectorstore') {
setTimeout(function() {
if (typeof initVectorStoreFeatures === 'function') {
initVectorStoreFeatures();
}
}, 100);
}
// Check if Pinecone was changed and we're going to import section
if (target === 'import' && sessionStorage.getItem('mxchat_pinecone_changed') === 'true') {
sessionStorage.removeItem('mxchat_pinecone_changed');
// Show refresh notice
var $knowledgeCard = $('#import .mxch-card').eq(1);
if ($knowledgeCard.length > 0 && $knowledgeCard.find('.notice-warning').length === 0) {
var refreshNotice = $('
' +
'
' +
'' +
'Database settings have changed. ' +
'Click here to refresh to see the updated knowledge base.' +
'
');
$knowledgeCard.prepend(refreshNotice);
}
}
});
// Also check on page load if we're already on one of these sections
setTimeout(function() {
if ($('#pinecone').is(':visible') || $('#pinecone.active').length > 0) {
if (typeof initPineconeFeatures === 'function') {
initPineconeFeatures();
}
}
if ($('#openai-vectorstore').is(':visible') || $('#openai-vectorstore.active').length > 0) {
if (typeof initVectorStoreFeatures === 'function') {
initVectorStoreFeatures();
}
}
}, 200);
});
// Pinecone and Vector Store functionality - global functions
var initPineconeFeatures, initVectorStoreFeatures;
(function($) {
// Helper function to update sidebar badge when integration is toggled
function updateSidebarBadge(section, isActive) {
// Find the nav item for this section (both desktop and mobile)
var $desktopNavItem = $('.mxch-nav-item[data-section="' + section + '"] .mxch-nav-link');
var $mobileNavItem = $('.mxch-mobile-nav-link[data-target="' + section + '"]');
// Remove existing badge if any
$desktopNavItem.find('.mxch-active-badge').remove();
$mobileNavItem.find('.mxch-active-badge').remove();
// Add badge if active
if (isActive) {
var badgeHtml = 'Active';
$desktopNavItem.append(badgeHtml);
$mobileNavItem.append(badgeHtml);
}
}
// Pinecone functionality
initPineconeFeatures = function() {
// Check for either old or new section ID
if ($('#pinecone').length === 0 && $('#mxchat-kb-tab-pinecone').length === 0) {
return;
}
initPineconeToggle();
initPineconeIndexType();
initPineconeConnectionTest();
checkPineconeCompatibility();
};
// Index type (plan 362c31): the document-index controls on the Pinecone
// card — Create index for me, Check index, Migrate, Delete old index —
// plus the save gate (a document host must be checked before it saves).
function initPineconeIndexType() {
var $type = $('input.mxchat-pinecone-index-type');
if ($type.length === 0) {
return;
}
var $docs = $('.mxchat-pinecone-docs-settings');
var $form = $type.closest('form');
var $submit = $form.find('input[type="submit"], button[type="submit"]');
var $result = $('#mxchat-pinecone-docs-result');
var stateEl = document.getElementById('mxchat-pinecone-docs-state');
var state = {};
try { state = stateEl ? JSON.parse(stateEl.textContent || '{}') : {}; } catch (e) { state = {}; }
var verifiedHost = String(state.verified_host || '').toLowerCase();
// 3e83e4: the LIVE chatbot's index type + host as saved, the migration
// state as last known, and the index name the page loaded with (so a
// suggested "-docs" name can be undone by switching back to Vector).
var liveType = state.index_type === 'document' ? 'document' : 'vector';
var storedHost = String(state.stored_host || '').toLowerCase();
var storedIndex = String(state.stored_index || '');
var originalIndexName = $.trim($('#mxchat_pinecone_index').val() || '');
var migrationState = (state.migration && state.migration.status) ? state.migration : null;
var ajaxUrl = (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.ajax_url) ? mxchatAdmin.ajax_url : ajaxurl;
var nonce = (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.settings_nonce) ? mxchatAdmin.settings_nonce :
((typeof mxchatPromptsAdmin !== 'undefined' && mxchatPromptsAdmin.prompts_setting_nonce) ? mxchatPromptsAdmin.prompts_setting_nonce : '');
function escapeText(text) { return $('
').show();
}
function setStatus(ok, text) {
$('#mxchat-pinecone-docs-status').removeClass('mxch-notice-success mxch-notice-warning').addClass(ok ? 'mxch-notice-success' : 'mxch-notice-warning');
$('#mxchat-pinecone-docs-status-text').text(text);
}
function currentType() { return $type.filter(':checked').val() || 'vector'; }
function hostValue() {
return $.trim($('#mxchat_pinecone_host').val() || '').replace(/^https?:\/\//i, '').replace(/\/+$/, '').toLowerCase();
}
function fmt(n) { n = parseInt(n, 10) || 0; return n.toLocaleString(); }
// Does saving Document right now point the chatbot at an index whose copy is unfinished?
function switchBlockedBy() {
if (currentType() !== 'document' || liveType === 'document' || !migrationState) { return null; }
if (migrationState.status !== 'running') { return null; }
if (String(migrationState.target_host || '').toLowerCase() !== hostValue()) { return null; }
return migrationState;
}
function refreshGate() {
var isDoc = currentType() === 'document';
$docs.toggle(isDoc);
var blocked = isDoc && (hostValue() === '' || hostValue() !== verifiedHost);
$('#mxchat-pinecone-docs-gate').toggle(blocked);
var running = blocked ? null : switchBlockedBy();
var $switchGate = $('#mxchat-pinecone-docs-switch-gate');
if (running) {
$('#mxchat-pinecone-docs-switch-gate-text').text(
'The copy into ' + (running.target_name || running.target_host) + ' is still running (' + fmt(running.copied) + ' of ' + fmt(running.total) +
' records). Let it finish before switching the chatbot to the document index.'
);
$switchGate.show();
blocked = !$('#mxchat_pinecone_docs_switch_anyway').is(':checked');
} else {
$switchGate.hide();
$('#mxchat_pinecone_docs_switch_anyway').prop('checked', false);
}
$submit.prop('disabled', blocked);
}
$('#mxchat_pinecone_docs_switch_anyway').off('change.pineconeDocs').on('change.pineconeDocs', refreshGate);
$('#mxchat_pinecone_host').off('input.pineconeDocs change.pineconeDocs').on('input.pineconeDocs change.pineconeDocs', refreshGate);
function post(data) {
data.nonce = nonce;
return $.post(ajaxUrl, data);
}
// ---- Index type change: a document index needs its own name, so when
// the field still holds the vector index's name (or is empty) suggest a
// free "-docs" from the project; Vector restores the original.
var suggestedName = '';
function nameIsOurs() {
var name = $.trim($('#mxchat_pinecone_index').val() || '');
return name === '' || name === storedIndex || name === originalIndexName || name === suggestedName;
}
// When the host in the field is a checked document index, the name is
// that index's own; otherwise a free "-docs" for the create button.
function suggestDocsName() {
var name = $.trim($('#mxchat_pinecone_index').val() || '');
var apiKey = $('#mxchat_pinecone_api_key').val();
if (liveType === 'document' || !apiKey || currentType() !== 'document') { return; }
if (!nameIsOurs()) { return; } // the owner typed their own
var host = (hostValue() !== '' && hostValue() === verifiedHost) ? hostValue() : '';
post({ action: 'mxchat_pinecone_docs_suggest_name', base: name || storedIndex, host: host, api_key: apiKey }).done(function (response) {
if (response && response.success && response.data && response.data.name && currentType() === 'document' && nameIsOurs()) {
suggestedName = response.data.name;
$('#mxchat_pinecone_index').val(suggestedName);
}
});
}
$type.off('change.pineconeDocs').on('change.pineconeDocs', function () {
if (currentType() === 'document') {
suggestDocsName();
} else if (suggestedName !== '' && $.trim($('#mxchat_pinecone_index').val() || '') === suggestedName) {
$('#mxchat_pinecone_index').val(originalIndexName);
}
refreshGate();
});
// Pasting the checked document host resolves the name to that index's own.
$('#mxchat_pinecone_host').on('change.pineconeDocsName', function () {
if (currentType() === 'document' && hostValue() !== '' && hostValue() === verifiedHost) { suggestDocsName(); }
});
refreshGate();
function failMessage(response, fallback) {
if (response && response.data) {
if (typeof response.data === 'string') { return response.data; }
if (response.data.message) { return response.data.message; }
}
return fallback;
}
// ---- Create index for me
$('#mxchat-pinecone-docs-create').off('click.pineconeDocs').on('click.pineconeDocs', function () {
var $btn = $(this);
var name = $.trim($('#mxchat_pinecone_index').val() || '');
if (!name) {
notice('error', 'Enter an Index Name first — the document index is created under that name.');
return;
}
$btn.prop('disabled', true).text('Creating…');
$result.hide();
post({
action: 'mxchat_pinecone_docs_create_index',
index_name: name,
api_key: $('#mxchat_pinecone_api_key').val(),
cloud: $('#mxchat_pinecone_docs_cloud').val(),
region: $('#mxchat_pinecone_docs_region').val(),
language: $('#mxchat_pinecone_docs_language').val()
}).done(function (response) {
if (response && response.success) {
$('#mxchat_pinecone_host').val(response.data.host);
$('#mxchat_pinecone_index').val(response.data.name);
if (response.data.vector_host && !$.trim($('#mxchat_pinecone_vector_host').val() || '')) {
$('#mxchat_pinecone_vector_host').val(response.data.vector_host);
}
verifiedHost = String(response.data.host || '').toLowerCase();
setStatus(true, 'Document index checked: ' + response.data.host + ' (' + response.data.dimension + ' dimensions).');
notice(response.data.ready ? 'success' : 'info', response.data.message);
refreshGate();
} else {
// A taken name comes back with a free suggestion — fill it in so the next click works.
if (response && response.data && response.data.suggested) {
suggestedName = response.data.suggested;
$('#mxchat_pinecone_index').val(response.data.suggested);
}
notice('error', failMessage(response, 'Could not create the index.'));
}
}).fail(function () {
notice('error', 'Could not reach the server to create the index.');
}).always(function () {
$btn.prop('disabled', false).text('Create index for me');
});
});
// ---- Check index
$('#mxchat-pinecone-docs-check').off('click.pineconeDocs').on('click.pineconeDocs', function () {
var $btn = $(this);
var host = hostValue();
if (!host) {
notice('error', 'Enter the Pinecone Host of the document index first.');
return;
}
$btn.prop('disabled', true).text('Checking…');
$result.hide();
post({
action: 'mxchat_pinecone_docs_check_index',
host: host,
api_key: $('#mxchat_pinecone_api_key').val()
}).done(function (response) {
if (response && response.success) {
verifiedHost = String(response.data.host || host).toLowerCase();
if (response.data.name) { $('#mxchat_pinecone_index').val(response.data.name); }
if (response.data.vector_host && !$.trim($('#mxchat_pinecone_vector_host').val() || '')) {
$('#mxchat_pinecone_vector_host').val(response.data.vector_host);
}
setStatus(true, 'Document index checked: ' + response.data.host + ' (' + response.data.dimension + ' dimensions).');
notice('success', response.data.message);
} else {
setStatus(false, 'Not checked: this host cannot be used as a document index.');
notice('error', failMessage(response, 'Check failed.'));
}
refreshGate();
}).fail(function () {
notice('error', 'Could not reach the server to check the index.');
}).always(function () {
$btn.prop('disabled', false).text('Check index');
});
});
// ---- Migrate (copy, page by page, resumable). 3e83e4: the copy runs
// against the document host in the form while the chatbot keeps
// answering from the vector index; a dropped request is retried with
// backoff, and a second click continues from the recorded position.
var $migrateBtn = $('#mxchat-pinecone-docs-migrate');
// e50d2e: "Copy every namespace" — offered once the start response
// reports records in other namespaces; the copy then walks every
// namespace of the source in one run, each into its own namespace on
// the document side. Disabled while a copy runs in this tab (the
// server extends a running copy on the next Migrate click, never
// underneath a step that is in flight).
var $allNs = $('#mxchat_pinecone_docs_all_namespaces');
var $allNsRow = $('#mxchat-pinecone-docs-all-namespaces');
var stepRetries = 0;
var STEP_RETRY_DELAYS = [1000, 2000, 4000, 8000, 16000];
function remainingOf(st) {
var total = parseInt(st.total, 10) || 0, copied = parseInt(st.copied, 10) || 0;
return Math.max(0, total - copied);
}
function otherNamespacesText(st) {
var other = st.other_namespaces || {};
var parts = [];
$.each(other, function (name, count) { parts.push(name + ' (' + fmt(count) + ')'); });
return parts.join(', ');
}
function otherNamespacesSummary(st) {
var records = 0, n = 0;
$.each(st.other_namespaces || {}, function (name, count) { records += parseInt(count, 10) || 0; n++; });
return n ? { records: records, count: n } : null;
}
function namespaceEntries(st) {
var out = [];
$.each(st.per_namespace || {}, function (key, e) { out.push($.extend({ key: key }, e)); });
return out;
}
function isMulti(st) { return !!st.all_namespaces || namespaceEntries(st).length > 1; }
function namespaceLine(e, sep) {
return e.key + ' ' + fmt(e.copied) + ' of ' + fmt(e.total) + (e.target_count ? (sep + 'the document index reports ' + fmt(e.target_count) + ')') : '');
}
function renderMigration(st) {
migrationState = (st && st.status) ? st : null;
var $progress = $('#mxchat-pinecone-docs-migrate-progress');
if (!st || !st.status) {
$progress.hide();
$('#mxchat-pinecone-docs-migrate-counts').text('');
$('#mxchat-pinecone-docs-delete-old').hide();
$allNsRow.hide();
refreshGate();
return;
}
var total = parseInt(st.total, 10) || 0;
var copied = parseInt(st.copied, 10) || 0;
var pct = total > 0 ? Math.min(100, Math.round(copied * 100 / total)) : (st.status === 'done' ? 100 : 0);
var multi = isMulti(st);
var entries = namespaceEntries(st);
var others = otherNamespacesSummary(st);
var label, suffix;
if (st.status === 'done') {
suffix = ' — done' + (st.target_count ? ('; the document index reports ' + fmt(st.target_count) + ' records') : '');
} else if (st.status === 'source_deleted') {
suffix = ' — old index deleted';
} else if (st.status === 'running') {
suffix = st.error ? (' — stopped: ' + st.error) : (' — copying, ' + fmt(remainingOf(st)) + ' remaining…');
} else {
suffix = '';
}
if (multi && st.status === 'running') {
var idx = 0, cur = null;
$.each(entries, function (i, e) { if (e.key === (st.current === '' ? '__default__' : st.current)) { idx = i + 1; cur = e; } });
label = (cur ? ('Namespace ' + idx + ' of ' + entries.length + ' (' + cur.key + ') — ' + fmt(cur.copied) + ' of ' + fmt(cur.total) + ' records copied; ') : '') +
fmt(copied) + ' of ' + fmt(total) + ' overall (' + pct + '%)' + (st.error ? (' — stopped: ' + st.error) : (', ' + fmt(remainingOf(st)) + ' remaining…'));
} else if (multi) {
label = fmt(copied) + ' of ' + fmt(total) + ' records copied across ' + entries.length + ' namespaces (' + pct + '%)' + suffix;
} else {
label = fmt(copied) + ' of ' + fmt(total) + ' records copied (' + pct + '%)' + suffix;
}
$progress.show().find('.mxch-progress-bar-fill').css('width', pct + '%');
$progress.find('.mxch-progress-label').text(label);
var counts;
if (multi) {
var lines = $.map(entries, function (e) { return namespaceLine(e, ' ('); });
counts = (st.source_name ? ('Source: ' + st.source_name + ' (' + fmt(total) + ' records in ' + entries.length + ' namespaces). ') : '') + lines.join(' · ') + '.';
} else {
counts = st.source_name ? ('Source: ' + st.source_name + ' (' + fmt(total) + ' records' + (st.namespace ? (' in namespace ' + st.namespace) : '') + ')') : '';
if (others) {
counts += (counts ? '. ' : '') + 'Not copied — ' + fmt(others.records) + ' records in ' + others.count + ' other namespace' + (others.count === 1 ? '' : 's') + ': ' + otherNamespacesText(st) + '. Tick Copy every namespace and click Migrate to include them.';
}
}
$('#mxchat-pinecone-docs-migrate-counts').text(counts);
// The toggle appears once the source is known to have other namespaces, and stays on for a copy of every namespace.
$allNsRow.toggle(multi || !!others);
if (st.all_namespaces) { $allNs.prop('checked', true); }
$allNs.prop('disabled', $migrateBtn.prop('disabled'));
// Delete old index is never offered while the copy is known to leave namespaces behind (the server refuses too).
$('#mxchat-pinecone-docs-delete-old').toggle(st.status === 'done' && total > 0 && copied >= total && liveType === 'document' && !others);
refreshGate();
}
function migrateFinished(st) {
$migrateBtn.prop('disabled', false).text('Migrate');
$allNs.prop('disabled', false);
var total = parseInt(st.total, 10) || 0, copied = parseInt(st.copied, 10) || 0;
var reported = st.target_count ? (' The document index reports ' + fmt(st.target_count) + ' records.') : '';
var stillVector = liveType === 'document' ? '' : ' Your chatbot is still on the vector index — save the Pinecone settings to switch it over.';
var entries = namespaceEntries(st);
var others = otherNamespacesSummary(st);
if (isMulti(st)) {
var lines = $.map(entries, function (e) { return namespaceLine(e, ' ('); });
if (copied >= total) {
notice('success', 'Copy finished: ' + fmt(copied) + ' of ' + fmt(total) + ' records across ' + entries.length + ' namespaces are in the document index — ' + lines.join(', ') + '.' + stillVector);
} else {
var short = $.map(entries, function (e) { return (parseInt(e.copied, 10) || 0) < (parseInt(e.total, 10) || 0) ? namespaceLine(e, ' (') : null; });
notice('warning', 'The copy reached the end of every namespace after ' + fmt(copied) + ' records, but ' + fmt(total) + ' were expected. Short: ' + short.join(', ') + '.' + reported +
(parseInt(st.skipped, 10) ? (' ' + fmt(st.skipped) + ' records had no vector and were skipped.') : ''));
}
return;
}
if (copied >= total) {
notice(others ? 'warning' : 'success', 'Copy finished: ' + fmt(copied) + ' of ' + fmt(total) + ' records are in the document index.' + reported +
(others ? (' ' + fmt(others.records) + ' records in ' + others.count + ' other namespace' + (others.count === 1 ? ' were' : 's were') + ' not copied: ' + otherNamespacesText(st) + '. Tick Copy every namespace and click Migrate to include them.') : '') + stillVector);
} else {
notice('warning', 'The copy reached the end of the source namespace after ' + fmt(copied) + ' records, but ' + fmt(total) + ' were expected.' + reported +
(others ? (' Records in other namespaces were not copied: ' + otherNamespacesText(st) + '.') : '') +
(parseInt(st.skipped, 10) ? (' ' + fmt(st.skipped) + ' records had no vector and were skipped.') : ''));
}
}
function migrateStep() {
post({ action: 'mxchat_pinecone_docs_migrate', mode: 'step', target_host: hostValue(), api_key: $('#mxchat_pinecone_api_key').val() }).done(function (response) {
stepRetries = 0;
if (response && response.success) {
renderMigration(response.data.state);
if (response.data.state && response.data.state.status === 'running') {
setTimeout(migrateStep, 250);
} else {
migrateFinished(response.data.state || {});
}
} else {
var st = (response && response.data && response.data.state) ? response.data.state : null;
if (st) { renderMigration(st); }
notice('error', failMessage(response, 'The copy stopped.') + (st ? (' Click Migrate to continue from record ' + fmt(st.copied) + '.') : ' Click Migrate to resume.'));
$migrateBtn.prop('disabled', false).text('Migrate');
$allNs.prop('disabled', false);
}
}).fail(function (xhr) {
// The server did not answer (timeout, 5xx, rate limit): wait and try the same step again.
if (stepRetries < STEP_RETRY_DELAYS.length) {
var delay = STEP_RETRY_DELAYS[stepRetries++];
notice('info', 'No answer from the server (HTTP ' + (xhr && xhr.status ? xhr.status : 0) + '). Retrying in ' + Math.round(delay / 1000) + ' s… the copy continues from where it stopped.');
setTimeout(migrateStep, delay);
return;
}
stepRetries = 0;
notice('error', 'The server stopped answering (HTTP ' + (xhr && xhr.status ? xhr.status : 0) + ') after several tries. Nothing is lost — click Migrate to continue from the last record copied.');
$migrateBtn.prop('disabled', false).text('Migrate');
$allNs.prop('disabled', false);
});
}
$migrateBtn.off('click.pineconeDocs').on('click.pineconeDocs', function () {
var source = $.trim($('#mxchat_pinecone_vector_host').val() || '');
if (!source) {
notice('error', 'Enter the host of the vector index to copy from.');
return;
}
if (!hostValue()) {
notice('error', 'Enter the host of the document index to copy into, or create one with Create index for me.');
return;
}
$migrateBtn.prop('disabled', true).text('Copying…');
$allNs.prop('disabled', true);
$result.hide();
stepRetries = 0;
post({ action: 'mxchat_pinecone_docs_migrate', mode: 'start', source_host: source, target_host: hostValue(), api_key: $('#mxchat_pinecone_api_key').val(), all_namespaces: $allNs.is(':checked') ? 1 : 0 }).done(function (response) {
if (response && response.success) {
var st = response.data.state || {};
if (st.target_host && !verifiedHost) { verifiedHost = String(st.target_host).toLowerCase(); setStatus(true, 'Document index checked: ' + st.target_host + '.'); }
renderMigration(st);
if (st.status === 'running') {
migrateStep();
} else {
migrateFinished(st);
}
} else {
notice('error', failMessage(response, 'The copy could not start.'));
$migrateBtn.prop('disabled', false).text('Migrate');
$allNs.prop('disabled', false);
}
}).fail(function () {
notice('error', 'Could not reach the server to start the copy.');
$migrateBtn.prop('disabled', false).text('Migrate');
$allNs.prop('disabled', false);
});
});
renderMigration(state.migration || null);
// ---- Delete old index (typed confirmation)
$('#mxchat-pinecone-docs-delete-old-btn').off('click.pineconeDocs').on('click.pineconeDocs', function () {
var $btn = $(this);
var confirmName = $.trim($('#mxchat_pinecone_docs_delete_confirm').val() || '');
if (!confirmName) {
notice('error', 'Type the old index name to confirm.');
return;
}
$btn.prop('disabled', true).text('Deleting…');
post({ action: 'mxchat_pinecone_docs_delete_old_index', confirm_name: confirmName }).done(function (response) {
if (response && response.success) {
notice('success', response.data.message);
$('#mxchat_pinecone_vector_host').val('');
$('#mxchat_pinecone_docs_delete_confirm').val('');
renderMigration(response.data.state);
} else {
notice('error', failMessage(response, 'The old index was not deleted.'));
}
}).fail(function () {
notice('error', 'Could not reach the server.');
}).always(function () {
$btn.prop('disabled', false).text('Delete old index');
});
});
}
function initPineconeToggle() {
// Remove any existing handlers to prevent duplicates
var $toggleInput = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]');
$toggleInput.off('change.pineconeToggle');
// Ensure the success notice exists in the settings div (add if not present)
// Check for both the JS-added class and any existing PHP-rendered success notice
var settingsDiv = $('.mxchat-pinecone-settings');
if (settingsDiv.length > 0 && settingsDiv.find('.mxch-notice-success').length === 0) {
var successNotice = $('
' +
'' +
'Pinecone is enabled. All new knowledge base content will be stored in Pinecone.' +
'
');
settingsDiv.prepend(successNotice);
}
// Add the toggle handler for UI only (auto-save will handle the actual saving)
$toggleInput.on('change.pineconeToggle', function() {
var $checkbox = $(this);
var isChecked = $checkbox.is(':checked');
var settingsDiv = $('.mxchat-pinecone-settings');
var enabledNotice = settingsDiv.find('.mxchat-pinecone-enabled-notice, .mxch-notice-success');
// Update the UI immediately
if (isChecked) {
settingsDiv.slideDown(300);
enabledNotice.slideDown(300);
} else {
enabledNotice.slideUp(300);
settingsDiv.slideUp(300);
}
// Update sidebar badge for Pinecone
updateSidebarBadge('pinecone', isChecked);
});
// Set initial state based on current checkbox value
var currentToggle = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]');
if (currentToggle.length > 0) {
var settingsDiv = $('.mxchat-pinecone-settings');
var enabledNotice = settingsDiv.find('.mxchat-pinecone-enabled-notice, .mxch-notice-success');
if (currentToggle.is(':checked')) {
settingsDiv.show();
enabledNotice.show();
} else {
settingsDiv.hide();
enabledNotice.hide();
}
}
}
function initPineconeConnectionTest() {
$('#test-pinecone-connection').off('click.pinecone');
$('#test-pinecone-connection').on('click.pinecone', function() {
var button = $(this);
var resultDiv = $('#connection-test-result');
var apiKey = $('#mxchat_pinecone_api_key').val();
var host = $('#mxchat_pinecone_host').val();
var index = $('#mxchat_pinecone_index').val();
if (!apiKey || !host || !index) {
resultDiv.html('
Connection test failed. Please check your settings.
').show();
},
complete: function() {
button.prop('disabled', false).text('Test Connection');
}
});
});
}
function checkPineconeCompatibility() {
if ($('.mxchat-pinecone-compatibility-notice').length > 0) {
return;
}
var hasOldAddon = $('body').hasClass('mxchat-pinecone-addon-active') ||
$('.pcm-card').length > 0;
if (hasOldAddon) {
var compatibilityNotice = $(`
Pinecone Integration Notice: We've detected you have the Pinecone add-on installed.
Pinecone functionality is now built into the core plugin. You can safely deactivate the separate
Pinecone add-on after confirming your settings are migrated below.
`);
$('#mxchat-kb-tab-pinecone .mxchat-card').prepend(compatibilityNotice);
migratePineconeSettings();
}
}
function migratePineconeSettings() {
if (typeof ajaxurl !== 'undefined') {
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'mxchat_migrate_pinecone_settings',
_ajax_nonce: (typeof mxchatAdmin !== 'undefined') ? mxchatAdmin.setting_nonce : ''
},
success: function(response) {
if (response.success && response.data.migrated) {
location.reload();
}
},
error: function() {
//console.log('Pinecone settings migration not available');
}
});
}
}
// ============================================
// OpenAI Vector Store functionality
// ============================================
initVectorStoreFeatures = function() {
if ($('#openai-vectorstore').length === 0) {
return;
}
initVectorStoreToggle();
};
function initVectorStoreToggle() {
// Remove any existing handlers to prevent duplicates
var $toggleInput = $('input[name="mxchat_openai_vectorstore_options[mxchat_use_openai_vectorstore]"]');
$toggleInput.off('change.vectorstoreToggle');
// Ensure the success notice exists in the settings div (add if not present)
// Check for both the JS-added class and any existing PHP-rendered success notice
var settingsDiv = $('.mxchat-vectorstore-settings');
if (settingsDiv.length > 0 && settingsDiv.find('.mxch-notice-success').length === 0) {
var successNotice = $('
' +
'' +
'OpenAI Vector Store is enabled. Queries will search your Vector Store for relevant content.' +
'
');
settingsDiv.prepend(successNotice);
}
// Add the toggle handler for UI only (form submit will handle the actual saving)
$toggleInput.on('change.vectorstoreToggle', function() {
var $checkbox = $(this);
var isChecked = $checkbox.is(':checked');
var settingsDiv = $('.mxchat-vectorstore-settings');
var enabledNotice = settingsDiv.find('.mxchat-vectorstore-enabled-notice, .mxch-notice-success');
// Update the UI immediately
if (isChecked) {
settingsDiv.slideDown(300);
enabledNotice.slideDown(300);
} else {
enabledNotice.slideUp(300);
settingsDiv.slideUp(300);
}
// Update sidebar badge for OpenAI Vector Store
updateSidebarBadge('openai-vectorstore', isChecked);
});
// Set initial state based on current checkbox value
var currentToggle = $('input[name="mxchat_openai_vectorstore_options[mxchat_use_openai_vectorstore]"]');
if (currentToggle.length > 0) {
var settingsDiv = $('.mxchat-vectorstore-settings');
var enabledNotice = settingsDiv.find('.mxchat-vectorstore-enabled-notice, .mxch-notice-success');
if (currentToggle.is(':checked')) {
settingsDiv.show();
enabledNotice.show();
} else {
settingsDiv.hide();
enabledNotice.hide();
}
}
}
})(jQuery);