2023-03-14 01:12:11 +08:00
import React , { useEffect , useRef , useState } from 'react' ;
2023-03-11 21:42:08 -05:00
import { SSE } from '~/utils/sse' ;
2023-02-06 21:17:46 -05:00
import SubmitButton from './SubmitButton' ;
2023-02-08 15:26:42 -05:00
import Regenerate from './Regenerate' ;
2023-03-04 20:48:59 -05:00
import ModelMenu from '../Models/ModelMenu' ;
2023-02-11 12:54:02 -05:00
import Footer from './Footer' ;
2023-02-06 13:27:28 -05:00
import TextareaAutosize from 'react-textarea-autosize' ;
2023-03-14 21:25:02 -04:00
import createPayload from '~/utils/createPayload' ;
import resetConvo from '~/utils/resetConvo' ;
2023-03-17 01:49:09 +08:00
import RegenerateIcon from '../svg/RegenerateIcon' ;
import StopGeneratingIcon from '../svg/StopGeneratingIcon' ;
2023-02-07 00:05:00 -05:00
import { useSelector , useDispatch } from 'react-redux' ;
2023-03-17 12:34:54 -04:00
import {
setConversation ,
setNewConvo ,
setError ,
refreshConversation
} from '~/store/convoSlice' ;
2023-02-07 10:26:19 -05:00
import { setMessages } from '~/store/messageSlice' ;
2023-03-11 21:42:08 -05:00
import { setSubmitState , setSubmission } from '~/store/submitSlice' ;
2023-02-08 09:59:01 -05:00
import { setText } from '~/store/textSlice' ;
2023-03-17 12:34:54 -04:00
import { useMessageHandler } from '../../utils/handleSubmit' ;
2023-02-05 15:29:35 -05:00
2023-02-13 13:32:54 -05:00
export default function TextChat ( { messages } ) {
2023-02-11 11:37:20 -05:00
const [ errorMessage , setErrorMessage ] = useState ( '' ) ;
2023-03-17 12:34:54 -04:00
const inputRef = useRef ( null ) ;
2023-03-13 22:53:29 +08:00
const isComposing = useRef ( false ) ;
2023-02-07 00:05:00 -05:00
const dispatch = useDispatch ( ) ;
2023-03-14 01:24:43 +08:00
const { user } = useSelector ( ( state ) => state . user ) ;
2023-02-07 09:41:54 -05:00
const convo = useSelector ( ( state ) => state . convo ) ;
2023-03-04 20:48:59 -05:00
const { initial } = useSelector ( ( state ) => state . models ) ;
2023-03-11 21:42:08 -05:00
const { isSubmitting , stopStream , submission , disabled , model , chatGptLabel , promptPrefix } =
useSelector ( ( state ) => state . submit ) ;
2023-02-08 09:59:01 -05:00
const { text } = useSelector ( ( state ) => state . text ) ;
2023-03-17 01:49:09 +08:00
const { error , latestMessage } = convo ;
2023-03-17 03:13:42 +08:00
const { ask , regenerate , stopGenerating } = useMessageHandler ( ) ;
2023-03-18 00:52:16 +08:00
const isNotAppendable = ( ! isSubmitting && latestMessage ? . cancelled ) || latestMessage ? . error ;
2023-02-07 00:05:00 -05:00
2023-03-14 01:12:11 +08:00
// auto focus to input, when enter a conversation.
useEffect ( ( ) => {
inputRef . current ? . focus ( ) ;
2023-03-17 12:34:54 -04:00
} , [ convo ? . conversationId ] ) ;
2023-03-14 01:12:11 +08:00
2023-03-13 13:11:53 +08:00
const messageHandler = ( data , currentState , currentMsg ) => {
2023-03-17 12:34:54 -04:00
2023-03-17 01:49:09 +08:00
const { messages , _currentMsg , message , sender , isRegenerate } = currentState ;
2023-03-13 13:11:53 +08:00
2023-03-17 01:49:09 +08:00
if ( isRegenerate )
2023-03-17 12:34:54 -04:00
dispatch (
setMessages ( [
... messages ,
{
sender ,
text : data ,
parentMessageId : message ? . overrideParentMessageId ,
messageId : message ? . overrideParentMessageId + '_' ,
submitting : true
}
] )
) ;
2023-03-17 01:49:09 +08:00
else
2023-03-17 12:34:54 -04:00
dispatch (
setMessages ( [
... messages ,
currentMsg ,
{
sender ,
text : data ,
parentMessageId : currentMsg ? . messageId ,
messageId : currentMsg ? . messageId + '_' ,
submitting : true
}
] )
) ;
2023-03-11 21:42:08 -05:00
} ;
2023-02-08 15:26:42 -05:00
2023-03-18 00:52:16 +08:00
const cancelHandler = ( data , currentState , currentMsg ) => {
const { messages , _currentMsg , message , sender , isRegenerate } = currentState ;
if ( isRegenerate )
dispatch ( setMessages ( [ ... messages , { sender , text : data , parentMessageId : message ? . overrideParentMessageId , messageId : message ? . overrideParentMessageId + '_' , cancelled : true } ] ) ) ;
else
dispatch ( setMessages ( [ ... messages , currentMsg , { sender , text : data , parentMessageId : currentMsg ? . messageId , messageId : currentMsg ? . messageId + '_' , cancelled : true } ] ) ) ;
} ;
2023-03-13 13:11:53 +08:00
const createdHandler = ( data , currentState , currentMsg ) => {
const { conversationId } = currentMsg ;
dispatch (
setConversation ( {
conversationId ,
2023-03-17 01:49:09 +08:00
latestMessage : null
2023-03-13 13:11:53 +08:00
} )
) ;
} ;
const convoHandler = ( data , currentState , currentMsg ) => {
2023-03-13 12:35:55 +08:00
const { requestMessage , responseMessage } = data ;
2023-03-15 00:54:50 +08:00
const { conversationId } = requestMessage ;
2023-03-17 01:49:09 +08:00
const { messages , _currentMsg , message , isCustomModel , sender , isRegenerate } =
2023-03-11 21:42:08 -05:00
currentState ;
2023-03-13 05:26:17 +08:00
const { model , chatGptLabel , promptPrefix } = message ;
2023-03-18 00:52:16 +08:00
<< << << < HEAD
2023-03-17 12:34:54 -04:00
if ( isRegenerate ) dispatch ( setMessages ( [ ... messages , responseMessage ] ) ) ;
else dispatch ( setMessages ( [ ... messages , requestMessage , responseMessage ] ) ) ;
2023-03-18 00:52:16 +08:00
=== === =
if ( isRegenerate )
dispatch (
setMessages ( [ ... messages , responseMessage , ] )
) ;
else
dispatch (
setMessages ( [ ... messages , requestMessage , responseMessage , ] )
) ;
dispatch ( setSubmitState ( false ) ) ;
>>> >>> > 92 d0d11 ( feat : save cancelled flag in message )
2023-03-11 21:42:08 -05:00
const isBing = model === 'bingai' || model === 'sydney' ;
2023-03-18 00:52:16 +08:00
// refresh title
2023-03-13 14:04:47 +08:00
if ( requestMessage . parentMessageId == '00000000-0000-0000-0000-000000000000' ) {
2023-03-15 00:53:15 +08:00
setTimeout ( ( ) => {
dispatch ( refreshConversation ( ) ) ;
} , 2000 ) ;
2023-03-13 14:04:47 +08:00
2023-03-15 00:53:15 +08:00
// in case it takes too long.
setTimeout ( ( ) => {
2023-03-15 00:54:50 +08:00
dispatch ( refreshConversation ( ) ) ;
2023-03-15 00:53:15 +08:00
} , 5000 ) ;
2023-03-13 14:04:47 +08:00
}
2023-03-13 12:35:55 +08:00
2023-03-11 21:42:08 -05:00
if ( ! isBing && convo . conversationId === null && convo . parentMessageId === null ) {
2023-03-13 12:35:55 +08:00
const { title } = data ;
const { conversationId , messageId } = responseMessage ;
2023-02-25 09:06:26 -05:00
dispatch (
2023-03-11 21:42:08 -05:00
setConversation ( {
title ,
conversationId ,
2023-03-13 12:35:55 +08:00
parentMessageId : messageId ,
2023-03-11 21:42:08 -05:00
jailbreakConversationId : null ,
conversationSignature : null ,
clientId : null ,
invocationId : null ,
chatGptLabel : model === isCustomModel ? chatGptLabel : null ,
2023-03-17 01:49:09 +08:00
promptPrefix : model === isCustomModel ? promptPrefix : null ,
latestMessage : null
2023-03-11 21:42:08 -05:00
} )
2023-02-25 09:06:26 -05:00
) ;
2023-03-17 12:34:54 -04:00
} else if ( model === 'bingai' ) {
2023-03-11 21:42:08 -05:00
console . log ( 'Bing data:' , data ) ;
2023-03-13 12:35:55 +08:00
const { title } = data ;
2023-03-17 12:34:54 -04:00
const { conversationSignature , clientId , conversationId , invocationId } =
responseMessage ;
2023-03-11 21:42:08 -05:00
dispatch (
setConversation ( {
2023-03-08 22:30:29 -05:00
title ,
2023-03-11 21:42:08 -05:00
parentMessageId : null ,
2023-03-08 22:30:29 -05:00
conversationSignature ,
clientId ,
conversationId ,
2023-03-17 01:49:09 +08:00
invocationId ,
latestMessage : null
2023-03-11 21:42:08 -05:00
} )
) ;
} else if ( model === 'sydney' ) {
2023-03-13 12:35:55 +08:00
const { title } = data ;
2023-03-11 21:42:08 -05:00
const {
jailbreakConversationId ,
parentMessageId ,
conversationSignature ,
clientId ,
conversationId ,
invocationId
2023-03-13 12:35:55 +08:00
} = responseMessage ;
2023-03-11 21:42:08 -05:00
dispatch (
setConversation ( {
2023-03-08 19:47:23 -05:00
title ,
jailbreakConversationId ,
2023-03-08 21:06:58 -05:00
parentMessageId ,
2023-03-08 19:47:23 -05:00
conversationSignature ,
clientId ,
conversationId ,
2023-03-17 01:49:09 +08:00
invocationId ,
latestMessage : null
2023-03-11 21:42:08 -05:00
} )
) ;
}
} ;
2023-02-24 23:16:19 -05:00
2023-03-15 00:50:27 +08:00
const errorHandler = ( data , currentState , currentMsg ) => {
2023-03-14 03:38:47 +08:00
const { initialResponse , messages , _currentMsg , message } = currentState ;
2023-03-15 00:50:27 +08:00
console . log ( 'Error:' , data ) ;
2023-03-11 21:42:08 -05:00
const errorResponse = {
2023-03-15 00:50:27 +08:00
... data ,
2023-03-14 03:38:47 +08:00
error : true ,
2023-03-17 12:34:54 -04:00
parentMessageId : currentMsg ? . messageId
2023-02-06 21:17:46 -05:00
} ;
2023-03-15 00:50:27 +08:00
setErrorMessage ( data ? . text ) ;
2023-03-11 21:42:08 -05:00
dispatch ( setSubmitState ( false ) ) ;
2023-03-15 00:50:27 +08:00
dispatch ( setMessages ( [ ... messages , currentMsg , errorResponse ] ) ) ;
2023-03-13 05:26:17 +08:00
dispatch ( setText ( message ? . text ) ) ;
2023-03-11 21:42:08 -05:00
dispatch ( setError ( true ) ) ;
return ;
} ;
const submitMessage = ( ) => {
2023-03-17 12:34:54 -04:00
ask ( { text } ) ;
2023-03-11 21:42:08 -05:00
} ;
useEffect ( ( ) => {
2023-03-15 18:05:34 -04:00
inputRef . current ? . focus ( ) ;
2023-03-11 21:42:08 -05:00
if ( Object . keys ( submission ) . length === 0 ) {
return ;
}
const currentState = submission ;
2023-03-18 00:52:16 +08:00
<< << << < HEAD
2023-03-17 12:34:54 -04:00
let currentMsg = { ... currentState . message } ;
2023-03-18 00:52:16 +08:00
=== === =
let currentMsg = { ... currentState . message } ;
let latestResponseText = '' ;
>>> >>> > 92 d0d11 ( feat : save cancelled flag in message )
2023-03-11 21:42:08 -05:00
const { server , payload } = createPayload ( submission ) ;
const onMessage = ( e ) => {
if ( stopStream ) {
return ;
}
const data = JSON . parse ( e . data ) ;
2023-03-13 12:35:55 +08:00
2023-03-11 21:42:08 -05:00
if ( data . final ) {
2023-03-13 13:11:53 +08:00
convoHandler ( data , currentState , currentMsg ) ;
2023-03-11 21:42:08 -05:00
console . log ( 'final' , data ) ;
2023-03-17 12:34:54 -04:00
}
if ( data . created ) {
2023-03-13 13:11:53 +08:00
currentMsg = data . message ;
createdHandler ( data , currentState , currentMsg ) ;
2023-03-11 21:42:08 -05:00
} else {
2023-03-13 12:35:55 +08:00
let text = data . text || data . response ;
2023-03-17 12:34:54 -04:00
if ( data . initial ) {
console . log ( data ) ;
}
2023-03-13 12:35:55 +08:00
if ( data . message ) {
2023-03-18 00:52:16 +08:00
latestResponseText = text ;
2023-03-13 13:11:53 +08:00
messageHandler ( text , currentState , currentMsg ) ;
2023-03-13 12:35:55 +08:00
}
2023-03-11 21:42:08 -05:00
// console.log('dataStream', data);
}
} ;
const events = new SSE ( server , {
payload : JSON . stringify ( payload ) ,
headers : { 'Content-Type' : 'application/json' }
} ) ;
events . onopen = function ( ) {
console . log ( 'connection is opened' ) ;
} ;
events . onmessage = onMessage ;
2023-03-18 00:52:16 +08:00
events . oncancel = ( e ) => {
cancelHandler ( latestResponseText , currentState , currentMsg ) ;
} ;
2023-03-11 21:42:08 -05:00
events . onerror = function ( e ) {
console . log ( 'error in opening conn.' ) ;
events . close ( ) ;
2023-03-15 00:50:27 +08:00
const data = JSON . parse ( e . data ) ;
errorHandler ( data , currentState , currentMsg ) ;
2023-03-11 21:42:08 -05:00
} ;
events . stream ( ) ;
return ( ) => {
2023-03-17 09:56:42 -04:00
dispatch ( setSubmitState ( false ) ) ;
2023-03-11 21:42:08 -05:00
events . removeEventListener ( 'message' , onMessage ) ;
2023-03-18 00:52:16 +08:00
const isCancelled = events . readyState <= 1 ;
2023-03-11 21:42:08 -05:00
events . close ( ) ;
2023-03-18 00:52:16 +08:00
if ( isCancelled ) {
const e = new Event ( "cancel" ) ;
events . dispatchEvent ( e )
}
2023-03-11 21:42:08 -05:00
} ;
} , [ submission ] ) ;
2023-03-17 01:49:09 +08:00
const handleRegenerate = ( ) => {
2023-03-17 12:34:54 -04:00
if ( latestMessage && ! latestMessage ? . isCreatedByUser ) regenerate ( latestMessage ) ;
} ;
2023-03-17 01:49:09 +08:00
2023-03-17 03:13:42 +08:00
const handleStopGenerating = ( ) => {
2023-03-17 12:34:54 -04:00
stopGenerating ( ) ;
} ;
2023-03-17 03:13:42 +08:00
2023-02-08 08:27:23 -05:00
const handleKeyDown = ( e ) => {
if ( e . key === 'Enter' && ! e . shiftKey ) {
e . preventDefault ( ) ;
}
2023-03-13 22:53:29 +08:00
if ( e . key === 'Enter' && ! e . shiftKey ) {
2023-03-17 12:34:54 -04:00
if ( ! isComposing . current ) submitMessage ( ) ;
2023-03-13 22:53:29 +08:00
}
2023-02-08 08:27:23 -05:00
} ;
const handleKeyUp = ( e ) => {
2023-02-04 20:48:33 -05:00
if ( e . key === 'Enter' && e . shiftKey ) {
2023-02-22 21:30:48 -05:00
return console . log ( 'Enter + Shift' ) ;
2023-02-04 20:48:33 -05:00
}
2023-02-22 21:30:48 -05:00
if ( isSubmitting ) {
return ;
}
2023-02-04 20:48:33 -05:00
} ;
2023-03-17 12:34:54 -04:00
2023-03-13 22:53:29 +08:00
const handleCompositionStart = ( e ) => {
2023-03-17 12:34:54 -04:00
isComposing . current = true ;
} ;
2023-03-13 22:53:29 +08:00
const handleCompositionEnd = ( e ) => {
isComposing . current = false ;
2023-03-17 12:34:54 -04:00
} ;
2023-02-04 20:48:33 -05:00
2023-02-07 16:22:35 -05:00
const changeHandler = ( e ) => {
const { value } = e . target ;
2023-03-14 11:42:35 +08:00
2023-02-07 16:22:35 -05:00
if ( isSubmitting && ( value === '' || value === '\n' ) ) {
return ;
}
2023-02-08 09:59:01 -05:00
dispatch ( setText ( value ) ) ;
2023-02-07 16:22:35 -05:00
} ;
2023-02-11 10:22:15 -05:00
const tryAgain = ( e ) => {
e . preventDefault ( ) ;
dispatch ( setError ( false ) ) ;
} ;
2023-03-17 12:34:54 -04:00
2023-02-04 20:48:33 -05:00
return (
2023-03-18 00:29:34 +08:00
< div className = "input-panel fixed md:absolute bottom-0 left-0 w-full border-t md:border-t-0 dark:border-white/20 md:border-transparent md:dark:border-transparent md:bg-vert-light-gradient bg-white dark:bg-gray-800 md:dark:bg-transparent md:bg-transparent dark:md:bg-vert-dark-gradient py-2" >
< form className = "stretch mx-2 flex flex-row gap-3 md:pt-2 last:mb-2 md:last:mb-6 lg:mx-auto lg:max-w-3xl lg:pt-6" >
2023-02-06 13:27:28 -05:00
< div className = "relative flex h-full flex-1 md:flex-col" >
2023-03-18 00:52:16 +08:00
< span className = "flex ml-1 md:w-full md:m-auto md:mb-2 gap-0 md:gap-2 justify-center order-last md:order-none" >
{ isSubmitting ?
2023-03-17 12:34:54 -04:00
< button
onClick = { handleStopGenerating }
className = "input-panel-button btn btn-neutral flex justify-center gap-2 border-0 md:border"
type = "button"
>
< StopGeneratingIcon / >
< span className = "hidden md:block" > Stop generating < / span >
< / button >
2023-03-18 00:52:16 +08:00
: ( latestMessage && ! latestMessage ? . isCreatedByUser ) ?
< button
onClick = { handleRegenerate }
className = "input-panel-button btn btn-neutral flex justify-center gap-2 border-0 md:border"
type = "button"
>
< RegenerateIcon / >
< span className = "hidden md:block" > Regenerate response < / span >
< / button >
: null
}
2023-03-17 12:34:54 -04:00
< / span >
< div
className = { ` relative flex flex-grow flex-col rounded-md border border-black/10 ${
disabled ? 'bg-gray-100' : 'bg-white'
} py - 2 shadow - [ 0 _0 _10px _rgba ( 0 , 0 , 0 , 0.10 ) ] dark : border - gray - 900 / 50 $ {
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
} dark : text - white dark : shadow - [ 0 _0 _15px _rgba ( 0 , 0 , 0 , 0.10 ) ] md : py - 3 md : pl - 4 ` }
>
< ModelMenu / >
< TextareaAutosize
tabIndex = "0"
autoFocus
ref = { inputRef }
// style={{maxHeight: '200px', height: '24px', overflowY: 'hidden'}}
rows = "1"
2023-03-18 00:52:16 +08:00
value = { ( disabled || isNotAppendable ) ? '' : text }
2023-03-17 12:34:54 -04:00
onKeyUp = { handleKeyUp }
onKeyDown = { handleKeyDown }
onChange = { changeHandler }
onCompositionStart = { handleCompositionStart }
onCompositionEnd = { handleCompositionEnd }
2023-03-18 00:52:16 +08:00
placeholder = { disabled ? 'Choose another model or customize GPT again' : isNotAppendable ? 'Can not send new message after an error or unfinished response.' : '' }
2023-03-17 12:34:54 -04:00
disabled = { disabled || isNotAppendable }
2023-03-18 00:52:16 +08:00
className = "m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-12 pr-8 leading-6 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-8"
2023-03-17 12:34:54 -04:00
/ >
2023-03-18 00:52:16 +08:00
< SubmitButton submitMessage = { submitMessage } disabled = { disabled || isNotAppendable } / >
2023-03-17 12:34:54 -04:00
< / div >
2023-02-06 13:27:28 -05:00
< / div >
< / form >
2023-02-11 12:54:02 -05:00
< Footer / >
2023-02-06 13:27:28 -05:00
< / div >
2023-02-04 20:48:33 -05:00
) ;
2023-02-20 21:52:08 -05:00
}