PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.23
MxChat – AI Chatbot & Content Generation for WordPress v3.2.23
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
← All changes | js/content-selector.js +432 -2 3.2.21 → 3.2.23 View file →
@@ -267,11 +267,11 @@
267 267 <span class="${badgeClass}">${badgeText}</span>
268 268 ${isProcessed ? '<span class="mxchat-kb-last-updated">Last updated: ' + item.processed_date + '</span>' : ''}
269 269 </div>
270 270 <div class="mxchat-kb-content-meta">
271 - <span class="mxchat-kb-content-type">${item.type}</span>
271 + <span class="mxchat-kb-content-type">${item.type_label || item.type}</span>
272 272 <span class="mxchat-kb-content-date">${item.date}</span>
273 - <span class="mxchat-kb-content-words">${item.word_count} words</span>
273 + <span class="mxchat-kb-content-words">${item.size_label ? item.size_label : item.word_count + ' words'}</span>
274 274 </div>
275 275 <div class="mxchat-kb-content-excerpt">${item.excerpt}</div>
276 276 </div>
277 277 </div>
@@ -446,10 +446,22 @@
446 446 // Handle filter changes
447 447 $typeFilter.add($statusFilter).add($processedFilter).on('change', function() {
448 448 currentPage = 1; // Reset to first page on filter change
449 449 selectedItems.clear(); // Clear selection when filter changes
450 + updateMediaNote();
450 451 loadContent();
451 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();
452 464
453 465 // Process selected content
454 466 $processButton.on('click', function() {
455 467 if (selectedItems.size === 0) {
@@ -789,11 +801,429 @@
789 801 return;
790 802 }
791 803
792 804 initPineconeToggle();
805 + initPineconeIndexType();
793 806 initPineconeConnectionTest();
794 807 checkPineconeCompatibility();
795 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 + // 3e83e4: the LIVE chatbot's index type + host as saved, the migration
827 + // state as last known, and the index name the page loaded with (so a
828 + // suggested "-docs" name can be undone by switching back to Vector).
829 + var liveType = state.index_type === 'document' ? 'document' : 'vector';
830 + var storedHost = String(state.stored_host || '').toLowerCase();
831 + var storedIndex = String(state.stored_index || '');
832 + var originalIndexName = $.trim($('#mxchat_pinecone_index').val() || '');
833 + var migrationState = (state.migration && state.migration.status) ? state.migration : null;
834 + var ajaxUrl = (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.ajax_url) ? mxchatAdmin.ajax_url : ajaxurl;
835 + var nonce = (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.settings_nonce) ? mxchatAdmin.settings_nonce :
836 + ((typeof mxchatPromptsAdmin !== 'undefined' && mxchatPromptsAdmin.prompts_setting_nonce) ? mxchatPromptsAdmin.prompts_setting_nonce : '');
837 +
838 + function escapeText(text) { return $('<div>').text(String(text == null ? '' : text)).html(); }
839 + function notice(kind, text) {
840 + $result.html('<div class="mxch-notice mxch-notice-' + kind + '" style="margin: 12px 0;"><span>' + escapeText(text) + '</span></div>').show();
841 + }
842 + function setStatus(ok, text) {
843 + $('#mxchat-pinecone-docs-status').removeClass('mxch-notice-success mxch-notice-warning').addClass(ok ? 'mxch-notice-success' : 'mxch-notice-warning');
844 + $('#mxchat-pinecone-docs-status-text').text(text);
845 + }
846 + function currentType() { return $type.filter(':checked').val() || 'vector'; }
847 + function hostValue() {
848 + return $.trim($('#mxchat_pinecone_host').val() || '').replace(/^https?:\/\//i, '').replace(/\/+$/, '').toLowerCase();
849 + }
850 + function fmt(n) { n = parseInt(n, 10) || 0; return n.toLocaleString(); }
851 + // Does saving Document right now point the chatbot at an index whose copy is unfinished?
852 + function switchBlockedBy() {
853 + if (currentType() !== 'document' || liveType === 'document' || !migrationState) { return null; }
854 + if (migrationState.status !== 'running') { return null; }
855 + if (String(migrationState.target_host || '').toLowerCase() !== hostValue()) { return null; }
856 + return migrationState;
857 + }
858 + function refreshGate() {
859 + var isDoc = currentType() === 'document';
860 + $docs.toggle(isDoc);
861 + var blocked = isDoc && (hostValue() === '' || hostValue() !== verifiedHost);
862 + $('#mxchat-pinecone-docs-gate').toggle(blocked);
863 + var running = blocked ? null : switchBlockedBy();
864 + var $switchGate = $('#mxchat-pinecone-docs-switch-gate');
865 + if (running) {
866 + $('#mxchat-pinecone-docs-switch-gate-text').text(
867 + 'The copy into ' + (running.target_name || running.target_host) + ' is still running (' + fmt(running.copied) + ' of ' + fmt(running.total) +
868 + ' records). Let it finish before switching the chatbot to the document index.'
869 + );
870 + $switchGate.show();
871 + blocked = !$('#mxchat_pinecone_docs_switch_anyway').is(':checked');
872 + } else {
873 + $switchGate.hide();
874 + $('#mxchat_pinecone_docs_switch_anyway').prop('checked', false);
875 + }
876 + $submit.prop('disabled', blocked);
877 + }
878 + $('#mxchat_pinecone_docs_switch_anyway').off('change.pineconeDocs').on('change.pineconeDocs', refreshGate);
879 + $('#mxchat_pinecone_host').off('input.pineconeDocs change.pineconeDocs').on('input.pineconeDocs change.pineconeDocs', refreshGate);
880 +
881 + function post(data) {
882 + data.nonce = nonce;
883 + return $.post(ajaxUrl, data);
884 + }
885 +
886 + // ---- Index type change: a document index needs its own name, so when
887 + // the field still holds the vector index's name (or is empty) suggest a
888 + // free "<name>-docs" from the project; Vector restores the original.
889 + var suggestedName = '';
890 + function nameIsOurs() {
891 + var name = $.trim($('#mxchat_pinecone_index').val() || '');
892 + return name === '' || name === storedIndex || name === originalIndexName || name === suggestedName;
893 + }
894 + // When the host in the field is a checked document index, the name is
895 + // that index's own; otherwise a free "<name>-docs" for the create button.
896 + function suggestDocsName() {
897 + var name = $.trim($('#mxchat_pinecone_index').val() || '');
898 + var apiKey = $('#mxchat_pinecone_api_key').val();
899 + if (liveType === 'document' || !apiKey || currentType() !== 'document') { return; }
900 + if (!nameIsOurs()) { return; } // the owner typed their own
901 + var host = (hostValue() !== '' && hostValue() === verifiedHost) ? hostValue() : '';
902 + post({ action: 'mxchat_pinecone_docs_suggest_name', base: name || storedIndex, host: host, api_key: apiKey }).done(function (response) {
903 + if (response && response.success && response.data && response.data.name && currentType() === 'document' && nameIsOurs()) {
904 + suggestedName = response.data.name;
905 + $('#mxchat_pinecone_index').val(suggestedName);
906 + }
907 + });
908 + }
909 + $type.off('change.pineconeDocs').on('change.pineconeDocs', function () {
910 + if (currentType() === 'document') {
911 + suggestDocsName();
912 + } else if (suggestedName !== '' && $.trim($('#mxchat_pinecone_index').val() || '') === suggestedName) {
913 + $('#mxchat_pinecone_index').val(originalIndexName);
914 + }
915 + refreshGate();
916 + });
917 + // Pasting the checked document host resolves the name to that index's own.
918 + $('#mxchat_pinecone_host').on('change.pineconeDocsName', function () {
919 + if (currentType() === 'document' && hostValue() !== '' && hostValue() === verifiedHost) { suggestDocsName(); }
920 + });
921 + refreshGate();
922 + function failMessage(response, fallback) {
923 + if (response && response.data) {
924 + if (typeof response.data === 'string') { return response.data; }
925 + if (response.data.message) { return response.data.message; }
926 + }
927 + return fallback;
928 + }
929 +
930 + // ---- Create index for me
931 + $('#mxchat-pinecone-docs-create').off('click.pineconeDocs').on('click.pineconeDocs', function () {
932 + var $btn = $(this);
933 + var name = $.trim($('#mxchat_pinecone_index').val() || '');
934 + if (!name) {
935 + notice('error', 'Enter an Index Name first — the document index is created under that name.');
936 + return;
937 + }
938 + $btn.prop('disabled', true).text('Creating…');
939 + $result.hide();
940 + post({
941 + action: 'mxchat_pinecone_docs_create_index',
942 + index_name: name,
943 + api_key: $('#mxchat_pinecone_api_key').val(),
944 + cloud: $('#mxchat_pinecone_docs_cloud').val(),
945 + region: $('#mxchat_pinecone_docs_region').val(),
946 + language: $('#mxchat_pinecone_docs_language').val()
947 + }).done(function (response) {
948 + if (response && response.success) {
949 + $('#mxchat_pinecone_host').val(response.data.host);
950 + $('#mxchat_pinecone_index').val(response.data.name);
951 + if (response.data.vector_host && !$.trim($('#mxchat_pinecone_vector_host').val() || '')) {
952 + $('#mxchat_pinecone_vector_host').val(response.data.vector_host);
953 + }
954 + verifiedHost = String(response.data.host || '').toLowerCase();
955 + setStatus(true, 'Document index checked: ' + response.data.host + ' (' + response.data.dimension + ' dimensions).');
956 + notice(response.data.ready ? 'success' : 'info', response.data.message);
957 + refreshGate();
958 + } else {
959 + // A taken name comes back with a free suggestion — fill it in so the next click works.
960 + if (response && response.data && response.data.suggested) {
961 + suggestedName = response.data.suggested;
962 + $('#mxchat_pinecone_index').val(response.data.suggested);
963 + }
964 + notice('error', failMessage(response, 'Could not create the index.'));
965 + }
966 + }).fail(function () {
967 + notice('error', 'Could not reach the server to create the index.');
968 + }).always(function () {
969 + $btn.prop('disabled', false).text('Create index for me');
970 + });
971 + });
972 +
973 + // ---- Check index
974 + $('#mxchat-pinecone-docs-check').off('click.pineconeDocs').on('click.pineconeDocs', function () {
975 + var $btn = $(this);
976 + var host = hostValue();
977 + if (!host) {
978 + notice('error', 'Enter the Pinecone Host of the document index first.');
979 + return;
980 + }
981 + $btn.prop('disabled', true).text('Checking…');
982 + $result.hide();
983 + post({
984 + action: 'mxchat_pinecone_docs_check_index',
985 + host: host,
986 + api_key: $('#mxchat_pinecone_api_key').val()
987 + }).done(function (response) {
988 + if (response && response.success) {
989 + verifiedHost = String(response.data.host || host).toLowerCase();
990 + if (response.data.name) { $('#mxchat_pinecone_index').val(response.data.name); }
991 + if (response.data.vector_host && !$.trim($('#mxchat_pinecone_vector_host').val() || '')) {
992 + $('#mxchat_pinecone_vector_host').val(response.data.vector_host);
993 + }
994 + setStatus(true, 'Document index checked: ' + response.data.host + ' (' + response.data.dimension + ' dimensions).');
995 + notice('success', response.data.message);
996 + } else {
997 + setStatus(false, 'Not checked: this host cannot be used as a document index.');
998 + notice('error', failMessage(response, 'Check failed.'));
999 + }
1000 + refreshGate();
1001 + }).fail(function () {
1002 + notice('error', 'Could not reach the server to check the index.');
1003 + }).always(function () {
1004 + $btn.prop('disabled', false).text('Check index');
1005 + });
1006 + });
1007 +
1008 + // ---- Migrate (copy, page by page, resumable). 3e83e4: the copy runs
1009 + // against the document host in the form while the chatbot keeps
1010 + // answering from the vector index; a dropped request is retried with
1011 + // backoff, and a second click continues from the recorded position.
1012 + var $migrateBtn = $('#mxchat-pinecone-docs-migrate');
1013 + // e50d2e: "Copy every namespace" — offered once the start response
1014 + // reports records in other namespaces; the copy then walks every
1015 + // namespace of the source in one run, each into its own namespace on
1016 + // the document side. Disabled while a copy runs in this tab (the
1017 + // server extends a running copy on the next Migrate click, never
1018 + // underneath a step that is in flight).
1019 + var $allNs = $('#mxchat_pinecone_docs_all_namespaces');
1020 + var $allNsRow = $('#mxchat-pinecone-docs-all-namespaces');
1021 + var stepRetries = 0;
1022 + var STEP_RETRY_DELAYS = [1000, 2000, 4000, 8000, 16000];
1023 + function remainingOf(st) {
1024 + var total = parseInt(st.total, 10) || 0, copied = parseInt(st.copied, 10) || 0;
1025 + return Math.max(0, total - copied);
1026 + }
1027 + function otherNamespacesText(st) {
1028 + var other = st.other_namespaces || {};
1029 + var parts = [];
1030 + $.each(other, function (name, count) { parts.push(name + ' (' + fmt(count) + ')'); });
1031 + return parts.join(', ');
1032 + }
1033 + function otherNamespacesSummary(st) {
1034 + var records = 0, n = 0;
1035 + $.each(st.other_namespaces || {}, function (name, count) { records += parseInt(count, 10) || 0; n++; });
1036 + return n ? { records: records, count: n } : null;
1037 + }
1038 + function namespaceEntries(st) {
1039 + var out = [];
1040 + $.each(st.per_namespace || {}, function (key, e) { out.push($.extend({ key: key }, e)); });
1041 + return out;
1042 + }
1043 + function isMulti(st) { return !!st.all_namespaces || namespaceEntries(st).length > 1; }
1044 + function namespaceLine(e, sep) {
1045 + return e.key + ' ' + fmt(e.copied) + ' of ' + fmt(e.total) + (e.target_count ? (sep + 'the document index reports ' + fmt(e.target_count) + ')') : '');
1046 + }
1047 + function renderMigration(st) {
1048 + migrationState = (st && st.status) ? st : null;
1049 + var $progress = $('#mxchat-pinecone-docs-migrate-progress');
1050 + if (!st || !st.status) {
1051 + $progress.hide();
1052 + $('#mxchat-pinecone-docs-migrate-counts').text('');
1053 + $('#mxchat-pinecone-docs-delete-old').hide();
1054 + $allNsRow.hide();
1055 + refreshGate();
1056 + return;
1057 + }
1058 + var total = parseInt(st.total, 10) || 0;
1059 + var copied = parseInt(st.copied, 10) || 0;
1060 + var pct = total > 0 ? Math.min(100, Math.round(copied * 100 / total)) : (st.status === 'done' ? 100 : 0);
1061 + var multi = isMulti(st);
1062 + var entries = namespaceEntries(st);
1063 + var others = otherNamespacesSummary(st);
1064 + var label, suffix;
1065 + if (st.status === 'done') {
1066 + suffix = ' — done' + (st.target_count ? ('; the document index reports ' + fmt(st.target_count) + ' records') : '');
1067 + } else if (st.status === 'source_deleted') {
1068 + suffix = ' — old index deleted';
1069 + } else if (st.status === 'running') {
1070 + suffix = st.error ? (' — stopped: ' + st.error) : (' — copying, ' + fmt(remainingOf(st)) + ' remaining…');
1071 + } else {
1072 + suffix = '';
1073 + }
1074 + if (multi && st.status === 'running') {
1075 + var idx = 0, cur = null;
1076 + $.each(entries, function (i, e) { if (e.key === (st.current === '' ? '__default__' : st.current)) { idx = i + 1; cur = e; } });
1077 + label = (cur ? ('Namespace ' + idx + ' of ' + entries.length + ' (' + cur.key + ') — ' + fmt(cur.copied) + ' of ' + fmt(cur.total) + ' records copied; ') : '') +
1078 + fmt(copied) + ' of ' + fmt(total) + ' overall (' + pct + '%)' + (st.error ? (' — stopped: ' + st.error) : (', ' + fmt(remainingOf(st)) + ' remaining…'));
1079 + } else if (multi) {
1080 + label = fmt(copied) + ' of ' + fmt(total) + ' records copied across ' + entries.length + ' namespaces (' + pct + '%)' + suffix;
1081 + } else {
1082 + label = fmt(copied) + ' of ' + fmt(total) + ' records copied (' + pct + '%)' + suffix;
1083 + }
1084 + $progress.show().find('.mxch-progress-bar-fill').css('width', pct + '%');
1085 + $progress.find('.mxch-progress-label').text(label);
1086 + var counts;
1087 + if (multi) {
1088 + var lines = $.map(entries, function (e) { return namespaceLine(e, ' ('); });
1089 + counts = (st.source_name ? ('Source: ' + st.source_name + ' (' + fmt(total) + ' records in ' + entries.length + ' namespaces). ') : '') + lines.join(' · ') + '.';
1090 + } else {
1091 + counts = st.source_name ? ('Source: ' + st.source_name + ' (' + fmt(total) + ' records' + (st.namespace ? (' in namespace ' + st.namespace) : '') + ')') : '';
1092 + if (others) {
1093 + 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.';
1094 + }
1095 + }
1096 + $('#mxchat-pinecone-docs-migrate-counts').text(counts);
1097 + // The toggle appears once the source is known to have other namespaces, and stays on for a copy of every namespace.
1098 + $allNsRow.toggle(multi || !!others);
1099 + if (st.all_namespaces) { $allNs.prop('checked', true); }
1100 + $allNs.prop('disabled', $migrateBtn.prop('disabled'));
1101 + // Delete old index is never offered while the copy is known to leave namespaces behind (the server refuses too).
1102 + $('#mxchat-pinecone-docs-delete-old').toggle(st.status === 'done' && total > 0 && copied >= total && liveType === 'document' && !others);
1103 + refreshGate();
1104 + }
1105 + function migrateFinished(st) {
1106 + $migrateBtn.prop('disabled', false).text('Migrate');
1107 + $allNs.prop('disabled', false);
1108 + var total = parseInt(st.total, 10) || 0, copied = parseInt(st.copied, 10) || 0;
1109 + var reported = st.target_count ? (' The document index reports ' + fmt(st.target_count) + ' records.') : '';
1110 + var stillVector = liveType === 'document' ? '' : ' Your chatbot is still on the vector index — save the Pinecone settings to switch it over.';
1111 + var entries = namespaceEntries(st);
1112 + var others = otherNamespacesSummary(st);
1113 + if (isMulti(st)) {
1114 + var lines = $.map(entries, function (e) { return namespaceLine(e, ' ('); });
1115 + if (copied >= total) {
1116 + notice('success', 'Copy finished: ' + fmt(copied) + ' of ' + fmt(total) + ' records across ' + entries.length + ' namespaces are in the document index — ' + lines.join(', ') + '.' + stillVector);
1117 + } else {
1118 + var short = $.map(entries, function (e) { return (parseInt(e.copied, 10) || 0) < (parseInt(e.total, 10) || 0) ? namespaceLine(e, ' (') : null; });
1119 + notice('warning', 'The copy reached the end of every namespace after ' + fmt(copied) + ' records, but ' + fmt(total) + ' were expected. Short: ' + short.join(', ') + '.' + reported +
1120 + (parseInt(st.skipped, 10) ? (' ' + fmt(st.skipped) + ' records had no vector and were skipped.') : ''));
1121 + }
1122 + return;
1123 + }
1124 + if (copied >= total) {
1125 + notice(others ? 'warning' : 'success', 'Copy finished: ' + fmt(copied) + ' of ' + fmt(total) + ' records are in the document index.' + reported +
1126 + (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);
1127 + } else {
1128 + notice('warning', 'The copy reached the end of the source namespace after ' + fmt(copied) + ' records, but ' + fmt(total) + ' were expected.' + reported +
1129 + (others ? (' Records in other namespaces were not copied: ' + otherNamespacesText(st) + '.') : '') +
1130 + (parseInt(st.skipped, 10) ? (' ' + fmt(st.skipped) + ' records had no vector and were skipped.') : ''));
1131 + }
1132 + }
1133 + function migrateStep() {
1134 + post({ action: 'mxchat_pinecone_docs_migrate', mode: 'step', target_host: hostValue(), api_key: $('#mxchat_pinecone_api_key').val() }).done(function (response) {
1135 + stepRetries = 0;
1136 + if (response && response.success) {
1137 + renderMigration(response.data.state);
1138 + if (response.data.state && response.data.state.status === 'running') {
1139 + setTimeout(migrateStep, 250);
1140 + } else {
1141 + migrateFinished(response.data.state || {});
1142 + }
1143 + } else {
1144 + var st = (response && response.data && response.data.state) ? response.data.state : null;
1145 + if (st) { renderMigration(st); }
1146 + notice('error', failMessage(response, 'The copy stopped.') + (st ? (' Click Migrate to continue from record ' + fmt(st.copied) + '.') : ' Click Migrate to resume.'));
1147 + $migrateBtn.prop('disabled', false).text('Migrate');
1148 + $allNs.prop('disabled', false);
1149 + }
1150 + }).fail(function (xhr) {
1151 + // The server did not answer (timeout, 5xx, rate limit): wait and try the same step again.
1152 + if (stepRetries < STEP_RETRY_DELAYS.length) {
1153 + var delay = STEP_RETRY_DELAYS[stepRetries++];
1154 + 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.');
1155 + setTimeout(migrateStep, delay);
1156 + return;
1157 + }
1158 + stepRetries = 0;
1159 + 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.');
1160 + $migrateBtn.prop('disabled', false).text('Migrate');
1161 + $allNs.prop('disabled', false);
1162 + });
1163 + }
1164 + $migrateBtn.off('click.pineconeDocs').on('click.pineconeDocs', function () {
1165 + var source = $.trim($('#mxchat_pinecone_vector_host').val() || '');
1166 + if (!source) {
1167 + notice('error', 'Enter the host of the vector index to copy from.');
1168 + return;
1169 + }
1170 + if (!hostValue()) {
1171 + notice('error', 'Enter the host of the document index to copy into, or create one with Create index for me.');
1172 + return;
1173 + }
1174 + $migrateBtn.prop('disabled', true).text('Copying…');
1175 + $allNs.prop('disabled', true);
1176 + $result.hide();
1177 + stepRetries = 0;
1178 + 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) {
1179 + if (response && response.success) {
1180 + var st = response.data.state || {};
1181 + if (st.target_host && !verifiedHost) { verifiedHost = String(st.target_host).toLowerCase(); setStatus(true, 'Document index checked: ' + st.target_host + '.'); }
1182 + renderMigration(st);
1183 + if (st.status === 'running') {
1184 + migrateStep();
1185 + } else {
1186 + migrateFinished(st);
1187 + }
1188 + } else {
1189 + notice('error', failMessage(response, 'The copy could not start.'));
1190 + $migrateBtn.prop('disabled', false).text('Migrate');
1191 + $allNs.prop('disabled', false);
1192 + }
1193 + }).fail(function () {
1194 + notice('error', 'Could not reach the server to start the copy.');
1195 + $migrateBtn.prop('disabled', false).text('Migrate');
1196 + $allNs.prop('disabled', false);
1197 + });
1198 + });
1199 + renderMigration(state.migration || null);
1200 +
1201 + // ---- Delete old index (typed confirmation)
1202 + $('#mxchat-pinecone-docs-delete-old-btn').off('click.pineconeDocs').on('click.pineconeDocs', function () {
1203 + var $btn = $(this);
1204 + var confirmName = $.trim($('#mxchat_pinecone_docs_delete_confirm').val() || '');
1205 + if (!confirmName) {
1206 + notice('error', 'Type the old index name to confirm.');
1207 + return;
1208 + }
1209 + $btn.prop('disabled', true).text('Deleting…');
1210 + post({ action: 'mxchat_pinecone_docs_delete_old_index', confirm_name: confirmName }).done(function (response) {
1211 + if (response && response.success) {
1212 + notice('success', response.data.message);
1213 + $('#mxchat_pinecone_vector_host').val('');
1214 + $('#mxchat_pinecone_docs_delete_confirm').val('');
1215 + renderMigration(response.data.state);
1216 + } else {
1217 + notice('error', failMessage(response, 'The old index was not deleted.'));
1218 + }
1219 + }).fail(function () {
1220 + notice('error', 'Could not reach the server.');
1221 + }).always(function () {
1222 + $btn.prop('disabled', false).text('Delete old index');
1223 + });
1224 + });
1225 + }
796 1226
797 1227 function initPineconeToggle() {
798 1228 // Remove any existing handlers to prevent duplicates
799 1229 var $toggleInput = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]');