PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.3.0
MxChat – AI Chatbot & Content Generation for WordPress v2.3.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
mxchat-basic / js / test-panel.js

test-panel.js in MxChat – AI Chatbot & Content Generation for WordPress 2.3.0, at js/test-panel.js

704 lines 27.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * MxChat Test Panel JavaScript
3 * Handles the testing interface for admins - always active when panel is open
4 */
5
6 class MxChatTestPanel {
7 constructor() {
8 this.panel = null;
9 this.tab = null;
10 this.isOpen = false;
11 this.lastQueryData = null;
12
13 this.init();
14 }
15
16 init() {
17 // Wait for DOM to be ready
18 if (document.readyState === 'loading') {
19 document.addEventListener('DOMContentLoaded', () => this.setup());
20 } else {
21 this.setup();
22 }
23 }
24
25 setup() {
26 this.createElements();
27 this.bindEvents();
28 this.setupChatInterception();
29 }
30
31 createElements() {
32 // Create the test tab
33 this.tab = document.createElement('div');
34 this.tab.className = 'mxchat-test-tab';
35 this.tab.innerHTML = 'MXCHAT DEBUGGING';
36 this.tab.title = 'Open MxChat Debug Panel';
37 document.body.appendChild(this.tab);
38
39 // Create the test panel
40 this.panel = document.createElement('div');
41 this.panel.className = 'mxchat-test-panel';
42 this.panel.innerHTML = this.getPanelHTML();
43 document.body.appendChild(this.panel);
44 }
45
46 getPanelHTML() {
47 return `
48 <div class="mxchat-test-header">
49 <h3>MxChat Debug Panel</h3>
50 <p>Always-on debugging for administrators</p>
51 <button class="mxchat-test-close" title="Close panel">&times;</button>
52 </div>
53
54 <div class="mxchat-test-content">
55 <!-- Session Management -->
56 <div class="mxchat-test-section">
57 <h4>Quick Actions</h4>
58 <button class="mxchat-test-btn danger" id="clear-chat-session">
59 Clear Chat Session
60 </button>
61 </div>
62
63 <!-- Query Analysis -->
64 <div class="mxchat-test-section">
65 <h4>Last Query Analysis</h4>
66 <div class="mxchat-test-info">
67 <strong>Similarity Threshold:</strong>
68 <span id="similarity-threshold">Loading...</span>
69 </div>
70 <div class="mxchat-test-info">
71 <strong>User Query:</strong>
72 <div id="last-query" class="query-display">Waiting for next query...</div>
73 </div>
74 <div class="mxchat-test-info">
75 <strong>Document Matches:</strong>
76 <div class="mxchat-test-results similarity-container" id="similarity-scores">
77 <div class="no-data-message">No query data yet</div>
78 </div>
79 </div>
80 <div class="mxchat-test-info">
81 <strong>Actions Triggered:</strong>
82 <div class="mxchat-test-results actions-container" id="action-scores">
83 <div class="no-data-message">No action data yet</div>
84 </div>
85 </div>
86 </div>
87
88 <!-- System Information -->
89 <div class="mxchat-test-section">
90 <h4>System Information</h4>
91 <div class="mxchat-test-info">
92 <strong>System Prompt:</strong>
93 <div class="mxchat-test-results system-prompt-container" id="system-prompt">Loading...</div>
94 </div>
95 <div class="mxchat-test-info">
96 <strong>Knowledge Base:</strong>
97 <span id="kb-status">Checking...</span>
98 </div>
99 </div>
100
101 <!-- Debug Console -->
102 <div class="mxchat-test-section">
103 <h4>Debug Log</h4>
104 <div class="mxchat-test-results debug-console-container" id="debug-console">
105 <div class="debug-entry">Debug panel ready - monitoring chat activity...</div>
106 </div>
107 <button class="mxchat-test-btn secondary" id="clear-debug">
108 Clear Log
109 </button>
110 </div>
111 </div>
112 `;
113 }
114
115
116 bindEvents() {
117 // Tab click to toggle panel
118 this.tab.addEventListener('click', () => this.togglePanel());
119
120 // Close button
121 const closeBtn = this.panel.querySelector('.mxchat-test-close');
122 closeBtn.addEventListener('click', () => this.closePanel());
123
124 // Action buttons
125 this.bindActionButtons();
126
127 // Keep escape key to close (useful shortcut)
128 document.addEventListener('keydown', (e) => {
129 if (e.key === 'Escape' && this.isOpen) {
130 this.closePanel();
131 }
132 });
133 }
134
135 bindActionButtons() {
136 // Clear chat session
137 this.panel.querySelector('#clear-chat-session').addEventListener('click', () => {
138 this.clearChatSession();
139 });
140
141 // Clear debug console
142 this.panel.querySelector('#clear-debug').addEventListener('click', () => {
143 this.clearDebugConsole();
144 });
145 }
146
147 togglePanel() {
148 if (this.isOpen) {
149 this.closePanel();
150 } else {
151 this.openPanel();
152 }
153 }
154
155 openPanel() {
156 this.panel.classList.add('open');
157 this.isOpen = true;
158 this.tab.style.display = 'none';
159 this.loadSystemInfo();
160 this.log('Debug panel opened - capturing chat data automatically');
161 }
162
163 closePanel() {
164 this.panel.classList.remove('open');
165 this.isOpen = false;
166 this.tab.style.display = 'block';
167 }
168
169 setupChatInterception() {
170 // Set up interception for chat responses to capture testing data
171 this.interceptChatResponses();
172 this.log('Chat monitoring initialized');
173 }
174
175 interceptChatResponses() {
176 // Store reference to the test panel instance
177 window.mxchatTestPanelInstance = this;
178
179 // Intercept jQuery AJAX calls (for regular chat)
180 if (window.jQuery) {
181 const originalAjax = jQuery.ajax;
182
183 jQuery.ajax = function(options) {
184 const originalSuccess = options.success;
185
186 options.success = function(data, textStatus, jqXHR) {
187
188 // Check if this is a chat request
189 if (options.data &&
190 (options.data.action === 'mxchat_handle_chat_request' ||
191 options.data.action === 'mxchat_stream_chat')) {
192
193 const testPanel = window.mxchatTestPanelInstance;
194
195 // Always try to handle testing data if it exists (no toggle check)
196 if (testPanel && data && data.testing_data) {
197 testPanel.handleTestingData(data.testing_data);
198 } else if (testPanel && data && data.data && data.data.testing_data) {
199 // Check if data is nested
200 testPanel.handleTestingData(data.data.testing_data);
201 }
202 }
203
204 // Call original success handler
205 if (originalSuccess) {
206 originalSuccess.call(this, data, textStatus, jqXHR);
207 }
208 };
209
210 return originalAjax.call(this, options);
211 };
212 }
213
214 // Also intercept fetch API calls (for streaming)
215 const originalFetch = window.fetch;
216
217 window.fetch = (...args) => {
218 return originalFetch(...args).then(response => {
219 // Check if this is a chat request
220 if (args[0].includes('admin-ajax.php') || args[0].includes('mxchat')) {
221 // For streaming responses that return JSON instead of streams
222 const contentType = response.headers.get('content-type');
223 if (contentType && contentType.includes('application/json')) {
224 response.clone().json().then(data => {
225 const testPanel = window.mxchatTestPanelInstance;
226
227 // Always try to handle testing data if it exists (no toggle check)
228 if (testPanel && data && data.testing_data) {
229 testPanel.handleTestingData(data.testing_data);
230 } else if (testPanel && data && data.data && data.data.testing_data) {
231 // Check nested data
232 testPanel.handleTestingData(data.data.testing_data);
233 }
234 }).catch(() => {
235 // Ignore JSON parsing errors
236 });
237 }
238 }
239 return response;
240 });
241 };
242
243 this.log('Chat interception active for jQuery and fetch requests');
244 }
245
246 handleTestingData(testingData) {
247 this.log('📊 Chat data captured from response');
248
249 // Update query analysis section
250 this.updateLastQuery(testingData.query || 'No query', testingData.top_matches || []);
251 this.updateTopMatches(testingData.top_matches || [], testingData.similarity_threshold || 0.75);
252
253 // NEW: Update action matches
254 this.updateActionMatches(testingData.action_matches || []);
255
256 // Log additional info
257 if (testingData.knowledge_base_type) {
258 this.log(`📚 Knowledge Base: ${testingData.knowledge_base_type}`);
259 }
260 if (testingData.similarity_threshold) {
261 this.log(`🎯 Similarity Threshold: ${(testingData.similarity_threshold * 100)}%`);
262 }
263
264 // Show summary in debug console
265 if (testingData.top_matches && testingData.top_matches.length > 0) {
266 const aboveThreshold = testingData.top_matches.filter(match => match.above_threshold).length;
267 const belowThreshold = testingData.top_matches.length - aboveThreshold;
268 const highestScore = testingData.top_matches[0].similarity_percentage;
269
270 this.log(`�
271 Analysis: ${aboveThreshold} above threshold, ${belowThreshold} below threshold`);
272 this.log(`🏆 Highest similarity: ${highestScore}%`);
273 } else {
274 this.log('⚠️ No document matches found');
275 }
276
277 // NEW: Log action summary
278 if (testingData.action_matches && testingData.action_matches.length > 0) {
279 const triggeredAction = testingData.action_matches.find(action => action.triggered);
280 if (triggeredAction) {
281 this.log(`🎯 Action Triggered: ${triggeredAction.intent_label} (${triggeredAction.similarity_percentage}%)`);
282 } else {
283 const highestAction = testingData.action_matches[0];
284 this.log(`🚫 No actions triggered - Highest: ${highestAction.intent_label} (${highestAction.similarity_percentage}%)`);
285 }
286 } else {
287 this.log('📝 No actions checked');
288 }
289 }
290
291 updateActionMatches(actionMatches) {
292 const actionsEl = this.panel.querySelector('#action-scores');
293
294 if (!actionMatches || actionMatches.length === 0) {
295 actionsEl.innerHTML = '<div class="no-data-message">No actions checked</div>';
296 return;
297 }
298
299 let html = `<div class="actions-header">
300 <strong>Top ${actionMatches.length} actions checked</strong>
301 </div>`;
302
303 actionMatches.forEach((action, index) => {
304 const isTriggered = action.triggered;
305 const isAboveThreshold = action.above_threshold;
306 const statusIcon = isTriggered ? '🎯' : (isAboveThreshold ? '⚠️' : '❌');
307
308 // Determine the correct label based on status
309 let statusLabel;
310 if (isTriggered) {
311 statusLabel = 'TRIGGERED';
312 } else if (isAboveThreshold) {
313 statusLabel = 'Above threshold';
314 } else {
315 statusLabel = 'Below threshold';
316 }
317
318 // Determine card styling
319 const cardClass = isTriggered ? 'action-triggered' : (isAboveThreshold ? 'action-above-threshold' : 'action-below-threshold');
320
321 html += `
322 <div class="action-card ${cardClass}">
323 <div class="action-line">
324 <span class="action-icon">${statusIcon}</span>
325 <span class="action-name">${action.intent_label}</span>
326 <span class="action-score">${action.similarity_percentage}%</span>
327 </div>
328 <div class="action-details">
329 <span class="action-status">${statusLabel}</span>
330 <span class="action-threshold">Threshold: ${action.threshold_percentage}%</span>
331 </div>
332 </div>
333 `;
334 });
335
336 actionsEl.innerHTML = html;
337 }
338
339 updateTopMatches(topMatches, threshold) {
340 const scoresEl = this.panel.querySelector('#similarity-scores');
341
342 if (!topMatches || topMatches.length === 0) {
343 scoresEl.innerHTML = '<div class="no-data-message">No similarity data available</div>';
344 return;
345 }
346
347 let html = `<div class="matches-header">
348 <strong>Top ${topMatches.length} matches</strong>
349 </div>`;
350
351 topMatches.forEach((match, index) => {
352 const isAboveThreshold = match.above_threshold;
353 const isUsedForContext = match.used_for_context; // Use the actual flag from PHP
354 const statusIcon = isAboveThreshold ? '✓' : '✗';
355
356 // Determine the correct label based on actual usage
357 let contextLabel;
358 if (isUsedForContext) {
359 contextLabel = 'Used for AI context';
360 } else if (isAboveThreshold) {
361 contextLabel = 'Above threshold (not used)';
362 } else {
363 contextLabel = 'Below threshold';
364 }
365
366 // Determine card styling - should be based on whether it was actually used
367 const cardClass = isUsedForContext ? 'above-threshold' : 'below-threshold';
368
369 html += `
370 <div class="match-card ${cardClass}">
371 <div class="match-header">
372 <div class="match-title">
373 <span class="status-icon">${statusIcon}</span>
374 <span class="similarity-score">${match.similarity_percentage}%</span>
375 </div>
376 <span class="context-label">${contextLabel}</span>
377 </div>
378 <div class="match-source">
379 ${match.source_display.startsWith('http') ?
380 `<span class="source-icon link-icon">🔗</span> ${match.source_display}` :
381 `<span class="source-icon doc-icon">📄</span> ${match.source_display}`
382 }
383 </div>
384 </div>
385 `;
386 });
387
388 scoresEl.innerHTML = html;
389 }
390
391 updateLastQuery(query, topMatches) {
392 const queryEl = this.panel.querySelector('#last-query');
393 queryEl.textContent = query;
394
395 this.lastQueryData = {
396 query,
397 topMatches,
398 timestamp: new Date()
399 };
400 }
401
402 clearChatSession() {
403 // Get current session ID from cookie (most reliable)
404 const sessionId = this.getCookie('mxchat_session_id') || this.getCurrentSessionId();
405
406 if (!sessionId) {
407 this.log('❌ No active session found');
408 return;
409 }
410
411 this.log(`🔍 Current session ID: ${sessionId}`);
412 this.log('🧹 Starting fresh session...');
413
414 // Generate new session ID (using your existing format)
415 const newSessionId = 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
416
417 // Clear the old cookie and set new one immediately
418 this.clearMxChatCookie();
419 this.setChatSession(newSessionId);
420
421 this.log(`🆕 New session ID: ${newSessionId}`);
422
423 // Call backend to clear old session data
424 fetch(mxchatTestData.ajaxUrl, {
425 method: 'POST',
426 headers: {
427 'Content-Type': 'application/x-www-form-urlencoded',
428 },
429 body: new URLSearchParams({
430 action: 'mxchat_start_fresh_session',
431 nonce: mxchatTestData.nonce,
432 old_session_id: sessionId,
433 new_session_id: newSessionId
434 })
435 })
436 .then(response => response.json())
437 .then(data => {
438 if (data.success) {
439 this.log('�
440 Backend session cleared: ' + data.data.message);
441
442 // Update the session ID everywhere in the DOM
443 this.updateSessionIdEverywhere(newSessionId);
444
445 // Clear the chat UI
446 this.clearChatUI();
447
448 // Show popular questions again
449 const popularQuestions = document.querySelector('#mxchat-popular-questions');
450 if (popularQuestions) {
451 popularQuestions.style.display = 'block';
452 }
453
454 // Clear testing data displays
455 this.updateLastQuery('New session started', []);
456 this.updateTopMatches([], 0);
457
458 this.log('🎉 Fresh session started successfully');
459
460 } else {
461 this.log('❌ Error clearing session: ' + (data.data?.message || 'Unknown error'));
462 }
463 })
464 .catch(error => {
465 console.error('Error clearing chat session:', error);
466 this.log('🔌 Connection error when clearing session');
467 });
468 }
469
470 // Helper function to get cookie (same as your existing one)
471 getCookie(name) {
472 let value = "; " + document.cookie;
473 let parts = value.split("; " + name + "=");
474 if (parts.length == 2) return parts.pop().split(";").shift();
475 }
476
477 // Helper function to set session cookie (same as your existing one)
478 setChatSession(sessionId) {
479 // Set the cookie with a 24-hour expiration (86400 seconds)
480 document.cookie = "mxchat_session_id=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
481 }
482
483 // Helper function to clear the MxChat session cookie
484 clearMxChatCookie() {
485 // Clear the cookie by setting it to expire in the past
486 document.cookie = "mxchat_session_id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax";
487 this.log('🍪 Session cookie cleared');
488 }
489
490 updateSessionIdEverywhere(newSessionId) {
491 // Update global session ID variable if it exists
492 if (window.mxchatSessionId) {
493 window.mxchatSessionId = newSessionId;
494 }
495
496 // Update session ID in chat input data attribute
497 const chatInput = document.querySelector('#chat-input');
498 if (chatInput) {
499 chatInput.dataset.sessionId = newSessionId;
500 }
501
502 // Update any hidden session ID fields
503 const sessionInputs = document.querySelectorAll('input[name="session_id"]');
504 sessionInputs.forEach(input => {
505 input.value = newSessionId;
506 });
507
508 // Update any data attributes that store session ID
509 const elementsWithSessionId = document.querySelectorAll('[data-session-id]');
510 elementsWithSessionId.forEach(element => {
511 element.dataset.sessionId = newSessionId;
512 });
513
514 // Update URL parameter if it exists
515 if (window.location.search.includes('session_id=')) {
516 const url = new URL(window.location);
517 url.searchParams.set('session_id', newSessionId);
518 window.history.replaceState({}, '', url);
519 }
520
521 this.log('🔄 Session ID updated everywhere in DOM');
522 }
523
524 clearChatUI() {
525 const chatBox = document.querySelector('#chat-box');
526 if (chatBox) {
527 // Remove all messages (both user and bot)
528 const allMessages = chatBox.querySelectorAll('.bot-message, .user-message');
529 allMessages.forEach(msg => {
530 // Keep the first bot message if it's a welcome message
531 if (msg === chatBox.querySelector('.bot-message') &&
532 msg.textContent.toLowerCase().includes('welcome')) {
533 return; // Keep welcome message
534 }
535 msg.remove();
536 });
537 this.log('🧹 Chat UI cleared');
538 }
539
540 // Clear chat input
541 const chatInput = document.querySelector('#chat-input');
542 if (chatInput) {
543 chatInput.value = '';
544 }
545 }
546
547 getCurrentSessionId() {
548 // First try to get from cookie (most reliable)
549 const cookieSessionId = this.getCookie('mxchat_session_id');
550 if (cookieSessionId) {
551 return cookieSessionId;
552 }
553
554 // Try to get session ID from various DOM sources
555 const chatInput = document.querySelector('#chat-input');
556 if (chatInput && chatInput.dataset.sessionId) {
557 return chatInput.dataset.sessionId;
558 }
559
560 // Try to get from URL parameters
561 const urlParams = new URLSearchParams(window.location.search);
562 const sessionFromUrl = urlParams.get('session_id');
563 if (sessionFromUrl) {
564 return sessionFromUrl;
565 }
566
567 // Try to get from global variables
568 if (window.chatSessionId) {
569 return window.chatSessionId;
570 }
571
572 // Generate a temporary session ID if none found
573 return 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
574 }
575
576 loadSystemInfo() {
577 // Load system information from backend
578 this.updateSimilarityThreshold();
579 this.updateSystemPrompt();
580 this.updateKnowledgeBaseStatus();
581 }
582
583 updateSimilarityThreshold() {
584 fetch(mxchatTestData.ajaxUrl, {
585 method: 'POST',
586 headers: {
587 'Content-Type': 'application/x-www-form-urlencoded',
588 },
589 body: new URLSearchParams({
590 action: 'mxchat_get_similarity_threshold',
591 nonce: mxchatTestData.nonce
592 })
593 })
594 .then(response => response.json())
595 .then(data => {
596 const thresholdEl = this.panel.querySelector('#similarity-threshold');
597 if (data.success) {
598 thresholdEl.innerHTML = `<code>${data.data.threshold_percentage}</code>`;
599 } else {
600 thresholdEl.innerHTML = '<span class="error-text">Error loading threshold</span>';
601 }
602 })
603 .catch(error => {
604 console.error('Error fetching similarity threshold:', error);
605 const thresholdEl = this.panel.querySelector('#similarity-threshold');
606 thresholdEl.innerHTML = '<span class="error-text">Connection error</span>';
607 });
608 }
609
610 updateSystemPrompt() {
611 const promptEl = this.panel.querySelector('#system-prompt');
612 promptEl.textContent = 'Loading system prompt...';
613
614 fetch(mxchatTestData.ajaxUrl, {
615 method: 'POST',
616 headers: {
617 'Content-Type': 'application/x-www-form-urlencoded',
618 },
619 body: new URLSearchParams({
620 action: 'mxchat_get_system_info',
621 nonce: mxchatTestData.nonce
622 })
623 })
624 .then(response => response.json())
625 .then(data => {
626 if (data.success) {
627 promptEl.textContent = data.data.system_prompt || 'No system prompt configured';
628 this.log(`🤖 Model: ${data.data.selected_model}`);
629 this.log(`🔑 API Status: ${JSON.stringify(data.data.api_status)}`);
630 } else {
631 promptEl.textContent = 'Error loading system prompt';
632 }
633 })
634 .catch(error => {
635 console.error('Error fetching system info:', error);
636 promptEl.textContent = 'Connection error';
637 });
638 }
639
640 updateKnowledgeBaseStatus() {
641 const statusEl = this.panel.querySelector('#kb-status');
642 statusEl.innerHTML = 'Checking...';
643
644 fetch(mxchatTestData.ajaxUrl, {
645 method: 'POST',
646 headers: {
647 'Content-Type': 'application/x-www-form-urlencoded',
648 },
649 body: new URLSearchParams({
650 action: 'mxchat_get_kb_status',
651 nonce: mxchatTestData.nonce
652 })
653 })
654 .then(response => response.json())
655 .then(data => {
656 if (data.success) {
657 const kbData = data.data;
658 statusEl.innerHTML = `<span class="success-text">${kbData.status}</span> (${kbData.type} - ${kbData.documents})`;
659 } else {
660 statusEl.innerHTML = '<span class="error-text">Error loading KB status</span>';
661 }
662 })
663 .catch(error => {
664 console.error('Error fetching KB status:', error);
665 statusEl.innerHTML = '<span class="error-text">Connection error</span>';
666 });
667 }
668
669 clearDebugConsole() {
670 const console = this.panel.querySelector('#debug-console');
671 console.innerHTML = '<div class="debug-entry">Debug console cleared...</div>';
672 }
673
674 log(message) {
675 const console = this.panel.querySelector('#debug-console');
676 const timestamp = new Date().toLocaleTimeString();
677 const logEntry = document.createElement('div');
678 logEntry.className = 'debug-entry';
679 logEntry.innerHTML = `<span class="debug-timestamp">[${timestamp}]</span> ${message}`;
680 console.appendChild(logEntry);
681 console.scrollTop = console.scrollHeight;
682
683 // Keep only last 50 entries to prevent memory issues
684 const entries = console.querySelectorAll('.debug-entry');
685 if (entries.length > 50) {
686 entries[0].remove();
687 }
688 }
689 }
690
691 // Initialize the test panel when the script loads
692 document.addEventListener('DOMContentLoaded', function() {
693 // Only initialize if user is admin and testing is enabled
694 if (window.mxchatTestingEnabled) {
695 window.mxchatTestPanel = new MxChatTestPanel();
696 }
697 });
698
699 // Global function to enable testing mode programmatically
700 window.enableMxChatTesting = function() {
701 if (!window.mxchatTestPanel) {
702 window.mxchatTestPanel = new MxChatTestPanel();
703 }
704 };