# mxchat-basic/2.6.3/js/mxchat_transcripts.js

MxChat – AI Chatbot &amp; Content Generation for WordPress, version 2.6.3. 524 lines.

- Page: https://pluginprobe.com/plugins/mxchat-basic/2.6.3/code/js/mxchat_transcripts.js
- Raw: https://pluginprobe.com/plugins/mxchat-basic/2.6.3/raw/js/mxchat_transcripts.js
- Modified: 2025-12-31T16:28:36+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/mxchat-basic/2.6.3/code/js/mxchat_transcripts.js#L10-L20`.

```javascript
jQuery(document).ready(function($) {
    // Current page state
    let currentPage = 1;
    const perPage = 50; // Display 50 sessions per page
    let totalPages = 1;
    
    // Track selected sessions for bulk delete
    let selectedSessions = new Set();

    // Select/Deselect All functionality
    const selectButton = $('#mxchat-select-all-transcripts');
    let isSelected = false;

    selectButton.click(function() {
        isSelected = !isSelected;
        $(this).toggleClass('selected');
        
        // Update button text
        const buttonText = $(this).find('.button-text');
        buttonText.text(isSelected ? 'Deselect All' : 'Select All');
        
        // Update session selection (for current page)
        $('.mxchat-session-header').each(function() {
            const sessionId = $(this).data('session-id');
            const sessionContainer = $(this).closest('.mxchat-session');
            if (isSelected) {
                selectedSessions.add(sessionId);
                sessionContainer.addClass('selected');
            } else {
                selectedSessions.delete(sessionId);
                sessionContainer.removeClass('selected');
            }
        });
        
        updateDeleteButtonState();
    });

    // Search functionality
    $('#mxchat-search-transcripts').on('input', function() {
        var searchTerm = $(this).val().toLowerCase();
        
        if (searchTerm.length > 0) {
            // Reset to first page when searching
            currentPage = 1;
            
            // Load with search filter
            loadTranscripts(currentPage, searchTerm);
        } else {
            // Reset to first page with no search term
            currentPage = 1;
            loadTranscripts(currentPage, '');
        }
    });

    // Initial load of transcripts
    loadTranscripts(currentPage, '');

    // Function to load transcripts with pagination
    function loadTranscripts(page, searchTerm = '') {
        $('#mxchat-transcripts').html('<div class="mxchat-loading">Loading transcripts...</div>');
        
        $.ajax({
            url: ajaxurl,
            type: 'POST',
            data: {
                action: 'mxchat_fetch_chat_history',
                page: page,
                per_page: perPage,
                search: searchTerm
            },
            success: function(response) {
                $('#mxchat-transcripts').html(response.html);
                currentPage = response.page;
                totalPages = response.total_pages;
                
                // Reset selection state when page changes (but keep selectedSessions for bulk operations)
                isSelected = false;
                selectButton.removeClass('selected');
                selectButton.find('.button-text').text('Select All');
                
                // Restore selection state for sessions on this page
                $('.mxchat-session-header').each(function() {
                    const sessionId = $(this).data('session-id');
                    const sessionContainer = $(this).closest('.mxchat-session');
                    if (selectedSessions.has(sessionId)) {
                        sessionContainer.addClass('selected');
                    }
                });
                
                updateDeleteButtonState();
                
                // Add click handlers to pagination buttons
                $('.mxchat-pagination-button').on('click', function() {
                    var pageNum = $(this).data('page');
                    loadTranscripts(pageNum, searchTerm);
                    
                    // Scroll to top of transcripts
                    $('html, body').animate({
                        scrollTop: $('#mxchat-transcripts').offset().top - 50
                    }, 300);
                });
                
                // Re-attach event handlers for newly loaded content
                attachDynamicEventHandlers();
            },
            error: function(xhr, status, error) {
                $('#mxchat-transcripts').html('<div class="mxchat-error">Error loading chat transcripts. Please try again.</div>');
                console.error("AJAX Error: " + status + " - " + error);
            }
        });
    }
    
    // Attach event handlers to dynamically loaded content
    function attachDynamicEventHandlers() {
        // Handle individual delete button clicks
        $('.mxchat-delete-btn').off('click').on('click', function(e) {
            e.preventDefault();
            e.stopPropagation();
            
            const sessionId = $(this).data('session-id');
            
            if (!confirm("Are you sure you want to delete this chat session? This action cannot be undone.")) {
                return;
            }
            
            // Delete single session
            deleteSessions([sessionId]);
        });
        
        // Handle session header clicks for selection (bulk delete)
        $('.mxchat-session-header').off('click').on('click', function(e) {
            // Don't trigger if clicking the delete button
            if ($(e.target).hasClass('mxchat-delete-btn') || $(e.target).closest('.mxchat-delete-btn').length) {
                return;
            }

            const sessionId = $(this).data('session-id');
            const sessionContainer = $(this).closest('.mxchat-session');

            if (selectedSessions.has(sessionId)) {
                selectedSessions.delete(sessionId);
                sessionContainer.removeClass('selected');
            } else {
                selectedSessions.add(sessionId);
                sessionContainer.addClass('selected');
            }

            updateDeleteButtonState();
        });

        // Handle clicks on Sources link for RAG context
        $('.mxchat-rag-link').off('click').on('click', function(e) {
            e.preventDefault();
            e.stopPropagation();

            const messageId = $(this).closest('.mxchat-message').data('message-id');
            if (messageId) {
                openRagContextModal(messageId);
            }
        });
    }
    
    // Update delete button state based on selections
    function updateDeleteButtonState() {
        const deleteButton = $('.delete-chats-button');
        if (selectedSessions.size > 0) {
            deleteButton.prop('disabled', false);
        } else {
            deleteButton.prop('disabled', true);
        }
    }
    
    // Function to delete sessions
    function deleteSessions(sessionIds) {
        $.ajax({
            url: ajaxurl,
            type: 'POST',
            data: {
                action: 'mxchat_delete_chat_history',
                delete_session_ids: sessionIds,
                security: $('#mxchat_delete_chat_nonce').val()
            },
            success: function(response) {
                var jsonResponse = JSON.parse(response);
                if (jsonResponse.success) {
                    alert("Success: " + jsonResponse.success);
                    
                    // Remove deleted sessions from selectedSessions
                    sessionIds.forEach(id => selectedSessions.delete(id));
                    
                } else if (jsonResponse.error) {
                    alert("Error: " + jsonResponse.error);
                } else {
                    //console.log("Unexpected response format.");
                }
                
                // Reload the current page of transcripts
                loadTranscripts(currentPage);
            },
            error: function(xhr, status, error) {
                //console.error("AJAX Error: " + status + " - " + error);
                //console.log(xhr.responseText);
                alert("An error occurred while deleting chat sessions. Please try again.");
            }
        });
    }

    // Delete form submission (bulk delete)
    $('#mxchat-delete-form').submit(function(e) {
        e.preventDefault();
        
        if (selectedSessions.size === 0) {
            alert("Please select at least one chat session to delete.");
            return;
        }
        
        // Confirm deletion
        if (!confirm(`Are you sure you want to delete the selected ${selectedSessions.size} chat session(s)? This action cannot be undone.`)) {
            return;
        }
        
        // Convert Set to Array and delete
        deleteSessions(Array.from(selectedSessions));
    });
    
    // Export functionality - this remains unchanged as it should export all transcripts
    $('#mxchat-export-transcripts').on('click', function() {
        var $button = $(this);
        $button.prop('disabled', true).addClass('loading');

        // Create a form and submit it
        var $form = $('<form>', {
            'method': 'post',
            'action': ajaxurl
        });

        $form.append($('<input>', {
            'type': 'hidden',
            'name': 'action',
            'value': 'mxchat_export_transcripts'
        }));

        $form.append($('<input>', {
            'type': 'hidden',
            'name': 'security',
            'value': mxchatAdmin.export_nonce
        }));

        $form.appendTo('body').submit();

        // Re-enable the button after a short delay
        setTimeout(function() {
            $button.prop('disabled', false).removeClass('loading');
        }, 2000);
    });
    
    // Chat Email Notification Modal functionality
    
    // Open modal
    $('#mxchat-chat-email-notification-btn').on('click', function(e) {
        e.preventDefault();
        $('#mxchat-chat-email-notification-modal').fadeIn(300);
    });
    
    // Close modal
    $('.mxchat-chat-notification-modal-close, .mxchat-chat-notification-modal-cancel').on('click', function() {
        $('#mxchat-chat-email-notification-modal').fadeOut(300);
    });
    
    // Close modal on outside click
    $('#mxchat-chat-email-notification-modal').on('click', function(e) {
        if ($(e.target).is('#mxchat-chat-email-notification-modal')) {
            $(this).fadeOut(300);
        }
    });
    
    // Handle form submission - Let WordPress handle it normally for settings
    $('#mxchat-chat-email-notification-form').on('submit', function(e) {
        // Don't prevent default - let the form submit normally to WordPress options.php
        var $submitButton = $(this).find('button[type="submit"]');
        var originalText = $submitButton.text();

        // Just show a loading state
        $submitButton.text('Saving...').prop('disabled', true);

        // The form will submit normally and reload the page
    });

    // ========== RAG Context Modal Functions ==========

    // Open RAG context modal and fetch data
    function openRagContextModal(messageId) {
        const $modal = $('#mxchat-rag-context-modal');
        const $loading = $modal.find('.mxchat-rag-loading');
        const $content = $modal.find('.mxchat-rag-content');

        // Show modal with loading state
        $modal.fadeIn(300);
        $loading.show();
        $content.html('');

        // Fetch RAG context via AJAX
        $.ajax({
            url: ajaxurl,
            type: 'POST',
            data: {
                action: 'mxchat_get_rag_context',
                message_id: messageId
            },
            success: function(response) {
                $loading.hide();
                if (response.success && response.data) {
                    renderRagContext(response.data, $content);
                } else {
                    $content.html('<div class="mxchat-rag-error">Unable to load document context.</div>');
                }
            },
            error: function() {
                $loading.hide();
                $content.html('<div class="mxchat-rag-error">Error loading document context. Please try again.</div>');
            }
        });
    }

    // Render RAG context data in the modal - grouped by URL
    function renderRagContext(data, $container) {
        let html = '';

        // Summary section
        html += '<div class="mxchat-rag-summary">';
        html += '<div class="mxchat-rag-summary-item">';
        html += '<span class="mxchat-rag-label">Knowledge Base:</span> ';
        html += '<span class="mxchat-rag-value">' + escapeHtml(data.knowledge_base_type || 'WordPress Database') + '</span>';
        html += '</div>';
        html += '<div class="mxchat-rag-summary-item">';
        html += '<span class="mxchat-rag-label">Similarity Threshold:</span> ';
        html += '<span class="mxchat-rag-value">' + Math.round((data.similarity_threshold || 0.35) * 100) + '%</span>';
        html += '</div>';
        html += '<div class="mxchat-rag-summary-item">';
        html += '<span class="mxchat-rag-label">Documents Checked:</span> ';
        html += '<span class="mxchat-rag-value">' + (data.total_documents_checked || 0) + '</span>';
        html += '</div>';
        html += '</div>';

        // Top matches section - grouped by URL
        if (data.top_matches && data.top_matches.length > 0) {
            // Group matches by source URL
            const groupedByUrl = {};
            data.top_matches.forEach(function(match) {
                const url = match.source_display || 'Unknown';
                if (!groupedByUrl[url]) {
                    groupedByUrl[url] = {
                        url: url,
                        isUrl: url.startsWith('http'),
                        bestScore: 0,
                        usedForContext: false,
                        totalChunks: match.total_chunks || 1,
                        matchedChunks: [],
                        isChunked: match.is_chunk || false,
                        roleRestriction: match.role_restriction
                    };
                }

                // Track best score
                if (match.similarity_percentage > groupedByUrl[url].bestScore) {
                    groupedByUrl[url].bestScore = match.similarity_percentage;
                }

                // Track if any chunk was used
                if (match.used_for_context) {
                    groupedByUrl[url].usedForContext = true;
                }

                // Add chunk info
                groupedByUrl[url].matchedChunks.push({
                    chunkIndex: match.chunk_index,
                    score: match.similarity_percentage,
                    usedForContext: match.used_for_context,
                    aboveThreshold: match.above_threshold,
                    contentPreview: match.content_preview
                });
            });

            // Convert to array and sort by best score
            const urlGroups = Object.values(groupedByUrl).sort(function(a, b) {
                return b.bestScore - a.bestScore;
            });

            // Count unique URLs used
            const usedUrlCount = urlGroups.filter(function(g) { return g.usedForContext; }).length;

            html += '<div class="mxchat-rag-matches">';
            html += '<h3>Retrieved Documents</h3>';
            html += '<p class="mxchat-rag-matches-summary">' + usedUrlCount + ' entr' + (usedUrlCount === 1 ? 'y' : 'ies') + ' used for response';
            html += ' <span style="color: #64748b; font-size: 12px;">(from ' + data.top_matches.length + ' chunk matches)</span></p>';

            urlGroups.forEach(function(group, groupIndex) {
                const cardClass = group.usedForContext ? 'mxchat-rag-match-used' : 'mxchat-rag-match-below';
                const statusIcon = group.usedForContext ? '✅' : '❌';
                const statusLabel = group.usedForContext ? 'Used for Response' : 'Not Used';

                // Build chunk summary badge
                let chunkBadge = '';
                if (group.isChunked && group.totalChunks > 1) {
                    const usedChunkCount = group.matchedChunks.filter(function(c) { return c.usedForContext; }).length;
                    chunkBadge = '<span class="mxchat-rag-chunk-badge">' + usedChunkCount + '/' + group.totalChunks + ' chunks</span>';
                }

                html += '<div class="mxchat-rag-match-card ' + cardClass + '">';
                html += '<div class="mxchat-rag-match-header">';
                html += '<span class="mxchat-rag-match-score">' + group.bestScore + '%</span>';
                html += chunkBadge;
                html += '<span class="mxchat-rag-match-status ' + (group.usedForContext ? 'status-used' : 'status-below') + '">';
                html += statusIcon + ' ' + statusLabel;
                html += '</span>';
                html += '</div>';

                html += '<div class="mxchat-rag-match-source">';
                if (group.isUrl) {
                    html += '<a href="' + escapeHtml(group.url) + '" target="_blank" rel="noopener noreferrer">';
                    html += '🔗 ' + escapeHtml(group.url);
                    html += '</a>';
                } else {
                    html += '📄 ' + escapeHtml(group.url);
                }
                html += '</div>';

                // Show role restriction if not public
                if (group.roleRestriction && group.roleRestriction !== 'public') {
                    html += '<div class="mxchat-rag-match-meta">';
                    html += '<span class="mxchat-rag-role-badge">🔒 ' + escapeHtml(group.roleRestriction) + '</span>';
                    html += '</div>';
                }

                // Expandable chunk details if multiple chunks
                if (group.matchedChunks.length > 1) {
                    html += '<div class="mxchat-rag-chunk-toggle" data-group="' + groupIndex + '">▶ Show ' + group.matchedChunks.length + ' matched chunks</div>';
                    html += '<div class="mxchat-rag-chunk-details" data-group="' + groupIndex + '">';

                    // Sort chunks by index
                    const sortedChunks = group.matchedChunks.slice().sort(function(a, b) {
                        return (a.chunkIndex || 0) - (b.chunkIndex || 0);
                    });

                    sortedChunks.forEach(function(chunk) {
                        const chunkNum = (chunk.chunkIndex !== null && chunk.chunkIndex !== undefined) ? chunk.chunkIndex + 1 : '?';
                        const chunkClass = chunk.usedForContext ? 'chunk-used' : 'chunk-not-used';
                        const chunkIcon = chunk.usedForContext ? '✓' : '○';

                        html += '<div class="mxchat-rag-chunk-row ' + chunkClass + '">';
                        html += '<span class="mxchat-rag-chunk-icon">' + chunkIcon + '</span>';
                        html += '<span class="mxchat-rag-chunk-num">Chunk ' + chunkNum + '</span>';
                        html += '<span class="mxchat-rag-chunk-score">' + chunk.score + '%</span>';
                        html += '</div>';
                    });

                    html += '</div>';
                }

                html += '</div>';
            });

            html += '</div>';
        } else {
            html += '<div class="mxchat-rag-no-matches">No document matches found for this response.</div>';
        }

        // Approved URLs section
        if (data.approved_urls && data.approved_urls.length > 0) {
            html += '<div class="mxchat-rag-urls">';
            html += '<h3>Approved URLs for Citations (' + data.approved_urls.length + ')</h3>';
            html += '<ul class="mxchat-rag-url-list">';
            data.approved_urls.forEach(function(url) {
                html += '<li><a href="' + escapeHtml(url) + '" target="_blank" rel="noopener noreferrer">' + escapeHtml(url) + '</a></li>';
            });
            html += '</ul>';
            html += '</div>';
        }

        $container.html(html);

        // Add click handlers for chunk toggles
        $container.find('.mxchat-rag-chunk-toggle').on('click', function() {
            const groupId = $(this).data('group');
            const $details = $container.find('.mxchat-rag-chunk-details[data-group="' + groupId + '"]');
            const isExpanded = $details.is(':visible');

            if (isExpanded) {
                $details.slideUp(200);
                $(this).text('▶ Show ' + $details.find('.mxchat-rag-chunk-row').length + ' matched chunks');
            } else {
                $details.slideDown(200);
                $(this).text('▼ Hide chunks');
            }
        });
    }

    // Helper function to escape HTML
    function escapeHtml(text) {
        if (!text) return '';
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    // Close RAG context modal
    $('.mxchat-rag-modal-close').on('click', function() {
        $('#mxchat-rag-context-modal').fadeOut(300);
    });

    // Close modal on outside click
    $('#mxchat-rag-context-modal').on('click', function(e) {
        if ($(e.target).is('#mxchat-rag-context-modal')) {
            $(this).fadeOut(300);
        }
    });

    // Close modal on Escape key
    $(document).on('keydown', function(e) {
        if (e.key === 'Escape') {
            $('#mxchat-rag-context-modal').fadeOut(300);
        }
    });
});
```
