import { IconCheck, IconCopy, IconEdit, IconRobot, IconTrash, IconUser, } from '@tabler/icons-react'; import { FC, memo, useContext, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'next-i18next'; import { updateConversation } from '@/utils/app/conversation'; import { Message } from '@/types/chat'; import HomeContext from '@/pages/api/home/home.context'; import { CodeBlock } from '../Markdown/CodeBlock'; import { MemoizedReactMarkdown } from '../Markdown/MemoizedReactMarkdown'; import rehypeMathjax from 'rehype-mathjax'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; export interface Props { message: Message; messageIndex: number; onEdit?: (editedMessage: Message) => void } export const ChatMessage: FC = memo(({ message, messageIndex, onEdit }) => { const { t } = useTranslation('chat'); const { state: { selectedConversation, conversations, currentMessage, messageIsStreaming }, dispatch: homeDispatch, } = useContext(HomeContext); const [isEditing, setIsEditing] = useState(false); const [isTyping, setIsTyping] = useState(false); const [messageContent, setMessageContent] = useState(message.content); const [messagedCopied, setMessageCopied] = useState(false); const textareaRef = useRef(null); const toggleEditing = () => { setIsEditing(!isEditing); }; const handleInputChange = (event: React.ChangeEvent) => { setMessageContent(event.target.value); if (textareaRef.current) { textareaRef.current.style.height = 'inherit'; textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; } }; const handleEditMessage = () => { if (message.content != messageContent) { if (selectedConversation && onEdit) { onEdit({ ...message, content: messageContent }); } } setIsEditing(false); }; const handleDeleteMessage = () => { if (!selectedConversation) return; const { messages } = selectedConversation; const findIndex = messages.findIndex((elm) => elm === message); if (findIndex < 0) return; if ( findIndex < messages.length - 1 && messages[findIndex + 1].role === 'assistant' ) { messages.splice(findIndex, 2); } else { messages.splice(findIndex, 1); } const updatedConversation = { ...selectedConversation, messages, }; const { single, all } = updateConversation( updatedConversation, conversations, ); homeDispatch({ field: 'selectedConversation', value: single }); homeDispatch({ field: 'conversations', value: all }); }; const handlePressEnter = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !isTyping && !e.shiftKey) { e.preventDefault(); handleEditMessage(); } }; const copyOnClick = () => { if (!navigator.clipboard) return; navigator.clipboard.writeText(message.content).then(() => { setMessageCopied(true); setTimeout(() => { setMessageCopied(false); }, 2000); }); }; useEffect(() => { setMessageContent(message.content); }, [message.content]); useEffect(() => { if (textareaRef.current) { textareaRef.current.style.height = 'inherit'; textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; } }, [isEditing]); return (
{message.role === 'assistant' ? ( ) : ( )}
{message.role === 'user' ? (
{isEditing ? (