2023-06-21 13:20:21 +00:00
import React , { FC , useCallback , useEffect , useRef , useState } from 'react' ;
2023-05-21 05:48:11 +00:00
import { useTranslation } from 'react-i18next' ;
2023-06-21 13:20:21 +00:00
import { Avatar , Button , Menu , MenuPopover , MenuTrigger , PresenceBadge , Textarea } from '@fluentui/react-components' ;
2023-05-21 05:48:11 +00:00
import commonStore , { ModelStatus } from '../stores/commonStore' ;
import { observer } from 'mobx-react-lite' ;
import { v4 as uuid } from 'uuid' ;
2023-05-19 06:22:37 +00:00
import classnames from 'classnames' ;
2023-05-21 05:48:11 +00:00
import { fetchEventSource } from '@microsoft/fetch-event-source' ;
2023-06-21 13:20:21 +00:00
import { KebabHorizontalIcon , PencilIcon , SyncIcon , TrashIcon } from '@primer/octicons-react' ;
2023-07-09 03:59:23 +00:00
import logo from '../assets/images/logo.png' ;
2023-05-19 07:40:17 +00:00
import MarkdownRender from '../components/MarkdownRender' ;
2023-05-21 05:48:11 +00:00
import { ToolTipButton } from '../components/ToolTipButton' ;
2023-10-27 03:36:29 +00:00
import {
ArrowCircleUp28Regular ,
ArrowClockwise16Regular ,
Attach16Regular ,
Delete28Regular ,
Dismiss16Regular ,
RecordStop28Regular ,
Save28Regular
} from '@fluentui/react-icons' ;
2023-05-21 05:48:11 +00:00
import { CopyButton } from '../components/CopyButton' ;
import { ReadButton } from '../components/ReadButton' ;
import { toast } from 'react-toastify' ;
2023-05-24 13:27:23 +00:00
import { WorkHeader } from '../components/WorkHeader' ;
2023-06-15 14:55:38 +00:00
import { DialogButton } from '../components/DialogButton' ;
2023-10-27 03:36:29 +00:00
import { OpenFileFolder , OpenOpenFileDialog , OpenSaveFileDialog } from '../../wailsjs/go/backend_golang/App' ;
2023-10-27 04:53:20 +00:00
import { absPathAsset , bytesToReadable , toastWithButton } from '../utils' ;
2023-06-24 16:07:14 +00:00
import { PresetsButton } from './PresetsManager/PresetsButton' ;
import { useMediaQuery } from 'usehooks-ts' ;
2023-05-19 06:22:37 +00:00
2023-05-20 08:07:08 +00:00
export const userName = 'M E' ;
export const botName = 'A I' ;
2023-05-19 06:22:37 +00:00
2023-06-21 13:20:21 +00:00
export const welcomeUuid = 'welcome' ;
2023-05-20 08:07:08 +00:00
export enum MessageType {
2023-05-19 06:22:37 +00:00
Normal ,
Error
}
2023-05-20 08:07:08 +00:00
export type Side = 'left' | 'right'
2023-05-19 06:22:37 +00:00
2023-05-20 08:07:08 +00:00
export type Color = 'neutral' | 'brand' | 'colorful'
2023-05-19 06:22:37 +00:00
2023-05-20 08:07:08 +00:00
export type MessageItem = {
2023-05-19 06:22:37 +00:00
sender : string ,
type : MessageType ,
color : Color ,
avatarImg? : string ,
time : string ,
content : string ,
side : Side ,
done : boolean
}
2023-06-15 16:12:13 +00:00
export type Conversation = {
2023-05-19 06:22:37 +00:00
[ uuid : string ] : MessageItem
}
2023-06-24 16:07:14 +00:00
export type Role = 'assistant' | 'user' | 'system' ;
export type ConversationMessage = {
role : Role ;
content : string ;
}
2023-10-27 04:13:05 +00:00
let chatSseControllers : { [ id : string ] : AbortController } = { } ;
2023-06-14 12:06:19 +00:00
2023-06-21 13:20:21 +00:00
const MoreUtilsButton : FC < { uuid : string , setEditing : ( editing : boolean ) = > void } > = observer ( ( {
uuid ,
setEditing
} ) = > {
const { t } = useTranslation ( ) ;
const [ speaking , setSpeaking ] = useState ( false ) ;
const messageItem = commonStore . conversation [ uuid ] ;
return < Menu >
< MenuTrigger disableButtonEnhancement >
< Button icon = { < KebabHorizontalIcon / > } size = "small" appearance = "subtle" / >
< / MenuTrigger >
< MenuPopover style = { { minWidth : 0 } } >
2023-06-21 15:11:22 +00:00
< CopyButton content = { messageItem . content } showDelay = { 500 } / >
2023-06-21 13:20:21 +00:00
< ReadButton content = { messageItem . content } inSpeaking = { speaking } showDelay = { 500 } setSpeakingOuter = { setSpeaking } / >
< ToolTipButton desc = { t ( 'Edit' ) } icon = { < PencilIcon / > } showDelay = { 500 } size = "small" appearance = "subtle"
onClick = { ( ) = > {
setEditing ( true ) ;
} } / >
< ToolTipButton desc = { t ( 'Delete' ) } icon = { < TrashIcon / > } showDelay = { 500 } size = "small" appearance = "subtle"
onClick = { ( ) = > {
commonStore . conversationOrder . splice ( commonStore . conversationOrder . indexOf ( uuid ) , 1 ) ;
delete commonStore . conversation [ uuid ] ;
} } / >
< / MenuPopover >
< / Menu > ;
} ) ;
const ChatMessageItem : FC < {
uuid : string , onSubmit : ( message : string | null , answerId : string | null ,
startUuid : string | null , endUuid : string | null , includeEndUuid : boolean ) = > void
} > = observer ( ( { uuid , onSubmit } ) = > {
const { t } = useTranslation ( ) ;
const [ editing , setEditing ] = useState ( false ) ;
const textareaRef = useRef < HTMLTextAreaElement > ( null ) ;
const messageItem = commonStore . conversation [ uuid ] ;
console . log ( uuid ) ;
const setEditingInner = ( editing : boolean ) = > {
setEditing ( editing ) ;
if ( editing ) {
setTimeout ( ( ) = > {
const textarea = textareaRef . current ;
if ( textarea ) {
textarea . focus ( ) ;
textarea . selectionStart = textarea . value . length ;
textarea . selectionEnd = textarea . value . length ;
textarea . style . height = textarea . scrollHeight + 'px' ;
}
} ) ;
}
} ;
2023-10-27 04:53:20 +00:00
let avatarImg : string | undefined ;
if ( commonStore . activePreset && messageItem . sender === botName ) {
avatarImg = absPathAsset ( commonStore . activePreset . avatarImg ) ;
} else if ( messageItem . avatarImg ) {
avatarImg = messageItem . avatarImg ;
}
2023-06-21 13:20:21 +00:00
return < div
className = { classnames (
'flex gap-2 mb-2 overflow-hidden' ,
messageItem . side === 'left' ? 'flex-row' : 'flex-row-reverse'
) }
onMouseEnter = { ( ) = > {
const utils = document . getElementById ( 'utils-' + uuid ) ;
if ( utils ) utils . classList . remove ( 'invisible' ) ;
} }
onMouseLeave = { ( ) = > {
const utils = document . getElementById ( 'utils-' + uuid ) ;
if ( utils ) utils . classList . add ( 'invisible' ) ;
} }
>
< Avatar
color = { messageItem . color }
name = { messageItem . sender }
2023-10-27 04:53:20 +00:00
image = { avatarImg ? { src : avatarImg } : undefined }
2023-06-21 13:20:21 +00:00
/ >
< div
className = { classnames (
'flex p-2 rounded-lg overflow-hidden' ,
editing ? 'grow' : '' ,
messageItem . side === 'left' ? 'bg-gray-200' : 'bg-blue-500' ,
messageItem . side === 'left' ? 'text-gray-600' : 'text-white'
) }
>
{ ! editing ?
< MarkdownRender > { messageItem . content } < / MarkdownRender > :
< Textarea ref = { textareaRef }
className = "grow"
style = { { minWidth : 0 } }
value = { messageItem . content }
onChange = { ( e ) = > {
messageItem . content = e . target . value ;
2023-10-27 05:11:12 +00:00
commonStore . conversation [ uuid ] . type = MessageType . Normal ;
commonStore . conversation [ uuid ] . done = true ;
commonStore . setConversation ( commonStore . conversation ) ;
commonStore . setConversationOrder ( [ . . . commonStore . conversationOrder ] ) ;
2023-06-21 13:20:21 +00:00
} }
onBlur = { ( ) = > {
setEditingInner ( false ) ;
} } / > }
< / div >
< div className = "flex flex-col gap-1 items-start" >
< div className = "grow" / >
{ ( messageItem . type === MessageType . Error || ! messageItem . done ) &&
< PresenceBadge size = "extra-small" status = {
messageItem . type === MessageType . Error ? 'busy' : 'away'
} / >
}
< div className = "flex invisible" id = { 'utils-' + uuid } >
{
messageItem . sender === botName && uuid !== welcomeUuid &&
< ToolTipButton desc = { t ( 'Retry' ) } size = "small" appearance = "subtle"
icon = { < SyncIcon / > } onClick = { ( ) = > {
2023-10-27 04:13:05 +00:00
if ( uuid in chatSseControllers ) {
chatSseControllers [ uuid ] . abort ( ) ;
delete chatSseControllers [ uuid ] ;
}
2023-06-21 13:20:21 +00:00
onSubmit ( null , uuid , null , uuid , false ) ;
} } / >
}
2023-06-21 15:11:22 +00:00
< ToolTipButton desc = { t ( 'Edit' ) } icon = { < PencilIcon / > } size = "small" appearance = "subtle"
onClick = { ( ) = > {
setEditingInner ( true ) ;
} } / >
2023-06-21 13:20:21 +00:00
< MoreUtilsButton uuid = { uuid } setEditing = { setEditingInner } / >
< / div >
< / div >
< / div > ;
} ) ;
2023-05-19 06:22:37 +00:00
const ChatPanel : FC = observer ( ( ) = > {
2023-05-21 05:48:11 +00:00
const { t } = useTranslation ( ) ;
2023-05-19 06:22:37 +00:00
const bodyRef = useRef < HTMLDivElement > ( null ) ;
2023-05-19 07:40:17 +00:00
const inputRef = useRef < HTMLTextAreaElement > ( null ) ;
2023-06-24 16:07:14 +00:00
const mq = useMediaQuery ( '(min-width: 640px)' ) ;
2023-07-25 07:59:37 +00:00
const currentConfig = commonStore . getCurrentModelConfig ( ) ;
const apiParams = currentConfig . apiParameters ;
const port = apiParams . apiPort ;
2023-10-27 04:13:05 +00:00
const generating : boolean = Object . keys ( chatSseControllers ) . length > 0 ;
2023-05-19 06:22:37 +00:00
2023-05-19 07:40:17 +00:00
useEffect ( ( ) = > {
if ( inputRef . current )
inputRef . current . style . maxHeight = '16rem' ;
2023-05-23 04:24:39 +00:00
scrollToBottom ( ) ;
2023-05-19 07:40:17 +00:00
} , [ ] ) ;
2023-05-20 08:07:08 +00:00
useEffect ( ( ) = > {
2023-06-15 16:12:13 +00:00
if ( commonStore . conversationOrder . length === 0 ) {
2023-06-21 13:20:21 +00:00
commonStore . setConversationOrder ( [ welcomeUuid ] ) ;
2023-06-15 16:12:13 +00:00
commonStore . setConversation ( {
2023-06-21 13:20:21 +00:00
[ welcomeUuid ] : {
2023-05-20 08:07:08 +00:00
sender : botName ,
type : MessageType . Normal ,
color : 'colorful' ,
avatarImg : logo ,
time : new Date ( ) . toISOString ( ) ,
2023-06-17 11:32:47 +00:00
content : t ( 'Hello! I\'m RWKV, an open-source and commercially usable large language model.' ) ,
2023-05-20 08:07:08 +00:00
side : 'left' ,
done : true
}
} ) ;
}
} , [ ] ) ;
2023-05-19 06:22:37 +00:00
const scrollToBottom = ( ) = > {
if ( bodyRef . current )
bodyRef . current . scrollTop = bodyRef . current . scrollHeight ;
} ;
2023-05-19 12:10:30 +00:00
const handleKeyDownOrClick = ( e : any ) = > {
e . stopPropagation ( ) ;
if ( e . type === 'click' || ( e . keyCode === 13 && ! e . shiftKey ) ) {
e . preventDefault ( ) ;
2023-06-20 16:26:50 +00:00
if ( commonStore . status . status === ModelStatus . Offline && ! commonStore . settings . apiUrl ) {
2023-05-21 05:48:11 +00:00
toast ( t ( 'Please click the button in the top right corner to start the model' ) , { type : 'warning' } ) ;
2023-05-20 02:38:35 +00:00
return ;
}
2023-06-14 12:26:04 +00:00
if ( ! commonStore . currentInput ) return ;
onSubmit ( commonStore . currentInput ) ;
commonStore . setCurrentInput ( '' ) ;
2023-05-19 12:10:30 +00:00
}
} ;
2023-06-21 13:20:21 +00:00
// if message is not null, create a user message;
// if answerId is not null, override the answer with new response;
// if startUuid is null, start generating api body messages from first message;
// if endUuid is null, generate api body messages until last message;
const onSubmit = useCallback ( ( message : string | null = null , answerId : string | null = null ,
startUuid : string | null = null , endUuid : string | null = null , includeEndUuid : boolean = false ) = > {
if ( message ) {
const newId = uuid ( ) ;
commonStore . conversation [ newId ] = {
sender : userName ,
type : MessageType . Normal ,
color : 'brand' ,
time : new Date ( ) . toISOString ( ) ,
content : message ,
side : 'right' ,
done : true
} ;
commonStore . setConversation ( commonStore . conversation ) ;
commonStore . conversationOrder . push ( newId ) ;
commonStore . setConversationOrder ( commonStore . conversationOrder ) ;
}
let startIndex = startUuid ? commonStore . conversationOrder . indexOf ( startUuid ) : 0 ;
let endIndex = endUuid ? ( commonStore . conversationOrder . indexOf ( endUuid ) + ( includeEndUuid ? 1 : 0 ) ) : commonStore . conversationOrder . length ;
let targetRange = commonStore . conversationOrder . slice ( startIndex , endIndex ) ;
2023-05-19 12:10:30 +00:00
2023-06-24 16:07:14 +00:00
const messages : ConversationMessage [ ] = [ ] ;
2023-10-27 03:36:29 +00:00
if ( commonStore . attachmentContent ) {
messages . push ( {
role : 'user' ,
content : t ( 'The content of file' ) + ` " ${ commonStore . attachmentName } " `
+ t ( 'is as follows. When replying to me, consider the file content and respond accordingly:' )
+ '\n\n' + commonStore . attachmentContent
} ) ;
messages . push ( { role : 'user' , content : t ( 'What\'s the file name' ) } ) ;
messages . push ( { role : 'assistant' , content : t ( 'The file name is: ' ) + commonStore . attachmentName } ) ;
}
2023-06-21 13:20:21 +00:00
targetRange . forEach ( ( uuid , index ) = > {
if ( uuid === welcomeUuid )
return ;
2023-06-15 16:12:13 +00:00
const messageItem = commonStore . conversation [ uuid ] ;
2023-06-21 13:20:21 +00:00
if ( messageItem . done && messageItem . type === MessageType . Normal && messageItem . sender === userName ) {
messages . push ( { role : 'user' , content : messageItem.content } ) ;
} else if ( messageItem . done && messageItem . type === MessageType . Normal && messageItem . sender === botName ) {
messages . push ( { role : 'assistant' , content : messageItem.content } ) ;
2023-05-19 12:10:30 +00:00
}
} ) ;
2023-06-21 13:20:21 +00:00
if ( answerId === null ) {
answerId = uuid ( ) ;
commonStore . conversationOrder . push ( answerId ) ;
}
2023-06-15 16:12:13 +00:00
commonStore . conversation [ answerId ] = {
2023-05-19 12:10:30 +00:00
sender : botName ,
type : MessageType . Normal ,
color : 'colorful' ,
avatarImg : logo ,
time : new Date ( ) . toISOString ( ) ,
content : '' ,
side : 'left' ,
done : false
} ;
2023-06-15 16:12:13 +00:00
commonStore . setConversation ( commonStore . conversation ) ;
commonStore . setConversationOrder ( commonStore . conversationOrder ) ;
2023-05-19 12:10:30 +00:00
setTimeout ( scrollToBottom ) ;
let answer = '' ;
2023-10-27 04:13:05 +00:00
const chatSseController = new AbortController ( ) ;
chatSseControllers [ answerId ] = chatSseController ;
2023-06-20 15:24:51 +00:00
fetchEventSource ( // https://api.openai.com/v1/chat/completions || http://127.0.0.1:${port}/chat/completions
commonStore . settings . apiUrl ?
commonStore . settings . apiUrl + '/v1/chat/completions' :
` http://127.0.0.1: ${ port } /chat/completions ` ,
2023-05-19 12:10:30 +00:00
{
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
2023-06-20 15:24:51 +00:00
Authorization : ` Bearer ${ commonStore . settings . apiKey } `
2023-05-19 12:10:30 +00:00
} ,
body : JSON.stringify ( {
messages ,
stream : true ,
2023-07-25 07:59:37 +00:00
model : commonStore.settings.apiChatModelName , // 'gpt-3.5-turbo'
temperature : apiParams.temperature ,
2023-09-16 05:02:06 +00:00
top_p : apiParams.topP ,
user_name : commonStore.activePreset?.userName ,
assistant_name : commonStore.activePreset?.assistantName ,
presystem : commonStore.activePreset?.presystem
2023-05-19 12:10:30 +00:00
} ) ,
2023-06-14 12:06:19 +00:00
signal : chatSseController?.signal ,
2023-05-19 12:10:30 +00:00
onmessage ( e ) {
scrollToBottom ( ) ;
2023-07-25 07:59:37 +00:00
if ( e . data . trim ( ) === '[DONE]' ) {
2023-06-21 13:20:21 +00:00
commonStore . conversation [ answerId ! ] . done = true ;
commonStore . conversation [ answerId ! ] . content = commonStore . conversation [ answerId ! ] . content . trim ( ) ;
2023-06-15 16:12:13 +00:00
commonStore . setConversation ( commonStore . conversation ) ;
commonStore . setConversationOrder ( [ . . . commonStore . conversationOrder ] ) ;
2023-05-19 12:10:30 +00:00
return ;
2023-05-19 06:22:37 +00:00
}
2023-05-19 12:10:30 +00:00
let data ;
try {
data = JSON . parse ( e . data ) ;
} catch ( error ) {
console . debug ( 'json error' , error ) ;
return ;
}
if ( data . choices && Array . isArray ( data . choices ) && data . choices . length > 0 ) {
answer += data . choices [ 0 ] ? . delta ? . content || '' ;
2023-06-21 13:20:21 +00:00
commonStore . conversation [ answerId ! ] . content = answer ;
2023-06-15 16:12:13 +00:00
commonStore . setConversation ( commonStore . conversation ) ;
commonStore . setConversationOrder ( [ . . . commonStore . conversationOrder ] ) ;
2023-05-19 12:10:30 +00:00
}
} ,
2023-06-20 16:26:50 +00:00
async onopen ( response ) {
if ( response . status !== 200 ) {
2023-06-21 13:20:21 +00:00
commonStore . conversation [ answerId ! ] . content += '\n[ERROR]\n```\n' + response . statusText + '\n' + ( await response . text ( ) ) + '\n```' ;
2023-06-20 16:26:50 +00:00
commonStore . setConversation ( commonStore . conversation ) ;
commonStore . setConversationOrder ( [ . . . commonStore . conversationOrder ] ) ;
setTimeout ( scrollToBottom ) ;
}
} ,
2023-05-19 12:10:30 +00:00
onclose() {
2023-10-27 04:13:05 +00:00
if ( answerId ! in chatSseControllers )
delete chatSseControllers [ answerId ! ] ;
2023-05-19 12:10:30 +00:00
console . log ( 'Connection closed' ) ;
} ,
onerror ( err ) {
2023-06-21 13:20:21 +00:00
commonStore . conversation [ answerId ! ] . type = MessageType . Error ;
commonStore . conversation [ answerId ! ] . done = true ;
2023-06-20 16:26:50 +00:00
err = err . message || err ;
if ( err && ! err . includes ( 'ReadableStreamDefaultReader' ) )
2023-06-21 13:20:21 +00:00
commonStore . conversation [ answerId ! ] . content += '\n[ERROR]\n```\n' + err + '\n```' ;
2023-06-15 16:12:13 +00:00
commonStore . setConversation ( commonStore . conversation ) ;
commonStore . setConversationOrder ( [ . . . commonStore . conversationOrder ] ) ;
2023-06-20 16:26:50 +00:00
setTimeout ( scrollToBottom ) ;
2023-05-19 12:10:30 +00:00
throw err ;
}
} ) ;
2023-06-21 13:20:21 +00:00
} , [ ] ) ;
2023-05-05 15:23:34 +00:00
2023-05-19 02:08:28 +00:00
return (
2023-05-19 06:22:37 +00:00
< div className = "flex flex-col w-full grow gap-4 pt-4 overflow-hidden" >
< div ref = { bodyRef } className = "grow overflow-y-scroll overflow-x-hidden pr-2" >
2023-06-21 13:20:21 +00:00
{ commonStore . conversationOrder . map ( uuid = >
< ChatMessageItem key = { uuid } uuid = { uuid } onSubmit = { onSubmit } / >
) }
2023-05-19 06:22:37 +00:00
< / div >
2023-06-24 16:07:14 +00:00
< div className = { classnames ( 'flex items-end' , mq ? 'gap-2' : '' ) } >
< PresetsButton tab = "Chat" size = { mq ? 'large' : 'small' } shape = "circular" appearance = "subtle" / >
2023-06-15 14:55:38 +00:00
< DialogButton tooltip = { t ( 'Clear' ) }
2023-05-21 05:48:11 +00:00
icon = { < Delete28Regular / > }
2023-06-24 16:07:14 +00:00
size = { mq ? 'large' : 'small' } shape = "circular" appearance = "subtle" title = { t ( 'Clear' ) }
2023-06-15 14:55:38 +00:00
contentText = { t ( 'Are you sure you want to clear the conversation? It cannot be undone.' ) }
onConfirm = { ( ) = > {
2023-10-27 04:13:05 +00:00
if ( generating ) {
for ( const id in chatSseControllers ) {
chatSseControllers [ id ] . abort ( ) ;
}
chatSseControllers = { } ;
}
2023-06-15 16:12:13 +00:00
commonStore . setConversation ( { } ) ;
commonStore . setConversationOrder ( [ ] ) ;
2023-06-15 14:55:38 +00:00
} } / >
2023-10-27 03:36:29 +00:00
< div className = "relative flex grow" >
< Textarea
ref = { inputRef }
style = { { minWidth : 0 } }
className = "grow"
resize = "vertical"
placeholder = { t ( 'Type your message here' ) ! }
value = { commonStore . currentInput }
onChange = { ( e ) = > commonStore . setCurrentInput ( e . target . value ) }
onKeyDown = { handleKeyDownOrClick }
/ >
< div className = "absolute right-2 bottom-2" >
{ ! commonStore . attachmentContent ?
< ToolTipButton
desc = { commonStore . attachmentUploading ?
t ( 'Uploading Attachment' ) :
t ( 'Add An Attachment (Accepts pdf, txt)' ) }
icon = { commonStore . attachmentUploading ?
< ArrowClockwise16Regular className = "animate-spin" / >
: < Attach16Regular / > }
size = "small" shape = "circular" appearance = "secondary"
onClick = { ( ) = > {
if ( commonStore . status . status === ModelStatus . Offline && ! commonStore . settings . apiUrl ) {
toast ( t ( 'Please click the button in the top right corner to start the model' ) , { type : 'warning' } ) ;
return ;
}
if ( commonStore . attachmentUploading )
return ;
OpenOpenFileDialog ( '*.txt;*.pdf' ) . then ( async filePath = > {
if ( ! filePath )
return ;
commonStore . setAttachmentUploading ( true ) ;
// Both are slow. Communication between frontend and backend is slow. Use AssetServer Handler to read the file.
// const blob = new Blob([atob(info.content as unknown as string)]); // await fetch(`data:application/octet-stream;base64,${info.content}`).then(r => r.blob());
2023-10-27 04:53:20 +00:00
const blob = await fetch ( absPathAsset ( filePath ) ) . then ( r = > r . blob ( ) ) ;
2023-10-27 03:36:29 +00:00
const attachmentName = filePath . split ( /[\\/]/ ) . pop ( ) ;
const urlPath = ` /file-to-text?file_name= ${ attachmentName } ` ;
const bodyForm = new FormData ( ) ;
bodyForm . append ( 'file_data' , blob , attachmentName ) ;
fetch ( commonStore . settings . apiUrl ?
commonStore . settings . apiUrl + urlPath :
` http://127.0.0.1: ${ port } ${ urlPath } ` , {
method : 'POST' ,
body : bodyForm
} ) . then ( async r = > {
if ( r . status === 200 ) {
const pages = ( await r . json ( ) ) . pages as any [ ] ;
let attachmentContent : string ;
if ( pages . length === 1 )
attachmentContent = pages [ 0 ] . page_content ;
else
attachmentContent = pages . map ( ( p , i ) = > ` Page ${ i + 1 } : \ n ${ p . page_content } ` ) . join ( '\n\n' ) ;
commonStore . setAttachmentName ( attachmentName ! ) ;
commonStore . setAttachmentSize ( blob . size ) ;
commonStore . setAttachmentContent ( attachmentContent ) ;
} else {
toast ( r . statusText + '\n' + ( await r . text ( ) ) , {
type : 'error'
} ) ;
}
commonStore . setAttachmentUploading ( false ) ;
}
) . catch ( e = > {
commonStore . setAttachmentUploading ( false ) ;
toast ( t ( 'Error' ) + ' - ' + ( e . message || e ) , { type : 'error' , autoClose : 2500 } ) ;
} ) ;
} ) . catch ( e = > {
toast ( t ( 'Error' ) + ' - ' + ( e . message || e ) , { type : 'error' , autoClose : 2500 } ) ;
} ) ;
} }
/ > :
< div >
< ToolTipButton
text = {
commonStore . attachmentName . replace (
new RegExp ( '(^[^\\.]{5})[^\\.]+' ) , '$1...' )
}
desc = { ` ${ commonStore . attachmentName } ( ${ bytesToReadable ( commonStore . attachmentSize ) } ) ` }
size = "small" shape = "circular" appearance = "secondary" / >
< ToolTipButton desc = { t ( 'Remove Attachment' ) }
icon = { < Dismiss16Regular / > }
size = "small" shape = "circular" appearance = "subtle"
onClick = { ( ) = > {
commonStore . setAttachmentName ( '' ) ;
commonStore . setAttachmentSize ( 0 ) ;
commonStore . setAttachmentContent ( '' ) ;
} } / >
< / div >
}
< / div >
< / div >
2023-05-19 12:10:30 +00:00
< ToolTipButton desc = { generating ? t ( 'Stop' ) : t ( 'Send' ) }
2023-05-21 05:48:11 +00:00
icon = { generating ? < RecordStop28Regular / > : < ArrowCircleUp28Regular / > }
2023-06-24 16:07:14 +00:00
size = { mq ? 'large' : 'small' } shape = "circular" appearance = "subtle"
2023-05-21 05:48:11 +00:00
onClick = { ( e ) = > {
if ( generating ) {
2023-10-27 04:13:05 +00:00
for ( const id in chatSseControllers ) {
chatSseControllers [ id ] . abort ( ) ;
commonStore . conversation [ id ] . type = MessageType . Error ;
commonStore . conversation [ id ] . done = true ;
2023-05-21 05:48:11 +00:00
}
2023-10-27 04:13:05 +00:00
chatSseControllers = { } ;
commonStore . setConversation ( commonStore . conversation ) ;
commonStore . setConversationOrder ( [ . . . commonStore . conversationOrder ] ) ;
2023-05-21 05:48:11 +00:00
} else {
handleKeyDownOrClick ( e ) ;
}
} } / >
2023-06-15 16:12:13 +00:00
< ToolTipButton desc = { t ( 'Save' ) }
icon = { < Save28Regular / > }
2023-06-24 16:07:14 +00:00
size = { mq ? 'large' : 'small' } shape = "circular" appearance = "subtle"
2023-06-15 16:12:13 +00:00
onClick = { ( ) = > {
let savedContent : string = '' ;
2023-06-28 12:48:22 +00:00
const isWorldModel = commonStore . getCurrentModelConfig ( ) . modelParameters . modelName . toLowerCase ( ) . includes ( 'world' ) ;
2023-10-26 08:58:53 +00:00
const user = isWorldModel ? 'User' : 'Bob' ;
const bot = isWorldModel ? 'Assistant' : 'Alice' ;
2023-06-15 16:12:13 +00:00
commonStore . conversationOrder . forEach ( ( uuid ) = > {
2023-06-28 12:48:22 +00:00
if ( uuid === welcomeUuid )
return ;
2023-06-15 16:12:13 +00:00
const messageItem = commonStore . conversation [ uuid ] ;
2023-06-28 12:48:22 +00:00
if ( messageItem . type !== MessageType . Error ) {
savedContent += ` ${ messageItem . sender === userName ? user : bot } : ${ messageItem . content } \ n \ n ` ;
}
2023-06-15 16:12:13 +00:00
} ) ;
2023-07-15 14:12:59 +00:00
OpenSaveFileDialog ( '*.txt' , 'conversation.txt' , savedContent ) . then ( ( path ) = > {
2023-06-15 16:12:13 +00:00
if ( path )
toastWithButton ( t ( 'Conversation Saved' ) , t ( 'Open' ) , ( ) = > {
OpenFileFolder ( path , false ) ;
} ) ;
} ) . catch ( e = > {
2023-06-23 05:55:45 +00:00
toast ( t ( 'Error' ) + ' - ' + ( e . message || e ) , { type : 'error' , autoClose : 2500 } ) ;
2023-06-15 16:12:13 +00:00
} ) ;
} } / >
2023-05-19 06:22:37 +00:00
< / div >
< / div >
2023-05-19 02:08:28 +00:00
) ;
2023-05-19 06:22:37 +00:00
} ) ;
2023-05-19 02:08:28 +00:00
export const Chat : FC = observer ( ( ) = > {
2023-05-05 15:23:34 +00:00
return (
2023-05-19 02:08:28 +00:00
< div className = "flex flex-col gap-1 p-2 h-full overflow-hidden" >
2023-05-24 13:27:23 +00:00
< WorkHeader / >
2023-05-21 05:48:11 +00:00
< ChatPanel / >
2023-05-19 02:08:28 +00:00
< / div >
2023-05-05 15:23:34 +00:00
) ;
2023-05-19 02:08:28 +00:00
} ) ;