From 86d840344741b03ae2403ad2993f727ed6a4931d Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Tue, 24 Mar 2026 23:13:16 +0100 Subject: [PATCH] Add Expert session summary event with conversation quality signals Fire expert-session-summary when a conversation ends, capturing: - trigger: close, transfer-to-app, page-leave, or clear - duration_seconds: time modal was open - user_messages: total user messages in session - ai_responses_rich: guide/resource responses (high-value) - ai_responses_plain: chat responses (standard) Fires once per session (deduplicated). Covers all exit paths: modal close, transfer to app, page navigation, and conversation clear. --- src/js/ai-expert-modal.js | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/js/ai-expert-modal.js b/src/js/ai-expert-modal.js index 22b78ca505..d8dfd62843 100644 --- a/src/js/ai-expert-modal.js +++ b/src/js/ai-expert-modal.js @@ -25,6 +25,28 @@ document.addEventListener('DOMContentLoaded', function() { // Transaction ID for preventing race conditions let lastTransactionId = null; + // Session analytics + let sessionOpenedAt = null; + let richResponseCount = 0; + let plainResponseCount = 0; + let sessionSummarySent = false; + + function sendSessionSummary(trigger) { + if (sessionSummarySent || !sessionOpenedAt) return; + if (typeof capture !== 'function') return; + const userMsgs = messages.filter(m => m.role === 'human').length; + if (userMsgs === 0 && trigger !== 'close') return; + sessionSummarySent = true; + capture('expert-session-summary', { + trigger: trigger, + duration_seconds: Math.round((Date.now() - sessionOpenedAt) / 1000), + user_messages: userMsgs, + ai_responses_rich: richResponseCount, + ai_responses_plain: plainResponseCount, + page: location.pathname + }); + } + // Auto-scroll management let autoScrollEnabled = true; const scrollThreshold = 50; // pixels from bottom to consider "at bottom" @@ -417,6 +439,10 @@ document.addEventListener('DOMContentLoaded', function() { // Generate new session ID for this chat session sessionId = crypto.randomUUID(); transferPayload = [] + sessionOpenedAt = Date.now(); + richResponseCount = 0; + plainResponseCount = 0; + sessionSummarySent = false; // Reset auto-scroll to enabled when opening modal autoScrollEnabled = true; @@ -527,6 +553,8 @@ document.addEventListener('DOMContentLoaded', function() { } function closeModal() { + sendSessionSummary('close'); + const homeTextarea = document.querySelector('textarea[aria-label="Describe your workflow"]'); const homeTextareaWrapper = homeTextarea ? homeTextarea.closest('.textarea-wrapper') : null; const modalInputSection = modal.querySelector('.p-4.bg-white.rounded-b-none.md\\:rounded-b-lg'); @@ -760,6 +788,8 @@ document.addEventListener('DOMContentLoaded', function() { } async function clearConversation() { + sendSessionSummary('clear'); + // Stop any ongoing generation if (currentAbortController) { currentAbortController.abort(); @@ -966,6 +996,13 @@ document.addEventListener('DOMContentLoaded', function() { if (typeof aiMessage === 'string') { aiMessage = { kind: 'chat', content: aiMessage }; } + // Track response type for session summary + if (aiMessage.kind === 'guide' || aiMessage.kind === 'resources') { + richResponseCount++; + } else { + plainResponseCount++; + } + // Add to messages array const message = { content: '', type: 'ai', isHTML: true }; messages.push(message); @@ -1474,6 +1511,8 @@ document.addEventListener('DOMContentLoaded', function() { : 'https://app.flowfuse.com'; document.getElementById('continue-to-app').addEventListener('click', function handler() { + sendSessionSummary('transfer-to-app'); + // Reuse existing target window or create new one if it doesn't exist or was closed if (!target || target.closed) { target = window.open(appURL, 'flowfuse-app'); @@ -1514,4 +1553,9 @@ document.addEventListener('DOMContentLoaded', function() { window.addEventListener('message', messageHandler); } }); + + // Send session summary when user leaves the page + window.addEventListener('pagehide', function() { + sendSessionSummary('page-leave'); + }); });