Skip to content
Open
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
5 changes: 5 additions & 0 deletions backend/src/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const chatMessageSchema = new mongoose.Schema({
default: Date.now,
index: true,
},
userReaction:{
type:String,
enum:['like','dislike','none'],
default:'none'
},
metadata: {
processingTime: Number,
tokensUsed: Number,
Expand Down
28 changes: 28 additions & 0 deletions backend/src/routes/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,34 @@ router.post("/documents/train", async (req, res) => {
}
});

/**
* PUT /api/chat/userreaction/:messageId
* Update the reaction (like or dislike) for a chat message.
*/
router.put('/userreaction/:messageId',async(req,res)=>{
try{

const {messageId}=req.params
const {userReaction}=req.body

if (!messageId){
return res.status(404).json({
success: false,
error: "Message Id is not found",
});}
const updateMessage=await ChatMessage.findByIdAndUpdate(messageId,{userReaction},{new:true})
res.status(200).json(updateMessage);

}
catch(error){
logger.error("Error update message reaction", { error: error.message });
res.status(500).json({
success: false,
error: "Failed to update message reaction",
});
}
})

/**
* GET /api/chat/stats
* Get chatbot statistics
Expand Down
71 changes: 69 additions & 2 deletions frontend/src/containers/ChatBot/ChatBotWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import {
CommentOutlined,
BookOutlined,
QuestionCircleOutlined,
ClockCircleOutlined ,
LikeOutlined,
DislikeOutlined

} from "@ant-design/icons";
import { Button, Input, AutoComplete, Dropdown, Menu,Tabs } from "antd";
import { useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState,useCallback } from "react";
import ReactMarkdown from "react-markdown";
import { useDispatch, useSelector } from "react-redux";
import styled, { keyframes } from "styled-components";
import { clearMessages, sendMessage } from "./actions";
import { updateUserReaction } from "./actions";
import {
ADD_MESSAGE,
CLEAR_MESSAGES,
Expand Down Expand Up @@ -53,6 +55,16 @@ const FloatingButton = styled(Button)`
}
`;

const LikeIconsWrapper=styled.div`
display:flex;
margin-top:10px;
align-items:center;
gap:15px;
margin-left:5px;
font-size:16px;
font-weight:bold;
;`

const ChatPanel = styled.div`
position: fixed;
bottom: 60px;
Expand Down Expand Up @@ -347,6 +359,28 @@ const Icon = styled.img`
object-fit: cover;
`;

const ReactionButton = styled.button`
background: none;
border: none;
cursor: pointer;
padding: 4px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
color: ${props => props.active ? '#1890ff' : '#666'};

&:hover {
background: #f0f0f0;
transform: scale(1.1);
}

&:active {
transform: scale(0.95);
}
`;

const ActiveIndicator = styled.div`
position: absolute;
bottom: 2px;
Expand Down Expand Up @@ -740,6 +774,13 @@ const ChatBotWidget = () => {
setShowSmartSuggestions(value.length === 0 && messages.length > 0);
};

const handleReaction = useCallback((messageId, reaction, event) => {
event?.preventDefault();
dispatch(updateUserReaction(messageId, reaction));
}, [dispatch]);



const handleAutoCompleteSelect = (value) => {
setInputValue(value);
setAutoCompleteData([]);
Expand Down Expand Up @@ -1042,7 +1083,33 @@ const ChatBotWidget = () => {
>
{message.text}
</ReactMarkdown>
<LikeIconsWrapper>
<ReactionButton
active={message.userReaction === "like"}
onClick={(e) => handleReaction(message.id, "like", e)}
title="Like this response"
>

<LikeOutlined style={{
color: message.userReaction === "like" ? '#1890ff' : '#666',
fontSize: '16px'
}} />
</ReactionButton>

<ReactionButton
active={message.userReaction === "dislike"}
onClick={() => handleReaction(message.id, "dislike")}
title="Dislike this response"
>
<DislikeOutlined style={{
color: message.userReaction === "dislike" ? '#ff4d4f' : '#666',
fontSize: '16px',
transform: "scaleX(-1)"
}} />
</ReactionButton>
</LikeIconsWrapper>
</MessageBubble>

<Timestamp sender="bot">
{formatTime(
message.timestamp
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/containers/ChatBot/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
SEND_MESSAGE_SUCCESS,
SEND_MESSAGE_ERROR,
CLEAR_MESSAGES,
UPDATE_USER_REACTION
} from "./constants";

export function sendMessage(message) {
Expand Down Expand Up @@ -55,3 +56,11 @@ export function clearMessages() {
type: CLEAR_MESSAGES,
};
}

export function updateUserReaction(messageId,reaction){

return {
type:UPDATE_USER_REACTION,
payload:{messageId,reaction}
}
}
1 change: 1 addition & 0 deletions frontend/src/containers/ChatBot/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export const SET_TYPING = "app/ChatBot/SET_TYPING";
export const SEND_MESSAGE_SUCCESS = "app/ChatBot/SEND_MESSAGE_SUCCESS";
export const SEND_MESSAGE_ERROR = "app/ChatBot/SEND_MESSAGE_ERROR";
export const CLEAR_MESSAGES = "app/ChatBot/CLEAR_MESSAGES";
export const UPDATE_USER_REACTION="app/ChatBot/UPDATE_USER_REACTION"
export const LOAD_MESSAGES_FROM_STORAGE =
"app/ChatBot/LOAD_MESSAGES_FROM_STORAGE";
11 changes: 11 additions & 0 deletions frontend/src/containers/ChatBot/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
SEND_MESSAGE_ERROR,
CLEAR_MESSAGES,
LOAD_MESSAGES_FROM_STORAGE,
UPDATE_USER_REACTION,
} from "./constants";

export const initialState = {
Expand Down Expand Up @@ -48,6 +49,16 @@ const chatBotReducer = (state = initialState, action) =>
draft.sessionId = null;
draft.error = null;
break;
case UPDATE_USER_REACTION: {
const { messageId, reaction } = action.payload;
const msg = draft.messages.find((m) => m.id === messageId);
if (msg) {
msg.userReaction = reaction;
}
break;
}


}
});

Expand Down
31 changes: 28 additions & 3 deletions frontend/src/containers/ChatBot/saga.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { takeLatest, put, call, select } from "redux-saga/effects";
import { SEND_MESSAGE, SEND_BOT_MESSAGE } from "./constants";
import { SEND_MESSAGE, SEND_BOT_MESSAGE,UPDATE_USER_REACTION } from "./constants";
import {
addMessage,
setTyping,
Expand Down Expand Up @@ -47,7 +47,7 @@ export function* handleSendMessage(action) {
if (response.success) {
// Add bot response to chat
const botMessage = {
id: Date.now() + 1,
id: response.data.message.id,
text:
response.data.message.content ||
"I received your message!",
Expand Down Expand Up @@ -132,15 +132,40 @@ export function* handleSendMessage(action) {

export function* handleSendBotMessage(action) {
const botMessage = {
id: Date.now(),
id: response.data.message.id,
text: action.payload,
sender: "bot",
timestamp: new Date().toISOString(),
};
yield put(addMessage(botMessage));
}


export function* handleUpdateUserReaction(action) {
const { messageId, reaction } = action.payload;

try {
const response = yield call(chatService.updateReaction, messageId, reaction);


if (response.success) {
yield put({
type: "UPDATE_USER_REACTION_SUCCESS",
payload: { messageId, reaction },
});
}

else {
console.error("Failed to update reaction:", response.error);
}
} catch (error) {
console.error("Error updating reaction:", error);
}
}


export default function* chatBotSaga() {
yield takeLatest(SEND_MESSAGE, handleSendMessage);
yield takeLatest(SEND_BOT_MESSAGE, handleSendBotMessage);
yield takeLatest(UPDATE_USER_REACTION, handleUpdateUserReaction);
}
40 changes: 27 additions & 13 deletions frontend/src/services/chatService.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,19 @@ export const chatService = {
) {
throw new Error("Message cannot be empty");
}

// Prepare the request data, excluding undefined values
const requestData = {
message: messageData.message.trim(),
provider: messageData.provider || "gemini",
useRag:
messageData.useRag !== undefined
? messageData.useRag
: true,
useRag: messageData.useRag !== undefined ? messageData.useRag : true,
topK: messageData.topK || 5,
};

};
// Only include sessionId if it exists
if (messageData.sessionId) {
requestData.sessionId = messageData.sessionId;
}

console.log("Sending request data:", requestData); // Debug log


const response = await api.post("/chat/message", requestData);

return {
success: true,
data: response.data,
Expand All @@ -57,7 +49,6 @@ export const chatService = {
};
}
},

/**
* Get chat history for a session
* @param {string} sessionId - The session ID
Expand All @@ -79,6 +70,29 @@ export const chatService = {
}
},


/**
* updated user reaction
* @param {string} messageId, - The message Id
*/

async updateReaction(messageId, reaction) {
try {
const res = await api.put(`/chat/userreaction/${messageId}`, { userReaction: reaction });
return {
success: true,
data: res.data,
};
} catch (error) {
console.error("Error updating reaction:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
},


/**
* Create a new chat session
* @returns {Promise<Object>} New session data
Expand Down