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

872 lines 34.1 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 // Group matches by source URL
405 const groupedByUrl = {};
406 topMatches.forEach((match) => {
407 const url = match.source_display || 'Unknown';
408 if (!groupedByUrl[url]) {
409 groupedByUrl[url] = {
410 url: url,
411 isUrl: url.startsWith('http'),
412 bestScore: 0,
413 usedForContext: false,
414 totalChunks: match.total_chunks || 1,
415 matchedChunks: [],
416 isChunked: match.is_chunk || false
417 };
418 }
419
420 // Track best score
421 if (match.similarity_percentage > groupedByUrl[url].bestScore) {
422 groupedByUrl[url].bestScore = match.similarity_percentage;
423 }
424
425 // Track if any chunk was used for context
426 if (match.used_for_context) {
427 groupedByUrl[url].usedForContext = true;
428 }
429
430 // Add chunk info
431 groupedByUrl[url].matchedChunks.push({
432 chunkIndex: match.chunk_index,
433 score: match.similarity_percentage,
434 usedForContext: match.used_for_context,
435 aboveThreshold: match.above_threshold
436 });
437 });
438
439 // Convert to array and sort by best score
440 const urlGroups = Object.values(groupedByUrl).sort((a, b) => b.bestScore - a.bestScore);
441
442 // Count unique URLs used for context
443 const usedUrlCount = urlGroups.filter(g => g.usedForContext).length;
444
445 let html = `<div class="matches-header">
446 <strong>${usedUrlCount} entr${usedUrlCount === 1 ? 'y' : 'ies'} used for AI context</strong>
447 <span class="matches-subheader">(from ${topMatches.length} chunk matches)</span>
448 </div>`;
449
450 urlGroups.forEach((group, groupIndex) => {
451 const cardClass = group.usedForContext ? 'above-threshold' : 'below-threshold';
452 const statusIcon = group.usedForContext ? '✓' : '✗';
453 const contextLabel = group.usedForContext ? 'Used for AI context' : 'Not used';
454
455 // Build chunk summary
456 let chunkSummary = '';
457 if (group.isChunked && group.totalChunks > 1) {
458 const usedChunkCount = group.matchedChunks.filter(c => c.usedForContext).length;
459 chunkSummary = `<span class="chunk-summary">${usedChunkCount}/${group.totalChunks} chunks matched</span>`;
460 }
461
462 // Check if this entry has multiple matched chunks to show expand toggle
463 const hasMultipleChunks = group.matchedChunks.length > 1;
464 const expandToggle = hasMultipleChunks
465 ? `<span class="chunk-expand-toggle" data-group="${groupIndex}">Show chunks</span>`
466 : '';
467
468 html += `
469 <div class="match-card ${cardClass}">
470 <div class="match-header">
471 <div class="match-title">
472 <span class="status-icon">${statusIcon}</span>
473 <span class="similarity-score">${group.bestScore}%</span>
474 ${chunkSummary}
475 </div>
476 <span class="context-label">${contextLabel}</span>
477 </div>
478 <div class="match-source">
479 ${group.isUrl ?
480 `<span class="source-icon link-icon">🔗</span> ${group.url}` :
481 `<span class="source-icon doc-icon">📄</span> ${group.url}`
482 }
483 </div>
484 ${expandToggle}
485 ${hasMultipleChunks ? this.renderChunkDetails(group.matchedChunks, groupIndex) : ''}
486 </div>
487 `;
488 });
489
490 scoresEl.innerHTML = html;
491
492 // Add click handlers for expand toggles
493 scoresEl.querySelectorAll('.chunk-expand-toggle').forEach(toggle => {
494 toggle.addEventListener('click', (e) => {
495 const groupId = e.target.dataset.group;
496 const details = scoresEl.querySelector(`.chunk-details[data-group="${groupId}"]`);
497 if (details) {
498 const isExpanded = details.classList.toggle('expanded');
499 e.target.textContent = isExpanded ? '▼ Hide chunks' : '▶ Show chunks';
500 }
501 });
502 });
503 }
504
505 renderChunkDetails(chunks, groupIndex) {
506 // Sort chunks by chunk index
507 const sortedChunks = [...chunks].sort((a, b) => (a.chunkIndex || 0) - (b.chunkIndex || 0));
508
509 let html = `<div class="chunk-details" data-group="${groupIndex}">`;
510
511 sortedChunks.forEach(chunk => {
512 const chunkNum = (chunk.chunkIndex !== null && chunk.chunkIndex !== undefined)
513 ? chunk.chunkIndex + 1
514 : '?';
515 const statusClass = chunk.usedForContext ? 'chunk-used' : 'chunk-not-used';
516 const statusIcon = chunk.usedForContext ? '✓' : '○';
517
518 html += `
519 <div class="chunk-detail-row ${statusClass}">
520 <span class="chunk-detail-icon">${statusIcon}</span>
521 <span class="chunk-detail-num">Chunk ${chunkNum}</span>
522 <span class="chunk-detail-score">${chunk.score}%</span>
523 </div>
524 `;
525 });
526
527 html += '</div>';
528 return html;
529 }
530
531 updateLastQuery(query, topMatches) {
532 const queryEl = this.panel.querySelector('#last-query');
533 queryEl.textContent = query;
534
535 this.lastQueryData = {
536 query,
537 topMatches,
538 timestamp: new Date()
539 };
540 }
541
542 clearChatSession() {
543 // Get current session ID from cookie (most reliable)
544 const sessionId = this.getCookie('mxchat_session_id') || this.getCurrentSessionId();
545
546 if (!sessionId) {
547 this.log('❌ No active session found');
548 return;
549 }
550
551 this.log(`🔍 Current session ID: ${sessionId}`);
552 this.log('🧹 Starting fresh session...');
553
554 // Generate new session ID (using your existing format)
555 const newSessionId = 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
556
557 // Clear the old cookie and set new one immediately
558 this.clearMxChatCookie();
559 this.setChatSession(newSessionId);
560
561 this.log(`🆕 New session ID: ${newSessionId}`);
562
563 // Call backend to clear old session data
564 fetch(mxchatTestData.ajaxUrl, {
565 method: 'POST',
566 headers: {
567 'Content-Type': 'application/x-www-form-urlencoded',
568 },
569 body: new URLSearchParams({
570 action: 'mxchat_start_fresh_session',
571 nonce: mxchatTestData.nonce,
572 old_session_id: sessionId,
573 new_session_id: newSessionId
574 })
575 })
576 .then(response => response.json())
577 .then(data => {
578 if (data.success) {
579 this.log('�
580 Backend session cleared: ' + data.data.message);
581
582 // Update the session ID everywhere in the DOM
583 this.updateSessionIdEverywhere(newSessionId);
584
585 // Clear the chat UI
586 this.clearChatUI();
587
588 // Show popular questions again
589 const popularQuestions = document.querySelector('#mxchat-popular-questions');
590 if (popularQuestions) {
591 popularQuestions.style.display = 'block';
592 }
593
594 // Clear testing data displays
595 this.updateLastQuery('New session started', []);
596 this.updateTopMatches([], 0);
597 this.updateApprovedUrls([]);
598
599 this.log('🎉 Fresh session started successfully');
600
601 } else {
602 this.log('❌ Error clearing session: ' + (data.data?.message || 'Unknown error'));
603 }
604 })
605 .catch(error => {
606 console.error('Error clearing chat session:', error);
607 this.log('🔌 Connection error when clearing session');
608 });
609 }
610
611 // Helper function to get cookie (same as your existing one)
612 getCookie(name) {
613 let value = "; " + document.cookie;
614 let parts = value.split("; " + name + "=");
615 if (parts.length == 2) return parts.pop().split(";").shift();
616 }
617
618 // Helper function to set session cookie (same as your existing one)
619 setChatSession(sessionId) {
620 // Set the cookie with a 24-hour expiration (86400 seconds)
621 document.cookie = "mxchat_session_id=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
622 }
623
624 // Helper function to clear the MxChat session cookie
625 clearMxChatCookie() {
626 // Clear the cookie by setting it to expire in the past
627 document.cookie = "mxchat_session_id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax";
628 this.log('🍪 Session cookie cleared');
629 }
630
631 updateSessionIdEverywhere(newSessionId) {
632 // Update global session ID variable if it exists
633 if (window.mxchatSessionId) {
634 window.mxchatSessionId = newSessionId;
635 }
636
637 // Update session ID in chat input data attribute
638 const chatInput = document.querySelector('#chat-input');
639 if (chatInput) {
640 chatInput.dataset.sessionId = newSessionId;
641 }
642
643 // Update any hidden session ID fields
644 const sessionInputs = document.querySelectorAll('input[name="session_id"]');
645 sessionInputs.forEach(input => {
646 input.value = newSessionId;
647 });
648
649 // Update any data attributes that store session ID
650 const elementsWithSessionId = document.querySelectorAll('[data-session-id]');
651 elementsWithSessionId.forEach(element => {
652 element.dataset.sessionId = newSessionId;
653 });
654
655 // Update URL parameter if it exists
656 if (window.location.search.includes('session_id=')) {
657 const url = new URL(window.location);
658 url.searchParams.set('session_id', newSessionId);
659 window.history.replaceState({}, '', url);
660 }
661
662 this.log('🔄 Session ID updated everywhere in DOM');
663 }
664
665 clearChatUI() {
666 const chatBox = document.querySelector('#chat-box');
667 if (chatBox) {
668 // Remove all messages (both user and bot)
669 const allMessages = chatBox.querySelectorAll('.bot-message, .user-message');
670 allMessages.forEach(msg => {
671 // Keep the first bot message if it's a welcome message
672 if (msg === chatBox.querySelector('.bot-message') &&
673 msg.textContent.toLowerCase().includes('welcome')) {
674 return; // Keep welcome message
675 }
676 msg.remove();
677 });
678 this.log('🧹 Chat UI cleared');
679 }
680
681 // Clear chat input
682 const chatInput = document.querySelector('#chat-input');
683 if (chatInput) {
684 chatInput.value = '';
685 }
686 }
687
688 getCurrentSessionId() {
689 // First try to get from cookie (most reliable)
690 const cookieSessionId = this.getCookie('mxchat_session_id');
691 if (cookieSessionId) {
692 return cookieSessionId;
693 }
694
695 // Try to get session ID from various DOM sources
696 const chatInput = document.querySelector('#chat-input');
697 if (chatInput && chatInput.dataset.sessionId) {
698 return chatInput.dataset.sessionId;
699 }
700
701 // Try to get from URL parameters
702 const urlParams = new URLSearchParams(window.location.search);
703 const sessionFromUrl = urlParams.get('session_id');
704 if (sessionFromUrl) {
705 return sessionFromUrl;
706 }
707
708 // Try to get from global variables
709 if (window.chatSessionId) {
710 return window.chatSessionId;
711 }
712
713 // Generate a temporary session ID if none found
714 return 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
715 }
716
717 loadSystemInfo() {
718 // Load system information from backend
719 this.updateSimilarityThreshold();
720 this.updateSystemPrompt();
721 this.updateKnowledgeBaseStatus();
722 }
723
724 updateSimilarityThreshold() {
725 fetch(mxchatTestData.ajaxUrl, {
726 method: 'POST',
727 headers: {
728 'Content-Type': 'application/x-www-form-urlencoded',
729 },
730 body: new URLSearchParams({
731 action: 'mxchat_get_similarity_threshold',
732 nonce: mxchatTestData.nonce
733 })
734 })
735 .then(response => response.json())
736 .then(data => {
737 const thresholdEl = this.panel.querySelector('#similarity-threshold');
738 if (data.success) {
739 thresholdEl.innerHTML = `<code>${data.data.threshold_percentage}</code>`;
740 } else {
741 thresholdEl.innerHTML = '<span class="error-text">Error loading threshold</span>';
742 }
743 })
744 .catch(error => {
745 console.error('Error fetching similarity threshold:', error);
746 const thresholdEl = this.panel.querySelector('#similarity-threshold');
747 thresholdEl.innerHTML = '<span class="error-text">Connection error</span>';
748 });
749 }
750
751 updateSystemPrompt() {
752 const promptEl = this.panel.querySelector('#system-prompt');
753 promptEl.textContent = 'Loading system prompt...';
754
755 fetch(mxchatTestData.ajaxUrl, {
756 method: 'POST',
757 headers: {
758 'Content-Type': 'application/x-www-form-urlencoded',
759 },
760 body: new URLSearchParams({
761 action: 'mxchat_get_system_info',
762 nonce: mxchatTestData.nonce
763 })
764 })
765 .then(response => response.json())
766 .then(data => {
767 if (data.success) {
768 promptEl.textContent = data.data.system_prompt || 'No system prompt configured';
769
770 // Enhanced model display with OpenRouter support
771 if (data.data.is_openrouter) {
772 this.log(`🤖 Model: OpenRouter`);
773 this.log(` └─ Using: ${data.data.openrouter_model}`);
774 } else {
775 this.log(`🤖 Model: ${data.data.selected_model}`);
776 }
777
778 // Enhanced API status with OpenRouter
779 const apiStatus = data.data.api_status;
780 const configuredApis = Object.keys(apiStatus).filter(key => apiStatus[key]);
781
782 if (configuredApis.length > 0) {
783 this.log(`🔑 Configured APIs: ${configuredApis.map(api => {
784 // Capitalize and format API names
785 if (api === 'openai') return 'OpenAI';
786 if (api === 'xai') return 'X.AI';
787 if (api === 'openrouter') return 'OpenRouter';
788 return api.charAt(0).toUpperCase() + api.slice(1);
789 }).join(', ')}`);
790 } else {
791 this.log(`⚠️ No API keys configured`);
792 }
793
794 // Specific warning for OpenRouter if selected but no key
795 if (data.data.is_openrouter && !apiStatus.openrouter) {
796 this.log(`WARNING: OpenRouter selected but no API key configured!`);
797 }
798 } else {
799 promptEl.textContent = 'Error loading system prompt';
800 }
801 })
802 .catch(error => {
803 console.error('Error fetching system info:', error);
804 promptEl.textContent = 'Connection error';
805 });
806 }
807
808 updateKnowledgeBaseStatus() {
809 const statusEl = this.panel.querySelector('#kb-status');
810 statusEl.innerHTML = 'Checking...';
811
812 fetch(mxchatTestData.ajaxUrl, {
813 method: 'POST',
814 headers: {
815 'Content-Type': 'application/x-www-form-urlencoded',
816 },
817 body: new URLSearchParams({
818 action: 'mxchat_get_kb_status',
819 nonce: mxchatTestData.nonce
820 })
821 })
822 .then(response => response.json())
823 .then(data => {
824 if (data.success) {
825 const kbData = data.data;
826 statusEl.innerHTML = `<span class="success-text">${kbData.status}</span> (${kbData.type} - ${kbData.documents})`;
827 } else {
828 statusEl.innerHTML = '<span class="error-text">Error loading KB status</span>';
829 }
830 })
831 .catch(error => {
832 console.error('Error fetching KB status:', error);
833 statusEl.innerHTML = '<span class="error-text">Connection error</span>';
834 });
835 }
836
837 clearDebugConsole() {
838 const console = this.panel.querySelector('#debug-console');
839 console.innerHTML = '<div class="debug-entry">Debug console cleared...</div>';
840 }
841
842 log(message) {
843 const console = this.panel.querySelector('#debug-console');
844 const timestamp = new Date().toLocaleTimeString();
845 const logEntry = document.createElement('div');
846 logEntry.className = 'debug-entry';
847 logEntry.innerHTML = `<span class="debug-timestamp">[${timestamp}]</span> ${message}`;
848 console.appendChild(logEntry);
849 console.scrollTop = console.scrollHeight;
850
851 // Keep only last 50 entries to prevent memory issues
852 const entries = console.querySelectorAll('.debug-entry');
853 if (entries.length > 50) {
854 entries[0].remove();
855 }
856 }
857 }
858
859 // Initialize the test panel when the script loads
860 document.addEventListener('DOMContentLoaded', function() {
861 // Only initialize if user is admin and testing is enabled
862 if (window.mxchatTestingEnabled) {
863 window.mxchatTestPanel = new MxChatTestPanel();
864 }
865 });
866
867 // Global function to enable testing mode programmatically
868 window.enableMxChatTesting = function() {
869 if (!window.mxchatTestPanel) {
870 window.mxchatTestPanel = new MxChatTestPanel();
871 }
872 };