diff --git a/backend/src/models/index.js b/backend/src/models/index.js index 699fb1d..4213727 100644 --- a/backend/src/models/index.js +++ b/backend/src/models/index.js @@ -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, diff --git a/backend/src/routes/chat.js b/backend/src/routes/chat.js index 7af5a09..8f73452 100644 --- a/backend/src/routes/chat.js +++ b/backend/src/routes/chat.js @@ -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 diff --git a/frontend/src/containers/ChatBot/ChatBotWidget.jsx b/frontend/src/containers/ChatBot/ChatBotWidget.jsx index a8ea556..95f56be 100644 --- a/frontend/src/containers/ChatBot/ChatBotWidget.jsx +++ b/frontend/src/containers/ChatBot/ChatBotWidget.jsx @@ -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, @@ -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; @@ -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; @@ -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([]); @@ -1042,7 +1083,33 @@ const ChatBotWidget = () => { > {message.text} + + handleReaction(message.id, "like", e)} + title="Like this response" +> + + + + + handleReaction(message.id, "dislike")} + title="Dislike this response" + > + + + + {formatTime( message.timestamp diff --git a/frontend/src/containers/ChatBot/actions.js b/frontend/src/containers/ChatBot/actions.js index 1f597af..556aaff 100644 --- a/frontend/src/containers/ChatBot/actions.js +++ b/frontend/src/containers/ChatBot/actions.js @@ -6,6 +6,7 @@ import { SEND_MESSAGE_SUCCESS, SEND_MESSAGE_ERROR, CLEAR_MESSAGES, + UPDATE_USER_REACTION } from "./constants"; export function sendMessage(message) { @@ -55,3 +56,11 @@ export function clearMessages() { type: CLEAR_MESSAGES, }; } + +export function updateUserReaction(messageId,reaction){ + + return { + type:UPDATE_USER_REACTION, + payload:{messageId,reaction} + } +} diff --git a/frontend/src/containers/ChatBot/constants.js b/frontend/src/containers/ChatBot/constants.js index 452cff3..e0a6fc9 100644 --- a/frontend/src/containers/ChatBot/constants.js +++ b/frontend/src/containers/ChatBot/constants.js @@ -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"; diff --git a/frontend/src/containers/ChatBot/reducer.js b/frontend/src/containers/ChatBot/reducer.js index 0881fa6..d29b800 100644 --- a/frontend/src/containers/ChatBot/reducer.js +++ b/frontend/src/containers/ChatBot/reducer.js @@ -6,6 +6,7 @@ import { SEND_MESSAGE_ERROR, CLEAR_MESSAGES, LOAD_MESSAGES_FROM_STORAGE, + UPDATE_USER_REACTION, } from "./constants"; export const initialState = { @@ -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; + } + + } }); diff --git a/frontend/src/containers/ChatBot/saga.js b/frontend/src/containers/ChatBot/saga.js index 84306ce..11c2956 100644 --- a/frontend/src/containers/ChatBot/saga.js +++ b/frontend/src/containers/ChatBot/saga.js @@ -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, @@ -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!", @@ -132,7 +132,7 @@ 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(), @@ -140,7 +140,32 @@ export function* handleSendBotMessage(action) { 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); } diff --git a/frontend/src/services/chatService.js b/frontend/src/services/chatService.js index 91261d8..08c626f 100644 --- a/frontend/src/services/chatService.js +++ b/frontend/src/services/chatService.js @@ -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, @@ -57,7 +49,6 @@ export const chatService = { }; } }, - /** * Get chat history for a session * @param {string} sessionId - The session ID @@ -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} New session data