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 +217 -20 3.2.22 → 3.2.23 View file →
@@ -822,8 +822,16 @@
822 822 var stateEl = document.getElementById('mxchat-pinecone-docs-state');
823 823 var state = {};
824 824 try { state = stateEl ? JSON.parse(stateEl.textContent || '{}') : {}; } catch (e) { state = {}; }
825 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;
826 834 var ajaxUrl = (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.ajax_url) ? mxchatAdmin.ajax_url : ajaxurl;
827 835 var nonce = (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.settings_nonce) ? mxchatAdmin.settings_nonce :
828 836 ((typeof mxchatPromptsAdmin !== 'undefined' && mxchatPromptsAdmin.prompts_setting_nonce) ? mxchatPromptsAdmin.prompts_setting_nonce : '');
829 837
@@ -838,23 +846,80 @@
838 846 function currentType() { return $type.filter(':checked').val() || 'vector'; }
839 847 function hostValue() {
840 848 return $.trim($('#mxchat_pinecone_host').val() || '').replace(/^https?:\/\//i, '').replace(/\/+$/, '').toLowerCase();
841 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 + }
842 858 function refreshGate() {
843 859 var isDoc = currentType() === 'document';
844 860 $docs.toggle(isDoc);
845 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 + }
846 876 $submit.prop('disabled', blocked);
847 - $('#mxchat-pinecone-docs-gate').toggle(blocked);
848 877 }
849 - $type.off('change.pineconeDocs').on('change.pineconeDocs', refreshGate);
878 + $('#mxchat_pinecone_docs_switch_anyway').off('change.pineconeDocs').on('change.pineconeDocs', refreshGate);
850 879 $('#mxchat_pinecone_host').off('input.pineconeDocs change.pineconeDocs').on('input.pineconeDocs change.pineconeDocs', refreshGate);
851 - refreshGate();
852 880
853 881 function post(data) {
854 882 data.nonce = nonce;
855 883 return $.post(ajaxUrl, data);
856 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();
857 922 function failMessage(response, fallback) {
858 923 if (response && response.data) {
859 924 if (typeof response.data === 'string') { return response.data; }
860 925 if (response.data.message) { return response.data.message; }
@@ -890,8 +955,13 @@
890 955 setStatus(true, 'Document index checked: ' + response.data.host + ' (' + response.data.dimension + ' dimensions).');
891 956 notice(response.data.ready ? 'success' : 'info', response.data.message);
892 957 refreshGate();
893 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 + }
894 964 notice('error', failMessage(response, 'Could not create the index.'));
895 965 }
896 966 }).fail(function () {
897 967 notice('error', 'Could not reach the server to create the index.');
@@ -934,45 +1004,162 @@
934 1004 $btn.prop('disabled', false).text('Check index');
935 1005 });
936 1006 });
937 1007
938 - // ---- Migrate (copy, batch by batch, resumable)
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.
939 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 + }
940 1047 function renderMigration(st) {
1048 + migrationState = (st && st.status) ? st : null;
941 1049 var $progress = $('#mxchat-pinecone-docs-migrate-progress');
942 1050 if (!st || !st.status) {
943 1051 $progress.hide();
944 1052 $('#mxchat-pinecone-docs-migrate-counts').text('');
945 1053 $('#mxchat-pinecone-docs-delete-old').hide();
1054 + $allNsRow.hide();
1055 + refreshGate();
946 1056 return;
947 1057 }
948 1058 var total = parseInt(st.total, 10) || 0;
949 1059 var copied = parseInt(st.copied, 10) || 0;
950 1060 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…' : ''));
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 + }
952 1084 $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);
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();
956 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 + }
957 1133 function migrateStep() {
958 - post({ action: 'mxchat_pinecone_docs_migrate', mode: 'step' }).done(function (response) {
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;
959 1136 if (response && response.success) {
960 1137 renderMigration(response.data.state);
961 1138 if (response.data.state && response.data.state.status === 'running') {
962 - setTimeout(migrateStep, 150);
1139 + setTimeout(migrateStep, 250);
963 1140 } 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.');
1141 + migrateFinished(response.data.state || {});
966 1142 }
967 1143 } 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.');
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.'));
970 1147 $migrateBtn.prop('disabled', false).text('Migrate');
1148 + $allNs.prop('disabled', false);
971 1149 }
972 - }).fail(function () {
973 - notice('error', 'Could not reach the server. Click Migrate to resume.');
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.');
974 1160 $migrateBtn.prop('disabled', false).text('Migrate');
1161 + $allNs.prop('disabled', false);
975 1162 });
976 1163 }
977 1164 $migrateBtn.off('click.pineconeDocs').on('click.pineconeDocs', function () {
978 1165 var source = $.trim($('#mxchat_pinecone_vector_host').val() || '');
@@ -979,25 +1166,35 @@
979 1166 if (!source) {
980 1167 notice('error', 'Enter the host of the vector index to copy from.');
981 1168 return;
982 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 + }
983 1174 $migrateBtn.prop('disabled', true).text('Copying…');
1175 + $allNs.prop('disabled', true);
984 1176 $result.hide();
985 - post({ action: 'mxchat_pinecone_docs_migrate', mode: 'start', source_host: source }).done(function (response) {
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) {
986 1179 if (response && response.success) {
987 - renderMigration(response.data.state);
988 - if (response.data.state && response.data.state.status === 'running') {
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') {
989 1184 migrateStep();
990 1185 } else {
991 - $migrateBtn.prop('disabled', false).text('Migrate');
1186 + migrateFinished(st);
992 1187 }
993 1188 } else {
994 1189 notice('error', failMessage(response, 'The copy could not start.'));
995 1190 $migrateBtn.prop('disabled', false).text('Migrate');
1191 + $allNs.prop('disabled', false);
996 1192 }
997 1193 }).fail(function () {
998 1194 notice('error', 'Could not reach the server to start the copy.');
999 1195 $migrateBtn.prop('disabled', false).text('Migrate');
1196 + $allNs.prop('disabled', false);
1000 1197 });
1001 1198 });
1002 1199 renderMigration(state.migration || null);
1003 1200