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/test-panel.js +64 -225 3.2.212.6.0 View file →
@@ -257,9 +257,9 @@
257 257
258 258 // NEW: Update approved URLs
259 259 this.updateApprovedUrls(testingData.approved_urls || []);
260 260
261 - this.updateTopMatches(testingData.top_matches || [], testingData.similarity_threshold || 0.75, testingData.sources_used || 0, testingData.total_chunks_used || 0);
261 + this.updateTopMatches(testingData.top_matches || [], testingData.similarity_threshold || 0.75);
262 262
263 263 // NEW: Update action matches
264 264 this.updateActionMatches(testingData.action_matches || []);
265 265
@@ -274,19 +274,8 @@
274 274 // NEW: Log approved URLs count
275 275 if (testingData.approved_urls && testingData.approved_urls.length > 0) {
276 276 this.log(`🔗 Approved URLs for citations: ${testingData.approved_urls.length}`);
277 277 }
278 -
279 - // 58f8b4: URL-guard outcome — stripping used to be completely silent,
280 - // which is why fabricated/stripped links were invisible from this panel.
281 - if (testingData.url_validation) {
282 - const uv = testingData.url_validation;
283 - if (uv.removed_count > 0) {
284 - this.log(`🚫 URL guard stripped ${uv.removed_count} unapproved link${uv.removed_count !== 1 ? 's' : ''}: ${(uv.removed_urls || []).join(', ')}`);
285 - } else if (uv.checked > 0) {
286 - this.log(`🔗 URL guard: all ${uv.checked} link${uv.checked !== 1 ? 's' : ''} in the answer approved (${uv.strict ? 'strict' : 'lenient'} mode)`);
287 - }
288 - }
289 278
290 279 // Show summary in debug console
291 280 if (testingData.top_matches && testingData.top_matches.length > 0) {
292 281 const aboveThreshold = testingData.top_matches.filter(match => match.above_threshold).length;
@@ -402,173 +391,60 @@
402 391
403 392 actionsEl.innerHTML = html;
404 393 }
405 394
406 - updateTopMatches(topMatches, threshold, sourcesUsed = 0, totalChunksUsed = 0) {
395 + updateTopMatches(topMatches, threshold) {
407 396 const scoresEl = this.panel.querySelector('#similarity-scores');
408 -
397 +
409 398 if (!topMatches || topMatches.length === 0) {
410 399 scoresEl.innerHTML = '<div class="no-data-message">No similarity data available</div>';
411 400 return;
412 401 }
413 -
414 - // Hybrid keyword boost (38ffa1): rows carry matched_via + fused_rank
415 - // when hybrid retrieval was on for this message.
416 - const hybridOn = topMatches.some(m => m.matched_via);
417 -
418 - // Group matches by source URL
419 - const groupedByUrl = {};
420 - topMatches.forEach((match) => {
421 - const url = match.source_display || 'Unknown';
422 - if (!groupedByUrl[url]) {
423 - groupedByUrl[url] = {
424 - url: url,
425 - isUrl: url.startsWith('http'),
426 - bestScore: 0,
427 - usedForContext: false,
428 - totalChunks: match.total_chunks || 1,
429 - matchedChunks: [],
430 - isChunked: match.is_chunk || false,
431 - bestFusedRank: Infinity,
432 - viaSet: {}
433 - };
434 - }
435 -
436 - // Track best score
437 - if (match.similarity_percentage > groupedByUrl[url].bestScore) {
438 - groupedByUrl[url].bestScore = match.similarity_percentage;
439 - }
440 -
441 - // Track if any chunk was used for context
442 - if (match.used_for_context) {
443 - groupedByUrl[url].usedForContext = true;
444 - }
445 -
446 - if (match.fused_rank && match.fused_rank < groupedByUrl[url].bestFusedRank) {
447 - groupedByUrl[url].bestFusedRank = match.fused_rank;
448 - }
449 - if (match.matched_via) {
450 - groupedByUrl[url].viaSet[match.matched_via] = true;
451 - }
452 -
453 - // Add chunk info
454 - groupedByUrl[url].matchedChunks.push({
455 - chunkIndex: match.chunk_index,
456 - score: match.similarity_percentage,
457 - usedForContext: match.used_for_context,
458 - aboveThreshold: match.above_threshold
459 - });
460 - });
461 -
462 - // Convert to array: fused-rank order when hybrid is on, best cosine otherwise
463 - const urlGroups = Object.values(groupedByUrl).sort((a, b) => {
464 - if (hybridOn && a.bestFusedRank !== b.bestFusedRank) {
465 - return a.bestFusedRank - b.bestFusedRank;
466 - }
467 - return b.bestScore - a.bestScore;
468 - });
469 -
470 - // Use backend counts if available, otherwise fall back to frontend calculation
471 - const usedUrlCount = sourcesUsed > 0 ? sourcesUsed : urlGroups.filter(g => g.usedForContext).length;
472 - const chunksInfo = totalChunksUsed > 0 ? `${totalChunksUsed} chunks sent to AI` : `${topMatches.length} chunk matches`;
473 -
402 +
474 403 let html = `<div class="matches-header">
475 - <strong>${usedUrlCount} source${usedUrlCount === 1 ? '' : 's'} used for AI context</strong>
476 - <span class="matches-subheader">(${chunksInfo})</span>
404 + <strong>Top ${topMatches.length} matches</strong>
477 405 </div>`;
478 -
479 - urlGroups.forEach((group, groupIndex) => {
480 - const cardClass = group.usedForContext ? 'above-threshold' : 'below-threshold';
481 - const statusIcon = group.usedForContext ? '✓' : '✗';
482 - const contextLabel = group.usedForContext ? 'Used for AI context' : 'Not used';
483 -
484 - // Build chunk summary
485 - let chunkSummary = '';
486 - if (group.isChunked && group.totalChunks > 1) {
487 - const usedChunkCount = group.matchedChunks.filter(c => c.usedForContext).length;
488 - chunkSummary = `<span class="chunk-summary">${usedChunkCount}/${group.totalChunks} chunks matched</span>`;
406 +
407 + topMatches.forEach((match, index) => {
408 + const isAboveThreshold = match.above_threshold;
409 + const isUsedForContext = match.used_for_context; // Use the actual flag from PHP
410 + const statusIcon = isAboveThreshold ? '✓' : '✗';
411 +
412 + // Determine the correct label based on actual usage
413 + let contextLabel;
414 + if (isUsedForContext) {
415 + contextLabel = 'Used for AI context';
416 + } else if (isAboveThreshold) {
417 + contextLabel = 'Above threshold (not used)';
418 + } else {
419 + contextLabel = 'Below threshold';
489 420 }
490 -
491 - // Check if this entry has multiple matched chunks to show expand toggle
492 - const hasMultipleChunks = group.matchedChunks.length > 1;
493 - const expandToggle = hasMultipleChunks
494 - ? `<span class="chunk-expand-toggle" data-group="${groupIndex}">▶ Show chunks</span>`
495 - : '';
496 -
497 - let viaChip = '';
498 - if (hybridOn) {
499 - const vias = Object.keys(group.viaSet);
500 - if (vias.length) {
501 - const viaLabel = (vias.length > 1 || vias[0] === 'both') ? 'Both'
502 - : (vias[0] === 'keyword' ? 'Keyword' : 'Vector');
503 - viaChip = `<span class="mxch-rag-via-chip mxch-rag-via-${viaLabel.toLowerCase()}">${viaLabel}</span>`;
504 - }
505 - }
506 -
421 +
422 + // Determine card styling - should be based on whether it was actually used
423 + const cardClass = isUsedForContext ? 'above-threshold' : 'below-threshold';
424 +
507 425 html += `
508 426 <div class="match-card ${cardClass}">
509 427 <div class="match-header">
510 428 <div class="match-title">
511 429 <span class="status-icon">${statusIcon}</span>
512 - <span class="similarity-score">${group.bestScore}%</span>
513 - ${viaChip}
514 - ${chunkSummary}
430 + <span class="similarity-score">${match.similarity_percentage}%</span>
515 431 </div>
516 432 <span class="context-label">${contextLabel}</span>
517 433 </div>
518 434 <div class="match-source">
519 - ${group.isUrl ?
520 - `<span class="source-icon link-icon">🔗</span> ${group.url}` :
521 - `<span class="source-icon doc-icon">📄</span> ${group.url}`
435 + ${match.source_display.startsWith('http') ?
436 + `<span class="source-icon link-icon">🔗</span> ${match.source_display}` :
437 + `<span class="source-icon doc-icon">📄</span> ${match.source_display}`
522 438 }
523 439 </div>
524 - ${expandToggle}
525 - ${hasMultipleChunks ? this.renderChunkDetails(group.matchedChunks, groupIndex) : ''}
526 440 </div>
527 441 `;
528 442 });
529 -
443 +
530 444 scoresEl.innerHTML = html;
531 -
532 - // Add click handlers for expand toggles
533 - scoresEl.querySelectorAll('.chunk-expand-toggle').forEach(toggle => {
534 - toggle.addEventListener('click', (e) => {
535 - const groupId = e.target.dataset.group;
536 - const details = scoresEl.querySelector(`.chunk-details[data-group="${groupId}"]`);
537 - if (details) {
538 - const isExpanded = details.classList.toggle('expanded');
539 - e.target.textContent = isExpanded ? '▼ Hide chunks' : '▶ Show chunks';
540 - }
541 - });
542 - });
543 445 }
544 446
545 - renderChunkDetails(chunks, groupIndex) {
546 - // Sort chunks by chunk index
547 - const sortedChunks = [...chunks].sort((a, b) => (a.chunkIndex || 0) - (b.chunkIndex || 0));
548 -
549 - let html = `<div class="chunk-details" data-group="${groupIndex}">`;
550 -
551 - sortedChunks.forEach(chunk => {
552 - const chunkNum = (chunk.chunkIndex !== null && chunk.chunkIndex !== undefined)
553 - ? chunk.chunkIndex + 1
554 - : '?';
555 - const statusClass = chunk.usedForContext ? 'chunk-used' : 'chunk-not-used';
556 - const statusIcon = chunk.usedForContext ? '✓' : '○';
557 -
558 - html += `
559 - <div class="chunk-detail-row ${statusClass}">
560 - <span class="chunk-detail-icon">${statusIcon}</span>
561 - <span class="chunk-detail-num">Chunk ${chunkNum}</span>
562 - <span class="chunk-detail-score">${chunk.score}%</span>
563 - </div>
564 - `;
565 - });
566 -
567 - html += '</div>';
568 - return html;
569 - }
570 -
571 447 updateLastQuery(query, topMatches) {
572 448 const queryEl = this.panel.querySelector('#last-query');
573 449 queryEl.textContent = query;
574 450
@@ -579,49 +455,28 @@
579 455 };
580 456 }
581 457
582 458 clearChatSession() {
583 - // Determine the active bot ID from MxChatInstances
584 - let botId = 'default';
585 - if (typeof MxChatInstances !== 'undefined' && typeof MxChatInstances.getAllBotIds === 'function') {
586 - const botIds = MxChatInstances.getAllBotIds();
587 - if (botIds.length > 0) {
588 - botId = botIds[0];
589 - }
590 - }
591 -
592 - // Get current session ID using the correct bot-suffixed cookie name
593 - const cookieName = 'mxchat_session_id_' + botId;
594 - const sessionId = this.getCookie(cookieName) || this.getCookie('mxchat_session_id') || this.getCurrentSessionId();
595 -
459 + // Get current session ID from cookie (most reliable)
460 + const sessionId = this.getCookie('mxchat_session_id') || this.getCurrentSessionId();
461 +
596 462 if (!sessionId) {
597 - this.log('No active session found');
463 + this.log('❌ No active session found');
598 464 return;
599 465 }
600 -
601 - this.log('Current session ID: ' + sessionId);
602 - this.log('Starting fresh session...');
603 -
604 - // Use MxChatInstances.resetChatSession to properly reset in-memory state + cookie
605 - if (typeof MxChatInstances !== 'undefined' && typeof MxChatInstances.resetChatSession === 'function') {
606 - MxChatInstances.resetChatSession(botId);
607 - }
608 -
609 - // Get the new session ID that was just set by resetChatSession
610 - let newSessionId = '';
611 - if (typeof MxChatInstances !== 'undefined' && typeof MxChatInstances.getChatSession === 'function') {
612 - newSessionId = MxChatInstances.getChatSession(botId);
613 - }
614 -
615 - // Fallback if MxChatInstances wasn't available
616 - if (!newSessionId) {
617 - newSessionId = 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
618 - this.clearMxChatCookie(botId);
619 - this.setChatSession(newSessionId, botId);
620 - }
621 -
622 - this.log('New session ID: ' + newSessionId);
623 -
466 +
467 + this.log(`🔍 Current session ID: ${sessionId}`);
468 + this.log('🧹 Starting fresh session...');
469 +
470 + // Generate new session ID (using your existing format)
471 + const newSessionId = 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
472 +
473 + // Clear the old cookie and set new one immediately
474 + this.clearMxChatCookie();
475 + this.setChatSession(newSessionId);
476 +
477 + this.log(`🆕 New session ID: ${newSessionId}`);
478 +
624 479 // Call backend to clear old session data
625 480 fetch(mxchatTestData.ajaxUrl, {
626 481 method: 'POST',
627 482 headers: {
@@ -636,36 +491,36 @@
636 491 })
637 492 .then(response => response.json())
638 493 .then(data => {
639 494 if (data.success) {
640 - this.log('Backend session cleared: ' + data.data.message);
641 -
495 + this.log('✅ Backend session cleared: ' + data.data.message);
496 +
642 497 // Update the session ID everywhere in the DOM
643 498 this.updateSessionIdEverywhere(newSessionId);
644 -
499 +
645 500 // Clear the chat UI
646 501 this.clearChatUI();
647 -
502 +
648 503 // Show popular questions again
649 504 const popularQuestions = document.querySelector('#mxchat-popular-questions');
650 505 if (popularQuestions) {
651 506 popularQuestions.style.display = 'block';
652 507 }
653 -
508 +
654 509 // Clear testing data displays
655 510 this.updateLastQuery('New session started', []);
656 511 this.updateTopMatches([], 0);
657 512 this.updateApprovedUrls([]);
658 -
659 - this.log('Fresh session started successfully');
660 -
513 +
514 + this.log('🎉 Fresh session started successfully');
515 +
661 516 } else {
662 - this.log('Error clearing session: ' + (data.data?.message || 'Unknown error'));
517 + this.log('❌ Error clearing session: ' + (data.data?.message || 'Unknown error'));
663 518 }
664 519 })
665 520 .catch(error => {
666 521 console.error('Error clearing chat session:', error);
667 - this.log('Connection error when clearing session');
522 + this.log('🔌 Connection error when clearing session');
668 523 });
669 524 }
670 525
671 526 // Helper function to get cookie (same as your existing one)
@@ -674,21 +529,19 @@
674 529 let parts = value.split("; " + name + "=");
675 530 if (parts.length == 2) return parts.pop().split(";").shift();
676 531 }
677 532
678 - // Helper function to set session cookie (matches chat-script.js format)
679 - setChatSession(sessionId, botId) {
680 - botId = botId || 'default';
681 - document.cookie = 'mxchat_session_id_' + botId + '=' + sessionId + '; path=/; max-age=86400; SameSite=Lax';
533 + // Helper function to set session cookie (same as your existing one)
534 + setChatSession(sessionId) {
535 + // Set the cookie with a 24-hour expiration (86400 seconds)
536 + document.cookie = "mxchat_session_id=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
682 537 }
683 538
684 539 // Helper function to clear the MxChat session cookie
685 - clearMxChatCookie(botId) {
686 - botId = botId || 'default';
687 - // Clear both bot-suffixed and legacy cookie formats
688 - document.cookie = 'mxchat_session_id_' + botId + '=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax';
689 - document.cookie = 'mxchat_session_id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax';
690 - this.log('Session cookie cleared');
540 + clearMxChatCookie() {
541 + // Clear the cookie by setting it to expire in the past
542 + document.cookie = "mxchat_session_id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax";
543 + this.log('🍪 Session cookie cleared');
691 544 }
692 545
693 546 updateSessionIdEverywhere(newSessionId) {
694 547 // Update global session ID variable if it exists
@@ -747,23 +600,9 @@
747 600 }
748 601 }
749 602
750 603 getCurrentSessionId() {
751 - // Try MxChatInstances first (most reliable — matches chat-script.js)
752 - if (typeof MxChatInstances !== 'undefined' && typeof MxChatInstances.getChatSession === 'function') {
753 - const botIds = typeof MxChatInstances.getAllBotIds === 'function' ? MxChatInstances.getAllBotIds() : ['default'];
754 - const botId = botIds.length > 0 ? botIds[0] : 'default';
755 - const instanceSession = MxChatInstances.getChatSession(botId);
756 - if (instanceSession) {
757 - return instanceSession;
758 - }
759 - }
760 -
761 - // Try bot-suffixed cookie, then legacy cookie
762 - const botCookieId = this.getCookie('mxchat_session_id_default');
763 - if (botCookieId) {
764 - return botCookieId;
765 - }
604 + // First try to get from cookie (most reliable)
766 605 const cookieSessionId = this.getCookie('mxchat_session_id');
767 606 if (cookieSessionId) {
768 607 return cookieSessionId;
769 608 }