PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.24
MxChat – AI Chatbot & Content Generation for WordPress v3.2.24
3.2.25 3.2.24 3.2.23 3.2.22 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 All 156 releases
← All changes | js/mxchat_transcripts.js +65 -13 3.2.19 → 3.2.24 View file →
@@ -984,11 +984,18 @@
984 984
985 985 function renderRagContext(data, $container) {
986 986 let html = '';
987 987
988 - // Check if we have any source data
989 - if (!data.top_matches || data.top_matches.length === 0) {
990 - html += '<div class="mxch-no-results"><p>No document matches found for this response.</p></div>';
988 + // 67fc92: "the KB was searched and nothing matched" and "nothing was
989 + // recorded for this row" are different facts. Retrieval keys present
990 + // means the search ran and was recorded (top_matches may be empty);
991 + // no retrieval keys means there is simply no record (older rows, or
992 + // turns that never consulted the knowledge base).
993 + const retrievalRecorded = typeof data.total_documents_checked !== 'undefined'
994 + || (data.top_matches && data.top_matches.length > 0);
995 +
996 + if (!retrievalRecorded) {
997 + html += '<div class="mxch-no-results"><p>No retrieval data was recorded for this response.</p></div>';
991 998 $container.html(html);
992 999 return;
993 1000 }
994 1001
@@ -994,9 +1001,10 @@
994 1001
995 1002 // Hybrid keyword boost (38ffa1): rows carry matched_via + fused_rank
996 1003 // when hybrid retrieval was on for this response. Cosine % stays the
997 1004 // anchor; the chip explains WHY a low-% row ranked high.
998 - const hybridOn = data.top_matches.some(function(m) { return m.matched_via; });
1005 + const topMatches = data.top_matches || [];
1006 + const hybridOn = topMatches.some(function(m) { return m.matched_via; });
999 1007
1000 1008 html += '<div class="mxch-rag-summary">';
1001 1009 html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Knowledge Base:</span> <span class="mxch-rag-value">' + escapeHtml(data.knowledge_base_type || 'WordPress Database') + '</span></div>';
1002 1010 html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Similarity Threshold:</span> <span class="mxch-rag-value">' + Math.round((data.similarity_threshold || 0.35) * 100) + '%</span></div>';
@@ -1005,8 +1013,14 @@
1005 1013 html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Retrieval:</span> <span class="mxch-rag-value">Hybrid</span></div>';
1006 1014 }
1007 1015 html += '</div>';
1008 1016
1017 + if (topMatches.length === 0) {
1018 + html += '<div class="mxch-no-results"><p>The knowledge base was searched &mdash; no documents matched this response.</p></div>';
1019 + $container.html(html);
1020 + return;
1021 + }
1022 +
1009 1023 const groupedByUrl = {};
1010 1024
1011 1025 data.top_matches.forEach(function(match) {
1012 1026 const url = match.source_display || 'Unknown';
@@ -1624,13 +1638,31 @@
1624 1638 });
1625 1639 $wrap.html(html);
1626 1640 }
1627 1641
1642 + // A lead's key is its email address, or "session:<id>" when the capture
1643 + // had no address (c0cfaf). Email keys are lowercased exactly as before;
1644 + // session keys pass through untouched, because the server resolves them
1645 + // back to a real session id and case matters there. Both shapes travel on
1646 + // the same wire field the tab has always used, so the selection set, the
1647 + // delete call and the export call need no new parameter.
1648 + function leadKeyOf(r) {
1649 + const email = (r && r.email ? String(r.email) : '').trim();
1650 + if (email) return email.toLowerCase();
1651 + if (r && r.lead_key) return String(r.lead_key);
1652 + return 'session:' + (r && r.latest_session_id ? r.latest_session_id : '');
1653 + }
1654 +
1655 + function normalizeLeadKey(value) {
1656 + const key = (value === undefined || value === null) ? '' : String(value);
1657 + return key.indexOf('session:') === 0 ? key : key.toLowerCase();
1658 + }
1659 +
1628 1660 function renderLeadsTable(rows) {
1629 1661 const $tbody = $('#mxch-leads-tbody');
1630 1662 if (!rows || rows.length === 0) {
1631 1663 $tbody.html(`
1632 - <tr><td colspan="6" class="mxch-leads-empty">
1664 + <tr><td colspan="7" class="mxch-leads-empty">
1633 1665 <div class="mxch-leads-empty-wrap">
1634 1666 <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
1635 1667 <p>No leads match the current filters.</p>
1636 1668 </div>
@@ -1640,9 +1672,9 @@
1640 1672 }
1641 1673
1642 1674 let html = '';
1643 1675 rows.forEach(function(r) {
1644 - const emailKey = (r.email || '').toLowerCase();
1676 + const emailKey = leadKeyOf(r);
1645 1677 const isChecked = leadsState.selected.has(emailKey) ? ' checked' : '';
1646 1678 // Status: 'active' (has conversations), 'chat_deleted' (admin removed the chat), 'orphan' (no chat ever).
1647 1679 const status = r.status || (r.is_orphan ? 'orphan' : 'active');
1648 1680 const isOrphan = (status === 'orphan');
@@ -1649,12 +1681,21 @@
1649 1681 const isChatDeleted = (status === 'chat_deleted');
1650 1682 const nameLine = r.name
1651 1683 ? `<span class="mxch-leads-lead-name">${escapeHtmlLeads(r.name)}</span>`
1652 1684 : '';
1685 + // A lead captured with no email address (c0cfaf) still has a
1686 + // name; show that on the primary line rather than an empty cell,
1687 + // and say plainly that there is no address.
1688 + const primaryLine = r.email
1689 + ? `<span class="mxch-leads-lead-email" title="${escapeHtmlLeads(r.email)}">${escapeHtmlLeads(r.email)}</span>`
1690 + : `<span class="mxch-leads-lead-email">${escapeHtmlLeads(r.name || 'Unnamed lead')}</span>`;
1691 + const secondaryLine = r.email
1692 + ? nameLine
1693 + : '<span class="mxch-leads-muted">No email address</span>';
1653 1694 const leadCell = `
1654 1695 <div class="mxch-leads-lead-cell">
1655 - <span class="mxch-leads-lead-email" title="${escapeHtmlLeads(r.email)}">${escapeHtmlLeads(r.email)}</span>
1656 - ${nameLine}
1696 + ${primaryLine}
1697 + ${secondaryLine}
1657 1698 </div>`;
1658 1699 let countCell;
1659 1700 if (isOrphan) {
1660 1701 countCell = `<span class="mxch-leads-pill mxch-leads-pill-orphan">Orphan</span>`;
@@ -1666,8 +1707,18 @@
1666 1707 const lastCell = escapeHtmlLeads(r.last_seen_display || (isOrphan ? 'No conversation yet' : ''));
1667 1708 const pageCell = r.top_page_url
1668 1709 ? `<a href="${escapeHtmlLeads(r.top_page_url)}" target="_blank" rel="noopener" class="mxch-leads-page-link" title="${escapeHtmlLeads(r.top_page_url)}">${escapeHtmlLeads(r.top_page_title || r.top_page_url)}</a>`
1669 1710 : '<span class="mxch-leads-muted">—</span>';
1711 + // Consent (b062c4): 'yes'/'no' are recorded decisions; '' means the
1712 + // capture predates the checkbox — "Not recorded", never "No".
1713 + let consentCell;
1714 + if (r.consent === 'yes' || r.consent === 'no') {
1715 + const consentTitle = (r.consent_at ? 'Recorded ' + r.consent_at : '') +
1716 + (r.consent_label ? (r.consent_at ? ' — ' : '') + '"' + r.consent_label + '"' : '');
1717 + consentCell = `<span class="mxch-leads-pill${r.consent === 'yes' ? ' mxch-leads-pill-consent-yes' : ' mxch-leads-pill-consent-no'}" title="${escapeHtmlLeads(consentTitle)}">${r.consent === 'yes' ? 'Yes' : 'No'}</span>`;
1718 + } else {
1719 + consentCell = '<span class="mxch-leads-muted" title="Captured before the consent checkbox was enabled">Not recorded</span>';
1720 + }
1670 1721 // View Convo only for active leads (orphans and chat_deleted have no viewable session).
1671 1722 const viewBtn = (status === 'active' && r.latest_session_id)
1672 1723 ? `<button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm mxch-leads-view" data-session-id="${escapeHtmlLeads(r.latest_session_id)}" title="View latest conversation">
1673 1724 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
@@ -1673,20 +1724,21 @@
1673 1724 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
1674 1725 <span>View convo</span>
1675 1726 </button>`
1676 1727 : '';
1677 - const deleteBtn = `<button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm mxch-btn-danger-ghost mxch-leads-delete-row" data-email="${escapeHtmlLeads(r.email)}" title="Delete lead">
1728 + const deleteBtn = `<button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm mxch-btn-danger-ghost mxch-leads-delete-row" data-email="${escapeHtmlLeads(emailKey)}" title="Delete lead">
1678 1729 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
1679 1730 </button>`;
1680 1731
1681 1732 const rowStateClass = isOrphan ? ' is-orphan' : (isChatDeleted ? ' is-chat-deleted' : '');
1682 1733 html += `
1683 - <tr class="mxch-leads-row${rowStateClass}" data-email="${escapeHtmlLeads(r.email)}">
1734 + <tr class="mxch-leads-row${rowStateClass}" data-email="${escapeHtmlLeads(emailKey)}">
1684 1735 <td class="mxch-leads-col-check"><input type="checkbox" class="mxch-leads-rowcheck"${isChecked}></td>
1685 1736 <td class="mxch-leads-col-lead">${leadCell}</td>
1686 1737 <td class="mxch-leads-col-count">${countCell}</td>
1687 1738 <td class="mxch-leads-col-last">${lastCell}</td>
1688 1739 <td class="mxch-leads-col-page">${pageCell}</td>
1740 + <td class="mxch-leads-col-consent">${consentCell}</td>
1689 1741 <td class="mxch-leads-col-actions">${viewBtn}${deleteBtn}</td>
1690 1742 </tr>
1691 1743 `;
1692 1744 });
@@ -1807,9 +1859,9 @@
1807 1859 $('#mxch-leads-select-all').on('change', function() {
1808 1860 const on = $(this).is(':checked');
1809 1861 $('.mxch-leads-rowcheck').prop('checked', on);
1810 1862 $('.mxch-leads-row').each(function() {
1811 - const email = ($(this).data('email') || '').toString().toLowerCase();
1863 + const email = normalizeLeadKey($(this).data('email'));
1812 1864 if (on) {
1813 1865 leadsState.selected.add(email);
1814 1866 } else {
1815 1867 leadsState.selected.delete(email);
@@ -1819,9 +1871,9 @@
1819 1871 });
1820 1872
1821 1873 // Row checkbox
1822 1874 $leads().on('change', '.mxch-leads-rowcheck', function() {
1823 - const email = ($(this).closest('.mxch-leads-row').data('email') || '').toString().toLowerCase();
1875 + const email = normalizeLeadKey($(this).closest('.mxch-leads-row').data('email'));
1824 1876 if ($(this).is(':checked')) {
1825 1877 leadsState.selected.add(email);
1826 1878 } else {
1827 1879 leadsState.selected.delete(email);
@@ -1887,9 +1939,9 @@
1887 1939 success: function(response) {
1888 1940 $btn.prop('disabled', false).text('Delete permanently');
1889 1941 closeLeadsConfirm();
1890 1942 if (response && response.success) {
1891 - emails.forEach(function(e) { leadsState.selected.delete(e.toLowerCase()); });
1943 + emails.forEach(function(e) { leadsState.selected.delete(normalizeLeadKey(e)); });
1892 1944 loadLeads(leadsState.page);
1893 1945 } else {
1894 1946 alert((response && response.data && response.data.message) || 'Failed to delete leads.');
1895 1947 }