Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/knowledgebase/chatbot/chatbot.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export class ChatbotController {
id,
data.msgIdx,
data.feedback,
data.conversationFeedback || false,
);
}

Expand Down
3 changes: 3 additions & 0 deletions src/knowledgebase/chatbot/chatbot.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export class SetChatbotSessionMsgFeedbackDTO {

@IsEnum(ChatAnswerFeedbackType)
feedback: ChatAnswerFeedbackType;

@IsOptional()
conversationFeedback?: boolean;
}

export class ChatbotQueryDTO {
Expand Down
21 changes: 15 additions & 6 deletions src/knowledgebase/chatbot/chatbot.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
16 changes: 16 additions & 0 deletions src/knowledgebase/knowledgebase-db.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,22 @@ export class KnowledgebaseDbService {
);
}

async setChatSessionConversationFeedback(
id: ObjectId,
feedback: ChatAnswerFeedbackType,
) {
await this.chatSessionCollection.updateOne(
{
_id: id,
},
{
$set: {
conversationFeedback: feedback,
},
},
);
}

/** *******************************************
* PROMPT RELATED
******************************************** */
Expand Down
1 change: 1 addition & 0 deletions src/knowledgebase/knowledgebase.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export interface ChatSession {
startedAt: Date;
updatedAt: Date;
embeddingModel?: EmbeddingModel;
conversationFeedback?: ChatAnswerFeedbackType;
}
export type ChatSessionSparse = Pick<
ChatSession,
Expand Down
67 changes: 65 additions & 2 deletions widget/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,7 @@ span.form-required {
}

@keyframes jumpingAnimation {
0 {
0% {
transform: translate(0, 0);
}

Expand All @@ -828,7 +828,7 @@ span.form-required {
}

@keyframes jumpingAnimationSmall {
0 {
0% {
transform: translate(0, 0);
}

Expand Down Expand Up @@ -1067,4 +1067,67 @@ a.chat-source-btn {
height: 14px;
margin-right: 5px;
color: #757575;
}

/* Persistent conversation feedback at bottom */
.conversation-feedback-persistent {
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 {
display: flex;
gap: 12px;
align-items: center;
}

.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: 12px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
opacity: 1;
}

.conversation-feedback-persistent .feedback-btn:hover {
background-color: rgba(0, 0, 0, 0.04);
box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.08);
transform: translateY(-1px);
}

.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 {
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(0, 0, 0, 0.04);
box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.08);
}
94 changes: 94 additions & 0 deletions widget/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,22 @@ <h2 id="offline-message-heading">
</div>
</div>

<!-- Conversation Feedback UI - Always visible at bottom -->
<div id="conversation-feedback-persistent" class="conversation-feedback-persistent" style="display: none;">
<div class="feedback-buttons">
<button class="feedback-btn conversation-thumbs-up" data-feedback="GOOD" title="Good experience">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button class="feedback-btn conversation-thumbs-down" data-feedback="BAD" title="Bad experience">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>

<form id="chat-form" class="chat-input-wrap">
<textarea rows='1' class="chat-input textarea js-auto-size" id="chat-input"
placeholder="Type your message"></textarea>
Expand Down Expand Up @@ -214,6 +230,7 @@ <h2 id="offline-message-heading">


let sessionId = '';
let messageIndex = 0; // Counter for tracking message indices for feedback

function getDomainFromUrl(url) {
if (!url) return '';
Expand Down Expand Up @@ -315,6 +332,73 @@ <h2 id="offline-message-heading">
return str.replace(new RegExp(escapeRegExp(find), 'g'), replace);
}

// Conversation-level feedback functions
async function submitConversationFeedback(feedback) {
try {
// 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: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
msgIdx: -1, // Special value to indicate conversation-level feedback
feedback: feedback,
conversationFeedback: true // Additional flag
})
});

if (response.ok) {
const responseText = await response.text();
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 handlePersistentFeedbackClick(event) {
const btn = event.currentTarget;
const feedback = btn.getAttribute('data-feedback');

// Remove selection from all buttons and mark clicked button as selected
const allButtons = document.querySelectorAll('.conversation-feedback-persistent .feedback-btn');
allButtons.forEach(button => {
button.classList.remove('selected');
});

// Mark the clicked button as selected
btn.classList.add('selected');

// Submit conversation feedback
submitConversationFeedback(feedback).then(success => {
if (!success) {
// Remove selection on error
btn.classList.remove('selected');
}
});
}

function resetChatHistory() {
const chatMessages = document.getElementById('chat-messages');
Expand Down Expand Up @@ -940,6 +1024,7 @@ <h2 id="offline-message-heading">
const chatMessages = document.getElementById('chat-messages');
chatMessages.insertAdjacentHTML("beforeend",
`<div class="chat-message user"><div class="chat-message-text">${message}</div></div>`)
messageIndex++; // Increment message counter for user messages too

const lastMesage = chatMessages.lastChild.querySelector('.chat-message-text');
chatMessages.scrollTop = chatMessages.scrollHeight;
Expand Down Expand Up @@ -1017,6 +1102,11 @@ <h2 id="offline-message-heading">
msg = replaceAll(msg, '<br/>', '\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 = `<div class="chat-message chatbot">
<div
Expand Down Expand Up @@ -1087,6 +1177,10 @@ <h2 id="offline-message-heading">
)
chatMessages.insertAdjacentHTML("beforeend",
`<div class="chat-message chatbot"><div class="chat-message-text">${markdownRes}</div></div>`)
messageIndex++; // Increment message counter

// Show persistent feedback UI after bot response
showPersistentFeedback();
}
}

Expand Down