/* eslint-disable react-hooks/exhaustive-deps */ import { useState, useEffect, useRef } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import copy from 'copy-to-clipboard'; import Plugin from './Plugin'; import SubRow from './Content/SubRow'; import Content from './Content/Content'; import MultiMessage from './MultiMessage'; import HoverButtons from './HoverButtons'; import SiblingSwitch from './SiblingSwitch'; import getIcon from '~/utils/getIcon'; import { useMessageHandler } from '~/utils/handleSubmit'; import { useGetConversationByIdQuery } from '@librechat/data-provider'; import { cn } from '~/utils/'; import store from '~/store'; import getError from '~/utils/getError'; export default function Message({ conversation, message, scrollToBottom, currentEditId, setCurrentEditId, siblingIdx, siblingCount, setSiblingIdx, }) { const { text, searchResult, isCreatedByUser, error, submitting, unfinished } = message; const isSubmitting = useRecoilValue(store.isSubmitting); const setLatestMessage = useSetRecoilState(store.latestMessage); const [abortScroll, setAbort] = useState(false); const textEditor = useRef(null); const last = !message?.children?.length; const edit = message.messageId == currentEditId; const { ask, regenerate } = useMessageHandler(); const { switchToConversation } = store.useConversation(); const blinker = submitting && isSubmitting; const getConversationQuery = useGetConversationByIdQuery(message.conversationId, { enabled: false, }); // debugging // useEffect(() => { // console.log('isSubmitting:', isSubmitting); // console.log('unfinished:', unfinished); // }, [isSubmitting, unfinished]); useEffect(() => { if (blinker && !abortScroll) { scrollToBottom(); } }, [isSubmitting, blinker, text, scrollToBottom]); useEffect(() => { if (last) { setLatestMessage({ ...message }); } }, [last, message]); const enterEdit = (cancel) => setCurrentEditId(cancel ? -1 : message.messageId); const handleWheel = () => { if (blinker) { setAbort(true); } else { setAbort(false); } }; const props = { className: 'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 bg-white dark:text-gray-100 group dark:bg-gray-800', }; const icon = getIcon({ ...conversation, ...message, model: message?.model || conversation?.model, }); if (!isCreatedByUser) { props.className = 'w-full border-b border-black/10 bg-gray-50 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-gray-1000'; } if (message.bg && searchResult) { props.className = message.bg.split('hover')[0]; props.titleclass = message.bg.split(props.className)[1] + ' cursor-pointer'; } const resubmitMessage = () => { const text = textEditor.current.innerText; ask({ text, parentMessageId: message?.parentMessageId, conversationId: message?.conversationId, }); setSiblingIdx(siblingCount - 1); enterEdit(true); }; const regenerateMessage = () => { if (!isSubmitting && !message?.isCreatedByUser) { regenerate(message); } }; const copyToClipboard = (setIsCopied) => { setIsCopied(true); copy(message?.text); setTimeout(() => { setIsCopied(false); }, 3000); }; const clickSearchResult = async () => { if (!searchResult) { return; } getConversationQuery.refetch(message.conversationId).then((response) => { switchToConversation(response.data); }); }; return ( <>
{typeof icon === 'string' && icon.match(/[^\\x00-\\x7F]+/) ? ( {icon} ) : ( icon )}
{searchResult && ( {`${message.title} | ${message.sender}`} )}
{message.plugin && } {error ? (
{getError(text)}
) : edit ? (
{/*
*/}
{text}
) : ( <>
{/*
*/}
{!isCreatedByUser ? ( <> ) : ( <>{text} )}
{/* {!isSubmitting && cancelled ? (
{`This is a cancelled message.`}
) : null} */} {!isSubmitting && unfinished ? (
{ 'This is an unfinished message. The AI may still be generating a response, it was aborted, or a censor was triggered. Refresh or visit later to see more updates.' }
) : null} )}
enterEdit()} regenerate={() => regenerateMessage()} copyToClipboard={copyToClipboard} />
); }