PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.6.0
MxChat – AI Chatbot & Content Generation for WordPress v2.6.0
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | js/mxchat_transcripts.js +372 -39 2.0.52.6.0 View file →
@@ -1,53 +1,184 @@
1 - jQuery(document).ready(function($) {
2 - const selectButton = $('#mxchat-select-all-transcripts');
3 - let isSelected = false;
1 +jQuery(document).ready(function($) {
2 + // Current page state
3 + let currentPage = 1;
4 + const perPage = 50; // Display 50 sessions per page
5 + let totalPages = 1;
6 +
7 + // Track selected sessions for bulk delete
8 + let selectedSessions = new Set();
9 +
10 + // Select/Deselect All functionality
11 + const selectButton = $('#mxchat-select-all-transcripts');
12 + let isSelected = false;
13 +
14 + selectButton.click(function() {
15 + isSelected = !isSelected;
16 + $(this).toggleClass('selected');
4 17
5 - selectButton.click(function() {
6 - isSelected = !isSelected;
7 - $(this).toggleClass('selected');
8 -
9 - // Update button text
10 - const buttonText = $(this).find('.button-text');
11 - buttonText.text(isSelected ? 'Deselect All' : 'Select All');
12 -
13 - // Update checkboxes
14 - $('#mxchat-transcripts').find('input[type=checkbox]').prop('checked', isSelected);
15 - });
18 + // Update button text
19 + const buttonText = $(this).find('.button-text');
20 + buttonText.text(isSelected ? 'Deselect All' : 'Select All');
21 +
22 + // Update session selection (for current page)
23 + $('.mxchat-session-header').each(function() {
24 + const sessionId = $(this).data('session-id');
25 + const sessionContainer = $(this).closest('.mxchat-session');
26 + if (isSelected) {
27 + selectedSessions.add(sessionId);
28 + sessionContainer.addClass('selected');
29 + } else {
30 + selectedSessions.delete(sessionId);
31 + sessionContainer.removeClass('selected');
32 + }
33 + });
34 +
35 + updateDeleteButtonState();
36 + });
16 37
17 - // Search functionality
18 - $('#mxchat-search-transcripts').on('input', function() {
19 - var searchTerm = $(this).val().toLowerCase();
20 - $('.mxchat-session').each(function() {
21 - var sessionText = $(this).text().toLowerCase();
22 - $(this).toggle(sessionText.includes(searchTerm));
23 - });
24 - });
38 + // Search functionality
39 + $('#mxchat-search-transcripts').on('input', function() {
40 + var searchTerm = $(this).val().toLowerCase();
41 +
42 + if (searchTerm.length > 0) {
43 + // Reset to first page when searching
44 + currentPage = 1;
45 +
46 + // Load with search filter
47 + loadTranscripts(currentPage, searchTerm);
48 + } else {
49 + // Reset to first page with no search term
50 + currentPage = 1;
51 + loadTranscripts(currentPage, '');
52 + }
53 + });
25 54
26 - // Load transcripts
55 + // Initial load of transcripts
56 + loadTranscripts(currentPage, '');
57 +
58 + // Function to load transcripts with pagination
59 + function loadTranscripts(page, searchTerm = '') {
60 + $('#mxchat-transcripts').html('<div class="mxchat-loading">Loading transcripts...</div>');
61 +
27 62 $.ajax({
28 63 url: ajaxurl,
29 64 type: 'POST',
30 65 data: {
31 - action: 'mxchat_fetch_chat_history'
66 + action: 'mxchat_fetch_chat_history',
67 + page: page,
68 + per_page: perPage,
69 + search: searchTerm
32 70 },
33 71 success: function(response) {
34 - $('#mxchat-transcripts').html(response);
72 + $('#mxchat-transcripts').html(response.html);
73 + currentPage = response.page;
74 + totalPages = response.total_pages;
75 +
76 + // Reset selection state when page changes (but keep selectedSessions for bulk operations)
77 + isSelected = false;
78 + selectButton.removeClass('selected');
79 + selectButton.find('.button-text').text('Select All');
80 +
81 + // Restore selection state for sessions on this page
82 + $('.mxchat-session-header').each(function() {
83 + const sessionId = $(this).data('session-id');
84 + const sessionContainer = $(this).closest('.mxchat-session');
85 + if (selectedSessions.has(sessionId)) {
86 + sessionContainer.addClass('selected');
87 + }
88 + });
89 +
90 + updateDeleteButtonState();
91 +
92 + // Add click handlers to pagination buttons
93 + $('.mxchat-pagination-button').on('click', function() {
94 + var pageNum = $(this).data('page');
95 + loadTranscripts(pageNum, searchTerm);
96 +
97 + // Scroll to top of transcripts
98 + $('html, body').animate({
99 + scrollTop: $('#mxchat-transcripts').offset().top - 50
100 + }, 300);
101 + });
102 +
103 + // Re-attach event handlers for newly loaded content
104 + attachDynamicEventHandlers();
105 + },
106 + error: function(xhr, status, error) {
107 + $('#mxchat-transcripts').html('<div class="mxchat-error">Error loading chat transcripts. Please try again.</div>');
108 + console.error("AJAX Error: " + status + " - " + error);
35 109 }
36 110 });
111 + }
112 +
113 + // Attach event handlers to dynamically loaded content
114 + function attachDynamicEventHandlers() {
115 + // Handle individual delete button clicks
116 + $('.mxchat-delete-btn').off('click').on('click', function(e) {
117 + e.preventDefault();
118 + e.stopPropagation();
119 +
120 + const sessionId = $(this).data('session-id');
121 +
122 + if (!confirm("Are you sure you want to delete this chat session? This action cannot be undone.")) {
123 + return;
124 + }
125 +
126 + // Delete single session
127 + deleteSessions([sessionId]);
128 + });
129 +
130 + // Handle session header clicks for selection (bulk delete)
131 + $('.mxchat-session-header').off('click').on('click', function(e) {
132 + // Don't trigger if clicking the delete button
133 + if ($(e.target).hasClass('mxchat-delete-btn') || $(e.target).closest('.mxchat-delete-btn').length) {
134 + return;
135 + }
37 136
38 - // Your existing delete form submission code remains the same
39 - $('#mxchat-delete-form').submit(function(e) {
40 - e.preventDefault();
41 - var checkedSessionIds = $('input[name="delete_session_ids[]"]:checked').map(function() {
42 - return $(this).val();
43 - }).get();
137 + const sessionId = $(this).data('session-id');
138 + const sessionContainer = $(this).closest('.mxchat-session');
139 +
140 + if (selectedSessions.has(sessionId)) {
141 + selectedSessions.delete(sessionId);
142 + sessionContainer.removeClass('selected');
143 + } else {
144 + selectedSessions.add(sessionId);
145 + sessionContainer.addClass('selected');
146 + }
147 +
148 + updateDeleteButtonState();
149 + });
150 +
151 + // Handle clicks on Sources link for RAG context
152 + $('.mxchat-rag-link').off('click').on('click', function(e) {
153 + e.preventDefault();
154 + e.stopPropagation();
155 +
156 + const messageId = $(this).closest('.mxchat-message').data('message-id');
157 + if (messageId) {
158 + openRagContextModal(messageId);
159 + }
160 + });
161 + }
162 +
163 + // Update delete button state based on selections
164 + function updateDeleteButtonState() {
165 + const deleteButton = $('.delete-chats-button');
166 + if (selectedSessions.size > 0) {
167 + deleteButton.prop('disabled', false);
168 + } else {
169 + deleteButton.prop('disabled', true);
170 + }
171 + }
172 +
173 + // Function to delete sessions
174 + function deleteSessions(sessionIds) {
44 175 $.ajax({
45 176 url: ajaxurl,
46 177 type: 'POST',
47 178 data: {
48 179 action: 'mxchat_delete_chat_history',
49 - delete_session_ids: checkedSessionIds,
180 + delete_session_ids: sessionIds,
50 181 security: $('#mxchat_delete_chat_nonce').val()
51 182 },
52 183 success: function(response) {
53 184 var jsonResponse = JSON.parse(response);
@@ -52,31 +183,53 @@
52 183 success: function(response) {
53 184 var jsonResponse = JSON.parse(response);
54 185 if (jsonResponse.success) {
55 186 alert("Success: " + jsonResponse.success);
187 +
188 + // Remove deleted sessions from selectedSessions
189 + sessionIds.forEach(id => selectedSessions.delete(id));
190 +
56 191 } else if (jsonResponse.error) {
57 192 alert("Error: " + jsonResponse.error);
58 193 } else {
59 194 //console.log("Unexpected response format.");
60 195 }
61 - location.reload();
196 +
197 + // Reload the current page of transcripts
198 + loadTranscripts(currentPage);
62 199 },
63 200 error: function(xhr, status, error) {
64 201 //console.error("AJAX Error: " + status + " - " + error);
65 202 //console.log(xhr.responseText);
203 + alert("An error occurred while deleting chat sessions. Please try again.");
66 204 }
67 205 });
206 + }
207 +
208 + // Delete form submission (bulk delete)
209 + $('#mxchat-delete-form').submit(function(e) {
210 + e.preventDefault();
211 +
212 + if (selectedSessions.size === 0) {
213 + alert("Please select at least one chat session to delete.");
214 + return;
215 + }
216 +
217 + // Confirm deletion
218 + if (!confirm(`Are you sure you want to delete the selected ${selectedSessions.size} chat session(s)? This action cannot be undone.`)) {
219 + return;
220 + }
221 +
222 + // Convert Set to Array and delete
223 + deleteSessions(Array.from(selectedSessions));
68 224 });
69 225
70 -
71 -
72 -
73 - // Export functionality
226 + // Export functionality - this remains unchanged as it should export all transcripts
74 227 $('#mxchat-export-transcripts').on('click', function() {
75 228 var $button = $(this);
76 229 $button.prop('disabled', true).addClass('loading');
77 230
78 - // Create a form and submit it (this way handles large datasets better than AJAX)
231 + // Create a form and submit it
79 232 var $form = $('<form>', {
80 233 'method': 'post',
81 234 'action': ajaxurl
82 235 });
@@ -100,6 +253,186 @@
100 253 $button.prop('disabled', false).removeClass('loading');
101 254 }, 2000);
102 255 });
103 256
257 + // Chat Email Notification Modal functionality
104 258
105 -});
259 + // Open modal
260 + $('#mxchat-chat-email-notification-btn').on('click', function(e) {
261 + e.preventDefault();
262 + $('#mxchat-chat-email-notification-modal').fadeIn(300);
263 + });
264 +
265 + // Close modal
266 + $('.mxchat-chat-notification-modal-close, .mxchat-chat-notification-modal-cancel').on('click', function() {
267 + $('#mxchat-chat-email-notification-modal').fadeOut(300);
268 + });
269 +
270 + // Close modal on outside click
271 + $('#mxchat-chat-email-notification-modal').on('click', function(e) {
272 + if ($(e.target).is('#mxchat-chat-email-notification-modal')) {
273 + $(this).fadeOut(300);
274 + }
275 + });
276 +
277 + // Handle form submission - Let WordPress handle it normally for settings
278 + $('#mxchat-chat-email-notification-form').on('submit', function(e) {
279 + // Don't prevent default - let the form submit normally to WordPress options.php
280 + var $submitButton = $(this).find('button[type="submit"]');
281 + var originalText = $submitButton.text();
282 +
283 + // Just show a loading state
284 + $submitButton.text('Saving...').prop('disabled', true);
285 +
286 + // The form will submit normally and reload the page
287 + });
288 +
289 + // ========== RAG Context Modal Functions ==========
290 +
291 + // Open RAG context modal and fetch data
292 + function openRagContextModal(messageId) {
293 + const $modal = $('#mxchat-rag-context-modal');
294 + const $loading = $modal.find('.mxchat-rag-loading');
295 + const $content = $modal.find('.mxchat-rag-content');
296 +
297 + // Show modal with loading state
298 + $modal.fadeIn(300);
299 + $loading.show();
300 + $content.html('');
301 +
302 + // Fetch RAG context via AJAX
303 + $.ajax({
304 + url: ajaxurl,
305 + type: 'POST',
306 + data: {
307 + action: 'mxchat_get_rag_context',
308 + message_id: messageId
309 + },
310 + success: function(response) {
311 + $loading.hide();
312 + if (response.success && response.data) {
313 + renderRagContext(response.data, $content);
314 + } else {
315 + $content.html('<div class="mxchat-rag-error">Unable to load document context.</div>');
316 + }
317 + },
318 + error: function() {
319 + $loading.hide();
320 + $content.html('<div class="mxchat-rag-error">Error loading document context. Please try again.</div>');
321 + }
322 + });
323 + }
324 +
325 + // Render RAG context data in the modal
326 + function renderRagContext(data, $container) {
327 + let html = '';
328 +
329 + // Summary section
330 + html += '<div class="mxchat-rag-summary">';
331 + html += '<div class="mxchat-rag-summary-item">';
332 + html += '<span class="mxchat-rag-label">Knowledge Base:</span> ';
333 + html += '<span class="mxchat-rag-value">' + escapeHtml(data.knowledge_base_type || 'WordPress Database') + '</span>';
334 + html += '</div>';
335 + html += '<div class="mxchat-rag-summary-item">';
336 + html += '<span class="mxchat-rag-label">Similarity Threshold:</span> ';
337 + html += '<span class="mxchat-rag-value">' + Math.round((data.similarity_threshold || 0.35) * 100) + '%</span>';
338 + html += '</div>';
339 + html += '<div class="mxchat-rag-summary-item">';
340 + html += '<span class="mxchat-rag-label">Documents Checked:</span> ';
341 + html += '<span class="mxchat-rag-value">' + (data.total_documents_checked || 0) + '</span>';
342 + html += '</div>';
343 + html += '</div>';
344 +
345 + // Top matches section
346 + if (data.top_matches && data.top_matches.length > 0) {
347 + html += '<div class="mxchat-rag-matches">';
348 + html += '<h3>Retrieved Documents</h3>';
349 +
350 + data.top_matches.forEach(function(match, index) {
351 + const isUsed = match.used_for_context;
352 + const isAboveThreshold = match.above_threshold;
353 + const cardClass = isUsed ? 'mxchat-rag-match-used' : (isAboveThreshold ? 'mxchat-rag-match-above' : 'mxchat-rag-match-below');
354 + const statusIcon = isUsed ? '✅' : (isAboveThreshold ? '⚠️' : '❌');
355 + const statusLabel = isUsed ? 'Used for Response' : (isAboveThreshold ? 'Above Threshold' : 'Below Threshold');
356 +
357 + html += '<div class="mxchat-rag-match-card ' + cardClass + '">';
358 + html += '<div class="mxchat-rag-match-header">';
359 + html += '<span class="mxchat-rag-match-rank">#' + (index + 1) + '</span>';
360 + html += '<span class="mxchat-rag-match-score">' + match.similarity_percentage + '%</span>';
361 + html += '<span class="mxchat-rag-match-status ' + (isUsed ? 'status-used' : (isAboveThreshold ? 'status-above' : 'status-below')) + '">';
362 + html += statusIcon + ' ' + statusLabel;
363 + html += '</span>';
364 + html += '</div>';
365 +
366 + html += '<div class="mxchat-rag-match-source">';
367 + if (match.source_display && match.source_display.startsWith('http')) {
368 + html += '<a href="' + escapeHtml(match.source_display) + '" target="_blank" rel="noopener noreferrer">';
369 + html += '🔗 ' + escapeHtml(match.source_display);
370 + html += '</a>';
371 + } else {
372 + html += '📄 ' + escapeHtml(match.source_display || 'Content snippet');
373 + }
374 + html += '</div>';
375 +
376 + if (match.content_preview) {
377 + html += '<div class="mxchat-rag-match-preview">';
378 + html += escapeHtml(match.content_preview);
379 + html += '</div>';
380 + }
381 +
382 + // Show role restriction if not public
383 + if (match.role_restriction && match.role_restriction !== 'public') {
384 + html += '<div class="mxchat-rag-match-meta">';
385 + html += '<span class="mxchat-rag-role-badge">🔒 ' + escapeHtml(match.role_restriction) + '</span>';
386 + html += '</div>';
387 + }
388 +
389 + html += '</div>';
390 + });
391 +
392 + html += '</div>';
393 + } else {
394 + html += '<div class="mxchat-rag-no-matches">No document matches found for this response.</div>';
395 + }
396 +
397 + // Approved URLs section
398 + if (data.approved_urls && data.approved_urls.length > 0) {
399 + html += '<div class="mxchat-rag-urls">';
400 + html += '<h3>Approved URLs for Citations (' + data.approved_urls.length + ')</h3>';
401 + html += '<ul class="mxchat-rag-url-list">';
402 + data.approved_urls.forEach(function(url) {
403 + html += '<li><a href="' + escapeHtml(url) + '" target="_blank" rel="noopener noreferrer">' + escapeHtml(url) + '</a></li>';
404 + });
405 + html += '</ul>';
406 + html += '</div>';
407 + }
408 +
409 + $container.html(html);
410 + }
411 +
412 + // Helper function to escape HTML
413 + function escapeHtml(text) {
414 + if (!text) return '';
415 + const div = document.createElement('div');
416 + div.textContent = text;
417 + return div.innerHTML;
418 + }
419 +
420 + // Close RAG context modal
421 + $('.mxchat-rag-modal-close').on('click', function() {
422 + $('#mxchat-rag-context-modal').fadeOut(300);
423 + });
424 +
425 + // Close modal on outside click
426 + $('#mxchat-rag-context-modal').on('click', function(e) {
427 + if ($(e.target).is('#mxchat-rag-context-modal')) {
428 + $(this).fadeOut(300);
429 + }
430 + });
431 +
432 + // Close modal on Escape key
433 + $(document).on('keydown', function(e) {
434 + if (e.key === 'Escape') {
435 + $('#mxchat-rag-context-modal').fadeOut(300);
436 + }
437 + });
438 +});