PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.5.2
MxChat – AI Chatbot & Content Generation for WordPress v2.5.2
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.5.2, at js/test-panel.js

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