import React, { useState, useEffect, useRef } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import copy from 'copy-to-clipboard'; 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 '~/data-provider'; import { cn } from '~/utils/'; import store from '~/store'; function isJson(str) { try { JSON.parse(str); } catch (e) { return false; } return true; } export default function Message({ conversation, message, scrollToBottom, currentEditId, setCurrentEditId, siblingIdx, siblingCount, setSiblingIdx }) { const { text, searchResult, isCreatedByUser, error, submitting, unfinished, cancelled } = 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 getError = (text) => { const match = text.match(/\{[^{}]*\}/); var json = match ? match[0] : ''; if (isJson(json)) { json = JSON.parse(json); if (json.code === 'invalid_api_key') { return 'Invalid API key. Please check your API key and try again. You can access your API key by clicking on the model logo in the top-left corner of the textbox.'; } else if (json.type === 'insufficient_quota') { return "We're sorry, but the default API key has reached its limit. To continue using this service, please set up your own API key. You can do this by clicking on the model logo in the top-left corner of the textbox."; } else { return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${text}`; } } else { return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${text}`; } }; 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 }); 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-[#444654]'; 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 ( <>