From 66a3300eb4d71d25a765c269fe0194414bd2e64a Mon Sep 17 00:00:00 2001 From: Petya Naydenova Ferreira Date: Wed, 9 Jul 2025 13:17:03 +0200 Subject: [PATCH 1/4] Implement functionallity to collect user feedback --- .../chatbot/chatbot.controller.ts | 1 + src/knowledgebase/chatbot/chatbot.dto.ts | 3 + src/knowledgebase/chatbot/chatbot.service.ts | 21 ++- src/knowledgebase/knowledgebase-db.service.ts | 16 +++ src/knowledgebase/knowledgebase.schema.ts | 1 + widget/app.css | 85 +++++++++++- widget/index.html | 124 ++++++++++++++++++ 7 files changed, 243 insertions(+), 8 deletions(-) diff --git a/src/knowledgebase/chatbot/chatbot.controller.ts b/src/knowledgebase/chatbot/chatbot.controller.ts index fa16fe7..d6d435f 100644 --- a/src/knowledgebase/chatbot/chatbot.controller.ts +++ b/src/knowledgebase/chatbot/chatbot.controller.ts @@ -125,6 +125,7 @@ export class ChatbotController { id, data.msgIdx, data.feedback, + data.conversationFeedback || false, ); } diff --git a/src/knowledgebase/chatbot/chatbot.dto.ts b/src/knowledgebase/chatbot/chatbot.dto.ts index 88d4ff2..4c28555 100644 --- a/src/knowledgebase/chatbot/chatbot.dto.ts +++ b/src/knowledgebase/chatbot/chatbot.dto.ts @@ -25,6 +25,9 @@ export class SetChatbotSessionMsgFeedbackDTO { @IsEnum(ChatAnswerFeedbackType) feedback: ChatAnswerFeedbackType; + + @IsOptional() + conversationFeedback?: boolean; } export class ChatbotQueryDTO { diff --git a/src/knowledgebase/chatbot/chatbot.service.ts b/src/knowledgebase/chatbot/chatbot.service.ts index 72e2131..2f9cbb0 100644 --- a/src/knowledgebase/chatbot/chatbot.service.ts +++ b/src/knowledgebase/chatbot/chatbot.service.ts @@ -987,22 +987,31 @@ export class ChatbotService { } /** - * Set Feedback for Chat Session Msg by Msg Idx + * Set Feedback for Chat Session Msg by Msg Idx or Conversation Level * @param sessionId * @param msgIdx * @param feedback + * @param isConversationFeedback */ async setSessionMessageFeedback( sessionId: string, msgIdx: number, feedback: ChatAnswerFeedbackType, + isConversationFeedback = false, ) { try { - await this.kbDbService.setChatSessionMessageFeedback( - new ObjectId(sessionId), - msgIdx, - feedback, - ); + if (isConversationFeedback) { + await this.kbDbService.setChatSessionConversationFeedback( + new ObjectId(sessionId), + feedback, + ); + } else { + await this.kbDbService.setChatSessionMessageFeedback( + new ObjectId(sessionId), + msgIdx, + feedback, + ); + } } catch { throw new HttpException('Invalid Session', HttpStatus.NOT_FOUND); } diff --git a/src/knowledgebase/knowledgebase-db.service.ts b/src/knowledgebase/knowledgebase-db.service.ts index 1949963..4f139da 100644 --- a/src/knowledgebase/knowledgebase-db.service.ts +++ b/src/knowledgebase/knowledgebase-db.service.ts @@ -661,6 +661,22 @@ export class KnowledgebaseDbService { ); } + async setChatSessionConversationFeedback( + id: ObjectId, + feedback: ChatAnswerFeedbackType, + ) { + await this.chatSessionCollection.updateOne( + { + _id: id, + }, + { + $set: { + conversationFeedback: feedback, + }, + }, + ); + } + /** ******************************************* * PROMPT RELATED ******************************************** */ diff --git a/src/knowledgebase/knowledgebase.schema.ts b/src/knowledgebase/knowledgebase.schema.ts index 8947dfd..0298ddb 100644 --- a/src/knowledgebase/knowledgebase.schema.ts +++ b/src/knowledgebase/knowledgebase.schema.ts @@ -207,6 +207,7 @@ export interface ChatSession { startedAt: Date; updatedAt: Date; embeddingModel?: EmbeddingModel; + conversationFeedback?: ChatAnswerFeedbackType; } export type ChatSessionSparse = Pick< ChatSession, diff --git a/widget/app.css b/widget/app.css index 37f52da..0e2d442 100644 --- a/widget/app.css +++ b/widget/app.css @@ -810,7 +810,7 @@ span.form-required { } @keyframes jumpingAnimation { - 0 { + 0% { transform: translate(0, 0); } @@ -828,7 +828,7 @@ span.form-required { } @keyframes jumpingAnimationSmall { - 0 { + 0% { transform: translate(0, 0); } @@ -1067,4 +1067,85 @@ a.chat-source-btn { height: 14px; margin-right: 5px; color: #757575; +} + +/* Persistent conversation feedback at bottom */ +.conversation-feedback-persistent { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + padding: 15px 20px; + border-top: 1px solid #e5e7eb; + background: #fafafa; + margin-bottom: 0; +} + +.conversation-feedback-persistent .feedback-question { + font-size: 14px; + color: #6b7280; + font-weight: 500; + text-align: center; +} + +.conversation-feedback-persistent .feedback-buttons { + display: flex; + gap: 12px; + align-items: center; +} + +.conversation-feedback-persistent .feedback-btn { + background: white; + border: 2px solid #e5e7eb; + cursor: pointer; + padding: 10px 12px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + opacity: 1; +} + +.conversation-feedback-persistent .feedback-btn:hover { + border-color: #d1d5db; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.conversation-feedback-persistent .feedback-btn svg { + width: 20px; + height: 20px; + stroke: currentColor; + fill: none; +} + +.conversation-feedback-persistent .feedback-btn.conversation-thumbs-up { + color: #10b981; +} + +.conversation-feedback-persistent .feedback-btn.conversation-thumbs-down { + color: #ef4444; +} + +.conversation-feedback-persistent .feedback-btn.selected { + border-color: currentColor; + background-color: rgba(16, 185, 129, 0.1); +} + +.conversation-feedback-persistent .feedback-btn.conversation-thumbs-down.selected { + background-color: rgba(239, 68, 68, 0.1); +} + +.conversation-feedback-persistent .feedback-btn:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.conversation-feedback-persistent .feedback-thank-you { + font-size: 14px; + color: #10b981; + font-weight: 500; + text-align: center; + margin-top: 5px; } \ No newline at end of file diff --git a/widget/index.html b/widget/index.html index 306c11c..a139556 100644 --- a/widget/index.html +++ b/widget/index.html @@ -165,6 +165,24 @@

+ + +
@@ -214,6 +232,7 @@

let sessionId = ''; + let messageIndex = 0; // Counter for tracking message indices for feedback function getDomainFromUrl(url) { if (!url) return ''; @@ -315,6 +334,101 @@

return str.replace(new RegExp(escapeRegExp(find), 'g'), replace); } + // Conversation-level feedback functions + async function submitConversationFeedback(feedback) { + try { + console.log('Submitting conversation feedback:', { feedback, sessionId }); + + // For now, we'll send it to the same endpoint but with msgIdx: -1 to indicate it's conversation-level + // You may want to create a separate endpoint for this + const response = await fetch(`${baseURL}/chatbot/session/${sessionId}/feedback`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + msgIdx: -1, // Special value to indicate conversation-level feedback + feedback: feedback, + conversationFeedback: true // Additional flag + }) + }); + + console.log('Conversation feedback response status:', response.status); + + if (response.ok) { + const responseText = await response.text(); + console.log('Conversation feedback submitted successfully:', responseText); + return true; + } else { + const errorText = await response.text(); + console.error('Failed to submit conversation feedback:', response.status, errorText); + return false; + } + } catch (error) { + console.error('Error submitting conversation feedback:', error); + return false; + } + } + + // Persistent feedback functions + function showPersistentFeedback() { + const feedbackDiv = document.getElementById('conversation-feedback-persistent'); + if (feedbackDiv && messageIndex > 1) { // Only show if there's been actual conversation + feedbackDiv.style.display = 'flex'; + + // Add event listeners to feedback buttons if not already added + const feedbackButtons = feedbackDiv.querySelectorAll('.feedback-btn'); + feedbackButtons.forEach(btn => { + // Remove existing listeners to avoid duplicates + btn.removeEventListener('click', handlePersistentFeedbackClick); + btn.addEventListener('click', handlePersistentFeedbackClick); + }); + } + } + + function hidePersistentFeedback() { + const feedbackDiv = document.getElementById('conversation-feedback-persistent'); + if (feedbackDiv) { + feedbackDiv.style.display = 'none'; + } + } + + function handlePersistentFeedbackClick(event) { + const btn = event.currentTarget; + const feedback = btn.getAttribute('data-feedback'); + + // Disable all feedback buttons + const allButtons = document.querySelectorAll('.conversation-feedback-persistent .feedback-btn'); + allButtons.forEach(button => { + button.disabled = true; + button.classList.remove('selected'); + }); + + // Mark the clicked button as selected + btn.classList.add('selected'); + + // Submit conversation feedback + submitConversationFeedback(feedback).then(success => { + if (success) { + // Show thank you message + const thankYou = document.querySelector('.conversation-feedback-persistent .feedback-thank-you'); + if (thankYou) { + thankYou.style.display = 'block'; + + // Hide the feedback UI after a short delay + setTimeout(() => { + hidePersistentFeedback(); + }, 2000); + } + } else { + // Re-enable buttons on error + allButtons.forEach(button => { + button.disabled = false; + }); + btn.classList.remove('selected'); + } + }); + } function resetChatHistory() { const chatMessages = document.getElementById('chat-messages'); @@ -940,6 +1054,7 @@

const chatMessages = document.getElementById('chat-messages'); chatMessages.insertAdjacentHTML("beforeend", `
${message}
`) + messageIndex++; // Increment message counter for user messages too const lastMesage = chatMessages.lastChild.querySelector('.chat-message-text'); chatMessages.scrollTop = chatMessages.scrollHeight; @@ -1017,6 +1132,11 @@

msg = replaceAll(msg, '
', '\n') const markdownRes = marked.parse(msg); msgHolder.innerHTML = markdownRes; + messageIndex++; // Increment message counter + + // Show persistent feedback UI after first bot response + showPersistentFeedback(); + if (collectEmail && isFirstMessage) { const collectEmailMarkup = `
) chatMessages.insertAdjacentHTML("beforeend", `
${markdownRes}
`) + messageIndex++; // Increment message counter + + // Show persistent feedback UI after bot response + showPersistentFeedback(); } } From 5848739466616ca0d13d338ee3487c65f74c8a98 Mon Sep 17 00:00:00 2001 From: Petya Naydenova Ferreira Date: Wed, 9 Jul 2025 13:47:06 +0200 Subject: [PATCH 2/4] widget feedback - don't hide the thank you message after feedback is given --- widget/index.html | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/widget/index.html b/widget/index.html index a139556..910a1ee 100644 --- a/widget/index.html +++ b/widget/index.html @@ -386,21 +386,13 @@

} } - function hidePersistentFeedback() { - const feedbackDiv = document.getElementById('conversation-feedback-persistent'); - if (feedbackDiv) { - feedbackDiv.style.display = 'none'; - } - } - function handlePersistentFeedbackClick(event) { const btn = event.currentTarget; const feedback = btn.getAttribute('data-feedback'); - // Disable all feedback buttons + // Remove selection from all buttons and mark clicked button as selected const allButtons = document.querySelectorAll('.conversation-feedback-persistent .feedback-btn'); allButtons.forEach(button => { - button.disabled = true; button.classList.remove('selected'); }); @@ -410,21 +402,14 @@

// Submit conversation feedback submitConversationFeedback(feedback).then(success => { if (success) { - // Show thank you message + // Show thank you message briefly const thankYou = document.querySelector('.conversation-feedback-persistent .feedback-thank-you'); if (thankYou) { thankYou.style.display = 'block'; - // Hide the feedback UI after a short delay - setTimeout(() => { - hidePersistentFeedback(); - }, 2000); } } else { - // Re-enable buttons on error - allButtons.forEach(button => { - button.disabled = false; - }); + // Remove selection on error btn.classList.remove('selected'); } }); From 3ecf04a9eb229ebfe3a8c123296d7e4955413bce Mon Sep 17 00:00:00 2001 From: Petya Naydenova Ferreira Date: Wed, 9 Jul 2025 14:01:29 +0200 Subject: [PATCH 3/4] clean up console.logs --- widget/index.html | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/widget/index.html b/widget/index.html index 910a1ee..ffb5617 100644 --- a/widget/index.html +++ b/widget/index.html @@ -337,10 +337,7 @@

// Conversation-level feedback functions async function submitConversationFeedback(feedback) { try { - console.log('Submitting conversation feedback:', { feedback, sessionId }); - - // For now, we'll send it to the same endpoint but with msgIdx: -1 to indicate it's conversation-level - // You may want to create a separate endpoint for this + // For now, we'll send it to the same endpoint but with msgIdx: -1 to indicate it's conversation-level, but we should consider a dedicated endpoint in the future const response = await fetch(`${baseURL}/chatbot/session/${sessionId}/feedback`, { method: 'PUT', headers: { @@ -353,11 +350,8 @@

}) }); - console.log('Conversation feedback response status:', response.status); - if (response.ok) { const responseText = await response.text(); - console.log('Conversation feedback submitted successfully:', responseText); return true; } else { const errorText = await response.text(); From 5fc098190a4380d88eb799169e7570881733d7ec Mon Sep 17 00:00:00 2001 From: Petya Naydenova Ferreira Date: Wed, 9 Jul 2025 17:17:00 +0200 Subject: [PATCH 4/4] update style of the feedback buttons in the widget --- widget/app.css | 50 +++++++++++++++-------------------------------- widget/index.html | 11 +---------- 2 files changed, 17 insertions(+), 44 deletions(-) diff --git a/widget/app.css b/widget/app.css index 0e2d442..f77a896 100644 --- a/widget/app.css +++ b/widget/app.css @@ -1071,21 +1071,14 @@ a.chat-source-btn { /* Persistent conversation feedback at bottom */ .conversation-feedback-persistent { - display: flex; - flex-direction: column; - align-items: center; - gap: 10px; - padding: 15px 20px; - border-top: 1px solid #e5e7eb; - background: #fafafa; - margin-bottom: 0; -} - -.conversation-feedback-persistent .feedback-question { - font-size: 14px; - color: #6b7280; - font-weight: 500; - text-align: center; + font-size: 12px; + margin-right: 5px; + margin-bottom: 5px; + cursor: pointer; + padding: 5px 8px; + color: #333; + white-space: nowrap; + width: fit-content; } .conversation-feedback-persistent .feedback-buttons { @@ -1097,9 +1090,10 @@ a.chat-source-btn { .conversation-feedback-persistent .feedback-btn { background: white; border: 2px solid #e5e7eb; + box-shadow: 0px 5px 6px -4px rgba(0, 0, 0, 0.15); cursor: pointer; padding: 10px 12px; - border-radius: 8px; + border-radius: 12px; display: flex; align-items: center; justify-content: center; @@ -1108,9 +1102,9 @@ a.chat-source-btn { } .conversation-feedback-persistent .feedback-btn:hover { - border-color: #d1d5db; + background-color: rgba(0, 0, 0, 0.04); + box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.08); transform: translateY(-1px); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .conversation-feedback-persistent .feedback-btn svg { @@ -1129,23 +1123,11 @@ a.chat-source-btn { } .conversation-feedback-persistent .feedback-btn.selected { - border-color: currentColor; - background-color: rgba(16, 185, 129, 0.1); + background-color: rgba(0, 0, 0, 0.04); + box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.08); } .conversation-feedback-persistent .feedback-btn.conversation-thumbs-down.selected { - background-color: rgba(239, 68, 68, 0.1); -} - -.conversation-feedback-persistent .feedback-btn:disabled { - cursor: not-allowed; - opacity: 0.5; -} - -.conversation-feedback-persistent .feedback-thank-you { - font-size: 14px; - color: #10b981; - font-weight: 500; - text-align: center; - margin-top: 5px; + background-color: rgba(0, 0, 0, 0.04); + box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.08); } \ No newline at end of file diff --git a/widget/index.html b/widget/index.html index ffb5617..131ce36 100644 --- a/widget/index.html +++ b/widget/index.html @@ -167,7 +167,6 @@

@@ -395,14 +393,7 @@

// Submit conversation feedback submitConversationFeedback(feedback).then(success => { - if (success) { - // Show thank you message briefly - const thankYou = document.querySelector('.conversation-feedback-persistent .feedback-thank-you'); - if (thankYou) { - thankYou.style.display = 'block'; - - } - } else { + if (!success) { // Remove selection on error btn.classList.remove('selected'); }