2023-04-11 03:26:38 +08:00
import React , { useState , useEffect , useRef } from 'react' ;
2023-04-06 13:53:04 -07:00
import { useRecoilValue , useSetRecoilState } from 'recoil' ;
2023-04-01 02:12:15 +08:00
import copy from 'copy-to-clipboard' ;
2023-03-22 16:06:11 -04:00
import SubRow from './Content/SubRow' ;
2023-03-26 23:02:57 +09:00
import Content from './Content/Content' ;
2023-03-15 10:42:45 -04:00
import MultiMessage from './MultiMessage' ;
2023-03-03 08:51:33 -05:00
import HoverButtons from './HoverButtons' ;
2023-03-13 21:44:30 +08:00
import SiblingSwitch from './SiblingSwitch' ;
2023-03-31 01:41:41 +08:00
import getIcon from '~/utils/getIcon' ;
2023-03-18 23:18:36 -04:00
import { useMessageHandler } from '~/utils/handleSubmit' ;
2023-04-07 06:02:28 -07:00
import { useGetConversationByIdQuery } from '~/data-provider' ;
2023-04-06 18:58:56 -04:00
import { cn } from '~/utils/' ;
2023-03-28 22:39:27 +08:00
import store from '~/store' ;
2023-05-18 04:51:30 +05:30
function isJson ( str ) {
try {
JSON . parse ( str ) ;
} catch ( e ) {
return false ;
}
return true ;
}
2023-02-13 20:13:59 -05:00
export default function Message ( {
2023-03-28 22:39:27 +08:00
conversation ,
2023-03-13 05:26:17 +08:00
message ,
scrollToBottom ,
2023-03-13 21:44:30 +08:00
currentEditId ,
setCurrentEditId ,
siblingIdx ,
siblingCount ,
setSiblingIdx
2023-02-13 20:13:59 -05:00
} ) {
2023-04-11 03:26:38 +08:00
const { text , searchResult , isCreatedByUser , error , submitting , unfinished , cancelled } = message ;
2023-03-28 22:39:27 +08:00
const isSubmitting = useRecoilValue ( store . isSubmitting ) ;
const setLatestMessage = useSetRecoilState ( store . latestMessage ) ;
2023-02-21 21:31:36 -05:00
const [ abortScroll , setAbort ] = useState ( false ) ;
2023-03-15 16:38:01 -04:00
const textEditor = useRef ( null ) ;
const last = ! message ? . children ? . length ;
2023-03-13 21:44:30 +08:00
const edit = message . messageId == currentEditId ;
2023-04-01 02:12:15 +08:00
const { ask , regenerate } = useMessageHandler ( ) ;
2023-03-28 22:39:27 +08:00
const { switchToConversation } = store . useConversation ( ) ;
2023-03-17 12:34:54 -04:00
const blinker = submitting && isSubmitting ;
2023-05-18 11:09:31 -07:00
const getConversationQuery = useGetConversationByIdQuery ( message . conversationId , {
enabled : false
} ) ;
2023-02-13 20:13:59 -05:00
2023-04-10 18:27:06 -04:00
// debugging
// useEffect(() => {
// console.log('isSubmitting:', isSubmitting);
// console.log('unfinished:', unfinished);
// }, [isSubmitting, unfinished]);
2023-02-13 20:13:59 -05:00
useEffect ( ( ) => {
2023-02-21 21:31:36 -05:00
if ( blinker && ! abortScroll ) {
2023-02-13 20:13:59 -05:00
scrollToBottom ( ) ;
}
2023-03-17 12:34:54 -04:00
} , [ isSubmitting , blinker , text , scrollToBottom ] ) ;
2023-03-13 21:44:30 +08:00
useEffect ( ( ) => {
2023-03-17 01:49:09 +08:00
if ( last ) {
2023-03-28 22:39:27 +08:00
setLatestMessage ( { ... message } ) ;
2023-03-17 01:49:09 +08:00
}
2023-03-17 03:13:42 +08:00
} , [ last , message ] ) ;
2023-02-21 21:31:36 -05:00
2023-05-18 11:44:07 -07:00
const enterEdit = ( cancel ) => setCurrentEditId ( cancel ? - 1 : message . messageId ) ;
2023-03-13 21:44:30 +08:00
2023-02-21 21:31:36 -05:00
const handleWheel = ( ) => {
if ( blinker ) {
setAbort ( true ) ;
} else {
setAbort ( false ) ;
}
} ;
2023-02-13 20:13:59 -05:00
2023-05-18 11:09:31 -07:00
const getError = ( text ) => {
2023-05-18 04:51:30 +05:30
const match = text . match ( /\{[^{}]*\}/ ) ;
2023-05-18 11:09:31 -07:00
var json = match ? match [ 0 ] : '' ;
2023-05-18 04:51:30 +05:30
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 } ` ;
}
} ;
2023-02-06 18:25:11 -05:00
const props = {
className :
2023-03-04 17:39:06 -05:00
'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'
2023-02-06 18:25:11 -05:00
} ;
2023-03-15 16:38:01 -04:00
2023-03-31 01:41:41 +08:00
const icon = getIcon ( {
2023-03-31 00:20:45 +08:00
... conversation ,
... message
2023-03-15 16:38:01 -04:00
} ) ;
2023-03-15 14:21:08 +08:00
if ( ! isCreatedByUser )
2023-02-23 23:56:55 -05:00
props . className =
2023-03-06 14:04:06 -05:00
'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]' ;
2023-02-15 12:29:56 -05:00
2023-03-18 23:18:36 -04:00
if ( message . bg && searchResult ) {
2023-03-22 16:06:11 -04:00
props . className = message . bg . split ( 'hover' ) [ 0 ] ;
2023-03-28 23:00:29 +08:00
props . titleclass = message . bg . split ( props . className ) [ 1 ] + ' cursor-pointer' ;
2023-03-18 23:18:36 -04:00
}
2023-03-13 05:26:17 +08:00
const resubmitMessage = ( ) => {
2023-03-15 16:38:01 -04:00
const text = textEditor . current . innerText ;
2023-03-13 05:26:17 +08:00
2023-03-17 12:34:54 -04:00
ask ( {
text ,
parentMessageId : message ? . parentMessageId ,
conversationId : message ? . conversationId
} ) ;
2023-03-13 05:26:17 +08:00
2023-03-15 16:38:01 -04:00
setSiblingIdx ( siblingCount - 1 ) ;
2023-03-13 05:26:17 +08:00
enterEdit ( true ) ;
} ;
2023-04-01 02:12:15 +08:00
const regenerateMessage = ( ) => {
if ( ! isSubmitting && ! message ? . isCreatedByUser ) regenerate ( message ) ;
} ;
2023-05-18 11:09:31 -07:00
const copyToClipboard = ( setIsCopied ) => {
2023-05-14 18:30:20 +05:30
setIsCopied ( true ) ;
2023-04-01 02:12:15 +08:00
copy ( message ? . text ) ;
2023-05-14 18:30:20 +05:30
setTimeout ( ( ) => {
setIsCopied ( false ) ;
} , 3000 ) ;
2023-04-01 02:12:15 +08:00
} ;
2023-03-18 23:18:36 -04:00
const clickSearchResult = async ( ) => {
if ( ! searchResult ) return ;
2023-05-18 11:09:31 -07:00
getConversationQuery . refetch ( message . conversationId ) . then ( ( response ) => {
2023-04-07 06:02:28 -07:00
switchToConversation ( response . data ) ;
} ) ;
2023-03-19 11:25:12 -04:00
} ;
2023-03-18 23:18:36 -04:00
2023-02-05 19:41:24 -05:00
return (
2023-03-13 21:44:30 +08:00
< >
2023-05-18 11:09:31 -07:00
< div { ...props } onWheel = { handleWheel } >
2023-03-13 21:44:30 +08:00
< div className = "relative m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl" >
2023-03-15 10:42:45 -04:00
< div className = "relative flex h-[30px] w-[30px] flex-col items-end text-right text-xs md:text-sm" >
2023-03-22 16:06:11 -04:00
{ typeof icon === 'string' && icon . match ( /[^\\x00-\\x7F]+/ ) ? (
2023-03-13 21:44:30 +08:00
< span className = " direction-rtl w-40 overflow-x-scroll" > { icon } < / span >
) : (
icon
) }
2023-03-15 19:05:17 -04:00
< div className = "sibling-switch invisible absolute left-0 top-2 -ml-4 flex -translate-x-full items-center justify-center gap-1 text-xs group-hover:visible" >
< SiblingSwitch
siblingIdx = { siblingIdx }
siblingCount = { siblingCount }
setSiblingIdx = { setSiblingIdx }
/ >
< / div >
2023-03-13 21:44:30 +08:00
< / div >
2023-04-06 13:53:19 -04:00
< div className = "relative flex w-[calc(100%-50px)] flex-col gap-1 md:gap-3 lg:w-[calc(100%-115px)]" >
2023-03-22 16:06:11 -04:00
{ searchResult && (
< SubRow
2023-03-28 23:00:29 +08:00
classes = { props . titleclass + ' rounded' }
2023-03-22 16:06:11 -04:00
subclasses = "switch-result pl-2 pb-2"
onClick = { clickSearchResult }
2023-03-22 18:26:29 -04:00
>
< strong > { ` ${ message . title } | ${ message . sender } ` } < / strong >
< / SubRow >
2023-03-22 16:06:11 -04:00
) }
2023-03-13 21:44:30 +08:00
< div className = "flex flex-grow flex-col gap-3" >
{ error ? (
2023-04-06 18:58:56 -04:00
< div className = "flex flex min-h-[20px] flex-grow flex-col items-start gap-2 gap-4 text-red-500" >
< div className = "rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-100" >
2023-05-18 04:51:30 +05:30
{ getError ( text ) }
2023-03-13 05:26:17 +08:00
< / div >
2023-02-22 22:42:22 -05:00
< / div >
2023-03-15 16:38:01 -04:00
) : edit ? (
2023-04-06 13:53:19 -04:00
< div className = "flex min-h-[20px] flex-grow flex-col items-start gap-4 " >
2023-03-15 16:38:01 -04:00
{ /* <div className={`${blinker ? 'result-streaming' : ''} markdown prose dark:prose-invert light w-full break-words`}> */ }
< div
2023-04-21 23:12:45 -04:00
className = "markdown prose dark:prose-invert light w-full whitespace-pre-wrap break-words border-none focus:outline-none"
2023-03-15 16:38:01 -04:00
contentEditable = { true }
ref = { textEditor }
suppressContentEditableWarning = { true }
>
{ text }
< / div >
< div className = "mt-2 flex w-full justify-center text-center" >
< button
className = "btn btn-primary relative mr-2"
disabled = { isSubmitting }
onClick = { resubmitMessage }
>
Save & Submit
< / button >
2023-05-18 11:09:31 -07:00
< button className = "btn btn-neutral relative" onClick = { ( ) => enterEdit ( true ) } >
2023-03-15 16:38:01 -04:00
Cancel
< / button >
2023-03-13 05:26:17 +08:00
< / div >
2023-03-15 16:38:01 -04:00
< / div >
) : (
2023-04-11 03:26:38 +08:00
< >
< div
className = { cn (
'flex min-h-[20px] flex-grow flex-col items-start gap-4 ' ,
isCreatedByUser ? 'whitespace-pre-wrap' : ''
2023-03-28 22:39:27 +08:00
) }
2023-04-11 03:26:38 +08:00
>
{ /* <div className={`${blinker ? 'result-streaming' : ''} markdown prose dark:prose-invert light w-full break-words`}> */ }
< div className = "markdown prose dark:prose-invert light w-full break-words" >
{ ! isCreatedByUser ? (
< >
< Content content = { text } / >
< / >
) : (
< > { text } < / >
) }
< / div >
2023-03-13 21:44:30 +08:00
< / div >
2023-04-10 17:15:28 -04:00
{ / * { ! i s S u b m i t t i n g & & c a n c e l l e d ? (
2023-04-11 03:26:38 +08:00
< div className = "flex flex min-h-[20px] flex-grow flex-col items-start gap-2 gap-4 text-red-500" >
< div className = "rounded-md border border-blue-400 bg-blue-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-100" >
{ ` This is a cancelled message. ` }
< / div >
< / div >
2023-04-10 17:15:28 -04:00
) : null } * / }
2023-04-11 03:42:05 +08:00
{ ! isSubmitting && unfinished ? (
2023-04-11 03:26:38 +08:00
< div className = "flex flex min-h-[20px] flex-grow flex-col items-start gap-2 gap-4 text-red-500" >
< div className = "rounded-md border border-blue-400 bg-blue-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-100" >
2023-04-10 17:15:28 -04:00
{ ` This is an unfinished message. The AI may still be generating a response or it was aborted. Refresh or visit later to see more updates. ` }
2023-04-11 03:26:38 +08:00
< / div >
< / div >
) : null }
< / >
2023-03-15 16:38:01 -04:00
) }
2023-03-13 21:44:30 +08:00
< / div >
2023-03-15 16:38:01 -04:00
< HoverButtons
2023-04-01 02:12:15 +08:00
isEditting = { edit }
isSubmitting = { isSubmitting }
message = { message }
conversation = { conversation }
enterEdit = { ( ) => enterEdit ( ) }
regenerate = { ( ) => regenerateMessage ( ) }
2023-05-14 18:30:20 +05:30
copyToClipboard = { copyToClipboard }
2023-03-15 16:38:01 -04:00
/ >
2023-03-22 16:06:11 -04:00
< SubRow subclasses = "switch-container" >
< SiblingSwitch
siblingIdx = { siblingIdx }
siblingCount = { siblingCount }
setSiblingIdx = { setSiblingIdx }
/ >
< / SubRow >
2023-02-08 00:02:29 -05:00
< / div >
2023-02-06 18:25:11 -05:00
< / div >
2023-02-05 19:41:24 -05:00
< / div >
2023-03-19 11:25:12 -04:00
< MultiMessage
2023-04-06 02:06:39 +08:00
messageId = { message . messageId }
2023-03-28 22:39:27 +08:00
conversation = { conversation }
messagesTree = { message . children }
2023-03-19 11:25:12 -04:00
scrollToBottom = { scrollToBottom }
currentEditId = { currentEditId }
setCurrentEditId = { setCurrentEditId }
/ >
2023-03-13 21:44:30 +08:00
< / >
2023-02-05 19:41:24 -05:00
) ;
}