LibreChat/client/src/hooks/Messages/useMessageActions.tsx
Ruben Talstra 4cbab86b45
📈 feat: Chat rating for feedback (#5878)
* feat: working started for feedback implementation.

TODO:
- needs some refactoring.
- needs some UI animations.

* feat: working rate functionality

* feat: works now as well to reader the already rated responses from the server.

* feat: added the option to give feedback in text (optional)

* feat: added Dismiss option `x` to the `FeedbackTagOptions`

*  feat: Add rating and ratingContent fields to message schema

* 🔧 chore: Bump version to 0.0.3 in package.json

*  feat: Enhance feedback localization and update UI elements

* 🚀 feat: Implement feedback tagging system with thumbs up/down options

* 🚀 feat: Add data-provider package to unused i18n keys detection

* 🎨 style: update HoverButtons' style

* 🎨 style: Update HoverButtons and Fork components for improved styling and visibility

* 🔧 feat: Implement feedback system with rating and content options

* 🔧 feat: Enhance feedback handling with improved rating toggle and tag options

* 🔧 feat: Integrate toast notifications for feedback submission and clean up unused state

* 🔧 feat: Remove unused feedback tag options from translation file

*  refactor: clean up Feedback component and improve HoverButtons structure

*  refactor: remove unused settings switches for auto scroll, hide side panel, and user message markdown

* refactor: reorganize import order

*  refactor: enhance HoverButtons and Fork components with improved styles and animations

*  refactor: update feedback response phrases for improved user engagement

*  refactor: add CheckboxOption component and streamline fork options rendering

* Refactor feedback components and logic

- Consolidated feedback handling into a single Feedback component, removing FeedbackButtons and FeedbackTagOptions.
- Introduced new feedback tagging system with detailed tags for both thumbs up and thumbs down ratings.
- Updated feedback schema to include new tags and improved type definitions.
- Enhanced user interface for feedback collection, including a dialog for additional comments.
- Removed obsolete files and adjusted imports accordingly.
- Updated translations for new feedback tags and placeholders.

*  refactor: update feedback handling by replacing rating fields with feedback in message updates

* fix: add missing validateMessageReq middleware to feedback route and refactor feedback system

* 🗑️ chore: Remove redundant fork option explanations from translation file

* 🔧 refactor: Remove unused dependency from feedback callback

* 🔧 refactor: Simplify message update response structure and improve error logging

* Chore: removed unused tests.

---------

Co-authored-by: Marco Beretta <81851188+berry-13@users.noreply.github.com>
2025-05-30 12:16:34 -04:00

186 lines
5.2 KiB
TypeScript

import { useRecoilValue } from 'recoil';
import { useCallback, useMemo, useState } from 'react';
import { useUpdateFeedbackMutation } from 'librechat-data-provider/react-query';
import {
isAssistantsEndpoint,
isAgentsEndpoint,
TUpdateFeedbackRequest,
getTagByKey,
TFeedback,
toMinimalFeedback,
SearchResultData,
} from 'librechat-data-provider';
import type { TMessageProps } from '~/common';
import {
useChatContext,
useAddedChatContext,
useAssistantsMapContext,
useAgentsMapContext,
} from '~/Providers';
import useCopyToClipboard from './useCopyToClipboard';
import { useAuthContext } from '~/hooks/AuthContext';
import { useLocalize } from '~/hooks';
import store from '~/store';
export type TMessageActions = Pick<
TMessageProps,
'message' | 'currentEditId' | 'setCurrentEditId'
> & {
isMultiMessage?: boolean;
searchResults?: { [key: string]: SearchResultData };
};
export default function useMessageActions(props: TMessageActions) {
const localize = useLocalize();
const { user } = useAuthContext();
const UsernameDisplay = useRecoilValue<boolean>(store.UsernameDisplay);
const { message, currentEditId, setCurrentEditId, isMultiMessage, searchResults } = props;
const {
ask,
index,
regenerate,
latestMessage,
handleContinue,
setLatestMessage,
conversation: rootConvo,
isSubmitting: isSubmittingRoot,
} = useChatContext();
const { conversation: addedConvo, isSubmitting: isSubmittingAdditional } = useAddedChatContext();
const conversation = useMemo(
() => (isMultiMessage === true ? addedConvo : rootConvo),
[isMultiMessage, addedConvo, rootConvo],
);
const agentsMap = useAgentsMapContext();
const assistantMap = useAssistantsMapContext();
const { text, content, messageId = null, isCreatedByUser } = message ?? {};
const edit = useMemo(() => messageId === currentEditId, [messageId, currentEditId]);
const [feedback, setFeedback] = useState<TFeedback | undefined>(() => {
if (message?.feedback) {
const tag = getTagByKey(message.feedback?.tag?.key);
return {
rating: message.feedback.rating,
tag,
text: message.feedback.text,
};
}
return undefined;
});
const enterEdit = useCallback(
(cancel?: boolean) => setCurrentEditId && setCurrentEditId(cancel === true ? -1 : messageId),
[messageId, setCurrentEditId],
);
const assistant = useMemo(() => {
if (!isAssistantsEndpoint(conversation?.endpoint)) {
return undefined;
}
const endpointKey = conversation?.endpoint ?? '';
const modelKey = message?.model ?? '';
return assistantMap?.[endpointKey] ? assistantMap[endpointKey][modelKey] : undefined;
}, [conversation?.endpoint, message?.model, assistantMap]);
const agent = useMemo(() => {
if (!isAgentsEndpoint(conversation?.endpoint)) {
return undefined;
}
if (!agentsMap) {
return undefined;
}
const modelKey = message?.model ?? '';
if (modelKey) {
return agentsMap[modelKey];
}
const agentId = conversation?.agent_id ?? '';
if (agentId) {
return agentsMap[agentId];
}
}, [agentsMap, conversation?.agent_id, conversation?.endpoint, message?.model]);
const isSubmitting = useMemo(
() => (isMultiMessage === true ? isSubmittingAdditional : isSubmittingRoot),
[isMultiMessage, isSubmittingAdditional, isSubmittingRoot],
);
const regenerateMessage = useCallback(() => {
if ((isSubmitting && isCreatedByUser === true) || !message) {
return;
}
regenerate(message);
}, [isSubmitting, isCreatedByUser, message, regenerate]);
const copyToClipboard = useCopyToClipboard({ text, content, searchResults });
const messageLabel = useMemo(() => {
if (message?.isCreatedByUser === true) {
return UsernameDisplay ? (user?.name ?? '') || user?.username : localize('com_user_message');
} else if (agent) {
return agent.name ?? 'Assistant';
} else if (assistant) {
return assistant.name ?? 'Assistant';
} else {
return message?.sender;
}
}, [message, agent, assistant, UsernameDisplay, user, localize]);
const feedbackMutation = useUpdateFeedbackMutation(
conversation?.conversationId || '',
message?.messageId || '',
);
const handleFeedback = useCallback(
({ feedback: newFeedback }: { feedback: TFeedback | undefined }) => {
const payload: TUpdateFeedbackRequest = {
feedback: newFeedback ? toMinimalFeedback(newFeedback) : undefined,
};
feedbackMutation.mutate(payload, {
onSuccess: (data) => {
if (!data.feedback) {
setFeedback(undefined);
} else {
const tag = getTagByKey(data.feedback?.tag ?? undefined);
setFeedback({
rating: data.feedback.rating,
tag,
text: data.feedback.text,
});
}
},
onError: (error) => {
console.error('Failed to update feedback:', error);
},
});
},
[feedbackMutation],
);
return {
ask,
edit,
index,
agent,
assistant,
enterEdit,
conversation,
messageLabel,
isSubmitting,
latestMessage,
handleContinue,
copyToClipboard,
setLatestMessage,
regenerateMessage,
handleFeedback,
feedback,
};
}