PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.8
MxChat – AI Chatbot & Content Generation for WordPress v3.2.8
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/mxchat_transcripts.js +14 -148 3.2.213.2.8 View file →
@@ -607,19 +607,13 @@
607 607 <div class="mxch-message-time">${escapeHtml(msg.timestamp)}</div>
608 608 </div>
609 609 `;
610 610 } else {
611 - // Live-agent replies (role 'agent') get their own label and never
612 - // carry a Sources link; bot/assistant rows render exactly as before.
613 - const isAgent = !!msg.is_agent;
614 - const ragLink = (!isAgent && msg.has_rag) ? `<a href="#" class="mxch-rag-link" data-message-id="${msg.id}">Sources</a>` : '';
615 - const senderLabel = isAgent
616 - ? '<span class="mxch-agent-label">Live Agent</span>'
617 - : '<span class="mxch-bot-label">AI Assistant</span>';
611 + const ragLink = msg.has_rag ? `<a href="#" class="mxch-rag-link" data-message-id="${msg.id}">Sources</a>` : '';
618 612 messagesHtml += `
619 - <div class="mxch-message mxch-message-bot${isAgent ? ' mxch-message-agent' : ''}" data-message-id="${msg.id}">
613 + <div class="mxch-message mxch-message-bot" data-message-id="${msg.id}">
620 614 <div class="mxch-message-header">
621 - ${senderLabel}
615 + <span class="mxch-bot-label">AI Assistant</span>
622 616 ${ragLink}
623 617 </div>
624 618 <div class="mxch-message-row">
625 619 <div class="mxch-message-bubble">
@@ -943,12 +937,9 @@
943 937 renderActionsContext(response.data, $actionsContent);
944 938
945 939 // Update badge counts
946 940 const sourcesCount = response.data.top_matches ? response.data.top_matches.length : 0;
947 - // 470f68: the Actions tab now carries two mechanisms — tools
948 - // that ran and trigger phrases that scored. Badge counts both.
949 - const toolCallsCount = response.data.tool_calls ? response.data.tool_calls.length : 0;
950 - const actionsCount = (response.data.action_analysis ? response.data.action_analysis.length : 0) + toolCallsCount;
941 + const actionsCount = response.data.action_analysis ? response.data.action_analysis.length : 0;
951 942
952 943 if (sourcesCount > 0) {
953 944 $('#mxch-sources-count').text(sourcesCount).show();
954 945 }
@@ -984,43 +975,21 @@
984 975
985 976 function renderRagContext(data, $container) {
986 977 let html = '';
987 978
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>';
979 + // Check if we have any source data
980 + if (!data.top_matches || data.top_matches.length === 0) {
981 + html += '<div class="mxch-no-results"><p>No document matches found for this response.</p></div>';
998 982 $container.html(html);
999 983 return;
1000 984 }
1001 985
1002 - // Hybrid keyword boost (38ffa1): rows carry matched_via + fused_rank
1003 - // when hybrid retrieval was on for this response. Cosine % stays the
1004 - // anchor; the chip explains WHY a low-% row ranked high.
1005 - const topMatches = data.top_matches || [];
1006 - const hybridOn = topMatches.some(function(m) { return m.matched_via; });
1007 -
1008 986 html += '<div class="mxch-rag-summary">';
1009 987 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>';
1010 988 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>';
1011 989 html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Documents Checked:</span> <span class="mxch-rag-value">' + (data.total_documents_checked || 0) + '</span></div>';
1012 - if (hybridOn) {
1013 - html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Retrieval:</span> <span class="mxch-rag-value">Hybrid</span></div>';
1014 - }
1015 990 html += '</div>';
1016 991
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 -
1023 992 const groupedByUrl = {};
1024 993
1025 994 data.top_matches.forEach(function(match) {
1026 995 const url = match.source_display || 'Unknown';
@@ -1029,11 +998,9 @@
1029 998 url: url,
1030 999 isUrl: url.startsWith('http'),
1031 1000 bestScore: 0,
1032 1001 usedForContext: false,
1033 - matchedChunks: [],
1034 - bestFusedRank: Infinity,
1035 - viaSet: {}
1002 + matchedChunks: []
1036 1003 };
1037 1004 }
1038 1005
1039 1006 if (match.similarity_percentage > groupedByUrl[url].bestScore) {
@@ -1043,15 +1010,8 @@
1043 1010 if (match.used_for_context) {
1044 1011 groupedByUrl[url].usedForContext = true;
1045 1012 }
1046 1013
1047 - if (match.fused_rank && match.fused_rank < groupedByUrl[url].bestFusedRank) {
1048 - groupedByUrl[url].bestFusedRank = match.fused_rank;
1049 - }
1050 - if (match.matched_via) {
1051 - groupedByUrl[url].viaSet[match.matched_via] = true;
1052 - }
1053 -
1054 1014 groupedByUrl[url].matchedChunks.push({
1055 1015 chunkIndex: match.chunk_index,
1056 1016 score: match.similarity_percentage,
1057 1017 usedForContext: match.used_for_context
@@ -1057,16 +1017,9 @@
1057 1017 usedForContext: match.used_for_context
1058 1018 });
1059 1019 });
1060 1020
1061 - // Hybrid on: order by fused rank (rows without one sort last);
1062 - // otherwise by cosine, exactly as before.
1063 - const urlGroups = Object.values(groupedByUrl).sort(function(a, b) {
1064 - if (hybridOn && a.bestFusedRank !== b.bestFusedRank) {
1065 - return a.bestFusedRank - b.bestFusedRank;
1066 - }
1067 - return b.bestScore - a.bestScore;
1068 - });
1021 + const urlGroups = Object.values(groupedByUrl).sort((a, b) => b.bestScore - a.bestScore);
1069 1022 const usedUrlCount = data.sources_used > 0 ? data.sources_used : urlGroups.filter(g => g.usedForContext).length;
1070 1023 const chunksInfo = data.total_chunks_used > 0 ? data.total_chunks_used + ' chunks sent to AI' : '';
1071 1024
1072 1025 html += '<div class="mxch-rag-matches">';
@@ -1081,17 +1034,8 @@
1081 1034 html += '<div class="mxch-rag-match-card ' + cardClass + '">';
1082 1035 html += '<div class="mxch-rag-match-header">';
1083 1036 html += '<span class="mxch-rag-match-score">' + group.bestScore + '%</span>';
1084 1037
1085 - if (hybridOn) {
1086 - const vias = Object.keys(group.viaSet);
1087 - if (vias.length) {
1088 - const viaLabel = (vias.length > 1 || vias[0] === 'both') ? 'Both'
1089 - : (vias[0] === 'keyword' ? 'Keyword' : 'Vector');
1090 - html += '<span class="mxch-rag-via-chip mxch-rag-via-' + viaLabel.toLowerCase() + '">' + viaLabel + '</span>';
1091 - }
1092 - }
1093 -
1094 1038 if (group.matchedChunks.length > 1) {
1095 1039 html += '<span class="mxch-rag-chunk-badge">' + group.matchedChunks.length + ' chunks</span>';
1096 1040 }
1097 1041
@@ -1111,86 +1055,19 @@
1111 1055 html += '</div>';
1112 1056 $container.html(html);
1113 1057 }
1114 1058
1115 - // AI Tools trace (470f68): one card per tool EXECUTION for this message.
1116 - // Rendered ABOVE the trigger-phrase scores — a tool that actually ran
1117 - // outranks a phrase that merely scored.
1118 - function renderToolCalls(toolCalls) {
1119 - const failed = toolCalls.filter(function(t) { return !t.ok; }).length;
1120 -
1121 - let html = '<div class="mxch-rag-summary">';
1122 - html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Tools Used:</span> <span class="mxch-rag-value">' + toolCalls.length + '</span></div>';
1123 - if (failed > 0) {
1124 - html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Failed:</span> <span class="mxch-rag-value" style="color: #ef4444; font-weight: 600;">' + failed + '</span></div>';
1125 - }
1126 - html += '</div>';
1127 -
1128 - html += '<div class="mxch-rag-matches">';
1129 - html += '<h3>AI Tools</h3>';
1130 - html += '<p style="color: var(--mxch-text-secondary); font-size: 13px; margin-bottom: 16px;">Tools the assistant chose and ran for this message, in order.</p>';
1131 -
1132 - toolCalls.forEach(function(tool) {
1133 - const ok = !!tool.ok;
1134 - const cardClass = ok ? 'mxch-rag-match-used' : 'mxch-tool-failed';
1135 - const statusClass = ok ? 'status-used' : 'status-failed';
1136 - const statusIcon = ok ? '&#10003;' : '&#10007;';
1137 - const statusLabel = ok ? 'Ran' : 'Failed';
1138 -
1139 - html += '<div class="mxch-rag-match-card ' + cardClass + '">';
1140 - html += '<div class="mxch-rag-match-header">';
1141 - html += '<span class="mxch-rag-match-score">&#9889;</span>';
1142 - if (typeof tool.ms === 'number') {
1143 - html += '<span class="mxch-tool-duration">' + tool.ms + ' ms</span>';
1144 - }
1145 - html += '<span class="mxch-rag-match-status ' + statusClass + '">' + statusIcon + ' ' + statusLabel + '</span>';
1146 - html += '</div>';
1147 -
1148 - html += '<div class="mxch-action-details">';
1149 - html += '<div class="mxch-action-label">' + escapeHtml(tool.label || tool.name) + '</div>';
1150 - html += '<div class="mxch-action-callback"><span class="mxch-action-callback-label">Tool:</span> ' + escapeHtml(tool.name) + '</div>';
1151 - html += '</div>';
1152 -
1153 - if (!ok && tool.error) {
1154 - html += '<div class="mxch-tool-error">' + escapeHtml(tool.error) + '</div>';
1155 - }
1156 -
1157 - if (tool.args_redacted === 'sensitive') {
1158 - html += '<div class="mxch-tool-redacted">Arguments not recorded &mdash; this tool is marked sensitive.</div>';
1159 - } else if (tool.args_excerpt) {
1160 - html += '<details class="mxch-tool-args"><summary>Arguments</summary>';
1161 - html += '<div class="mxch-tool-args-body">' + escapeHtml(tool.args_excerpt) + '</div></details>';
1162 - }
1163 -
1164 - html += '</div>';
1165 - });
1166 -
1167 - html += '</div>';
1168 - return html;
1169 - }
1170 -
1171 1059 function renderActionsContext(data, $container) {
1172 1060 let html = '';
1173 1061
1174 - const toolCalls = (data.tool_calls && data.tool_calls.length) ? data.tool_calls : [];
1175 - const actions = (data.action_analysis && data.action_analysis.length) ? data.action_analysis : [];
1176 -
1177 - // 95d79d's empty state now shows only when NEITHER mechanism produced
1178 - // anything — the string itself is unchanged.
1179 - if (toolCalls.length === 0 && actions.length === 0) {
1180 - html += '<div class="mxch-no-results"><p>No trigger-phrase analysis for this message.</p><p style="color: var(--mxch-text-secondary); font-size: 13px; margin-top: 8px;">This panel shows how your <strong>Trigger Phrases</strong> scored &mdash; it stays empty if you have none enabled for this bot, or if the answer came from <strong>AI Tools</strong>, which don\'t produce similarity scores. It\'s recorded when the answer is generated, so it won\'t appear on older conversations.</p></div>';
1062 + // Check if we have action analysis data
1063 + if (!data.action_analysis || data.action_analysis.length === 0) {
1064 + html += '<div class="mxch-no-results"><p>No action analysis available for this message.</p><p style="color: var(--mxch-text-secondary); font-size: 13px; margin-top: 8px;">Actions are only evaluated when enabled in your bot configuration.</p></div>';
1181 1065 $container.html(html);
1182 1066 return;
1183 1067 }
1184 1068
1185 - if (toolCalls.length > 0) {
1186 - html += renderToolCalls(toolCalls);
1187 - }
1188 -
1189 - if (actions.length === 0) {
1190 - $container.html(html);
1191 - return;
1192 - }
1069 + const actions = data.action_analysis;
1193 1070 const triggeredAction = actions.find(a => a.triggered);
1194 1071 const actionsAboveThreshold = actions.filter(a => a.above_threshold).length;
1195 1072
1196 1073 // Summary section
@@ -1642,9 +1519,9 @@
1642 1519 function renderLeadsTable(rows) {
1643 1520 const $tbody = $('#mxch-leads-tbody');
1644 1521 if (!rows || rows.length === 0) {
1645 1522 $tbody.html(`
1646 - <tr><td colspan="7" class="mxch-leads-empty">
1523 + <tr><td colspan="6" class="mxch-leads-empty">
1647 1524 <div class="mxch-leads-empty-wrap">
1648 1525 <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>
1649 1526 <p>No leads match the current filters.</p>
1650 1527 </div>
@@ -1680,18 +1557,8 @@
1680 1557 const lastCell = escapeHtmlLeads(r.last_seen_display || (isOrphan ? 'No conversation yet' : ''));
1681 1558 const pageCell = r.top_page_url
1682 1559 ? `<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>`
1683 1560 : '<span class="mxch-leads-muted">—</span>';
1684 - // Consent (b062c4): 'yes'/'no' are recorded decisions; '' means the
1685 - // capture predates the checkbox — "Not recorded", never "No".
1686 - let consentCell;
1687 - if (r.consent === 'yes' || r.consent === 'no') {
1688 - const consentTitle = (r.consent_at ? 'Recorded ' + r.consent_at : '') +
1689 - (r.consent_label ? (r.consent_at ? ' — ' : '') + '"' + r.consent_label + '"' : '');
1690 - 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>`;
1691 - } else {
1692 - consentCell = '<span class="mxch-leads-muted" title="Captured before the consent checkbox was enabled">Not recorded</span>';
1693 - }
1694 1561 // View Convo only for active leads (orphans and chat_deleted have no viewable session).
1695 1562 const viewBtn = (status === 'active' && r.latest_session_id)
1696 1563 ? `<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">
1697 1564 <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>
@@ -1709,9 +1576,8 @@
1709 1576 <td class="mxch-leads-col-lead">${leadCell}</td>
1710 1577 <td class="mxch-leads-col-count">${countCell}</td>
1711 1578 <td class="mxch-leads-col-last">${lastCell}</td>
1712 1579 <td class="mxch-leads-col-page">${pageCell}</td>
1713 - <td class="mxch-leads-col-consent">${consentCell}</td>
1714 1580 <td class="mxch-leads-col-actions">${viewBtn}${deleteBtn}</td>
1715 1581 </tr>
1716 1582 `;
1717 1583 });