feat: regenerate response button

This commit is contained in:
Jing Hua 2023-03-04 10:46:41 +08:00
parent 7f39bbf5b0
commit 710f4c3f65
4 changed files with 130 additions and 74 deletions

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { createRef, useState } from 'react';
import ScrollToBottom from 'react-scroll-to-bottom'; import ScrollToBottom from 'react-scroll-to-bottom';
import useStore from '@store/store'; import useStore from '@store/store';
@ -13,83 +13,14 @@ import { parseEventSource } from '@api/helper';
import RefreshIcon from '@icon/RefreshIcon'; import RefreshIcon from '@icon/RefreshIcon';
import { MessageInterface } from '@type/chat'; import { MessageInterface } from '@type/chat';
import useSubmit from '@hooks/useSubmit';
const ChatContent = () => { const ChatContent = () => {
const [ const [messages, inputRole] = useStore((state) => [
messages,
inputRole,
apiFree,
apiKey,
setMessages,
setGenerating,
generating,
] = useStore((state) => [
state.messages, state.messages,
state.inputRole, state.inputRole,
state.apiFree,
state.apiKey,
state.setMessages,
state.setGenerating,
state.generating,
]); ]);
const [error, setError] = useState<string>(''); const { handleSubmit, error } = useSubmit();
const handleSubmit = async () => {
if (generating) return;
const updatedMessages: MessageInterface[] = JSON.parse(
JSON.stringify(messages)
);
updatedMessages.push({ role: 'assistant', content: '' });
setMessages(updatedMessages);
setGenerating(true);
let stream;
try {
if (apiFree) {
stream = await getChatCompletionStreamFree(messages);
} else if (apiKey) {
stream = await getChatCompletionStreamCustom(apiKey, messages);
}
if (stream) {
const reader = stream.getReader();
let reading = true;
while (reading) {
const { done, value } = await reader.read();
const result = parseEventSource(new TextDecoder().decode(value));
if (result === '[DONE]' || done) {
reading = false;
} else {
const resultString = result.reduce((output: string, curr) => {
if (typeof curr === 'string') return output;
else {
const content = curr.choices[0].delta.content;
if (content) output += content;
return output;
}
}, '');
const updatedMessages: MessageInterface[] = JSON.parse(
JSON.stringify(useStore.getState().messages)
);
updatedMessages[updatedMessages.length - 1].content += resultString;
setMessages(updatedMessages);
}
}
}
} catch (e: unknown) {
const err = (e as Error).message;
console.log(err);
setError(err);
setTimeout(() => {
setError(''), 10000;
});
}
setGenerating(false);
};
return ( return (
<div className='flex-1 overflow-hidden'> <div className='flex-1 overflow-hidden'>
@ -122,7 +53,9 @@ const ChatContent = () => {
<div className='text-center mt-6 flex justify-center gap-2'> <div className='text-center mt-6 flex justify-center gap-2'>
<button <button
className='btn relative btn-primary mt-2' className='btn relative btn-primary mt-2'
onClick={handleSubmit} onClick={() => {
handleSubmit();
}}
> >
Submit Submit
</button> </button>

View file

@ -41,6 +41,7 @@ const Message = ({
sticky={sticky} sticky={sticky}
/> />
<MessageContent <MessageContent
role={role}
content={content} content={content}
messageIndex={messageIndex} messageIndex={messageIndex}
sticky={sticky} sticky={sticky}

View file

@ -9,15 +9,20 @@ import EditIcon2 from '@icon/EditIcon2';
import DeleteIcon from '@icon/DeleteIcon'; import DeleteIcon from '@icon/DeleteIcon';
import TickIcon from '@icon/TickIcon'; import TickIcon from '@icon/TickIcon';
import CrossIcon from '@icon/CrossIcon'; import CrossIcon from '@icon/CrossIcon';
import RefreshIcon from '@icon/RefreshIcon';
import DownChevronArrow from '@icon/DownChevronArrow'; import DownChevronArrow from '@icon/DownChevronArrow';
import { MessageInterface } from '@type/chat'; import { MessageInterface } from '@type/chat';
import useSubmit from '@hooks/useSubmit';
const MessageContent = ({ const MessageContent = ({
role,
content, content,
messageIndex, messageIndex,
sticky = false, sticky = false,
}: { }: {
role: string;
content: string; content: string;
messageIndex: number; messageIndex: number;
sticky?: boolean; sticky?: boolean;
@ -36,6 +41,7 @@ const MessageContent = ({
/> />
) : ( ) : (
<ContentView <ContentView
role={role}
content={content} content={content}
setIsEdit={setIsEdit} setIsEdit={setIsEdit}
messageIndex={messageIndex} messageIndex={messageIndex}
@ -46,14 +52,18 @@ const MessageContent = ({
}; };
const ContentView = ({ const ContentView = ({
role,
content, content,
setIsEdit, setIsEdit,
messageIndex, messageIndex,
}: { }: {
role: string;
content: string; content: string;
setIsEdit: React.Dispatch<React.SetStateAction<boolean>>; setIsEdit: React.Dispatch<React.SetStateAction<boolean>>;
messageIndex: number; messageIndex: number;
}) => { }) => {
const { handleSubmit, error } = useSubmit();
const [isDelete, setIsDelete] = useState<boolean>(false); const [isDelete, setIsDelete] = useState<boolean>(false);
const [copied, setCopied] = useState<boolean>(false); const [copied, setCopied] = useState<boolean>(false);
@ -150,12 +160,20 @@ const ContentView = ({
<div className='flex justify-end gap-2 w-full mt-2'> <div className='flex justify-end gap-2 w-full mt-2'>
{isDelete || ( {isDelete || (
<> <>
{role === 'assistant' && messageIndex === messages?.length - 1 && (
<RefreshButton
onClick={() => {
handleSubmit(true);
}}
/>
)}
{messageIndex !== 0 && ( {messageIndex !== 0 && (
<UpButton onClick={() => handleMove('up')} /> <UpButton onClick={() => handleMove('up')} />
)} )}
{messageIndex !== messages?.length - 1 && ( {messageIndex !== messages?.length - 1 && (
<DownButton onClick={() => handleMove('down')} /> <DownButton onClick={() => handleMove('down')} />
)} )}
<EditButton setIsEdit={setIsEdit} /> <EditButton setIsEdit={setIsEdit} />
<DeleteButton setIsDelete={setIsDelete} /> <DeleteButton setIsDelete={setIsDelete} />
</> </>
@ -236,6 +254,14 @@ const UpButton = ({
); );
}; };
const RefreshButton = ({
onClick,
}: {
onClick: React.MouseEventHandler<HTMLButtonElement>;
}) => {
return <MessageButton icon={<RefreshIcon />} onClick={onClick} />;
};
const EditView = ({ const EditView = ({
content, content,
setIsEdit, setIsEdit,

96
src/hooks/useSubmit.ts Normal file
View file

@ -0,0 +1,96 @@
import React, { useState } from 'react';
import useStore from '@store/store';
import { MessageInterface } from '@type/chat';
import { getChatCompletionStream as getChatCompletionStreamFree } from '@api/freeApi';
import { getChatCompletionStream as getChatCompletionStreamCustom } from '@api/customApi';
import { parseEventSource } from '@api/helper';
const useSubmit = () => {
const [error, setError] = useState<string>('');
const [messages, apiFree, apiKey, setMessages, setGenerating, generating] =
useStore((state) => [
state.messages,
state.apiFree,
state.apiKey,
state.setMessages,
state.setGenerating,
state.generating,
]);
const handleSubmit = async (refresh?: boolean) => {
if (generating) return;
const updatedMessages: MessageInterface[] = JSON.parse(
JSON.stringify(messages)
);
if (refresh) {
updatedMessages[updatedMessages.length - 1] = {
role: 'assistant',
content: '',
};
} else {
updatedMessages.push({ role: 'assistant', content: '' });
}
setMessages(updatedMessages);
setGenerating(true);
let stream;
try {
if (apiFree) {
if (refresh)
stream = await getChatCompletionStreamFree(
updatedMessages.slice(0, updatedMessages.length - 1)
);
else stream = await getChatCompletionStreamFree(messages);
} else if (apiKey) {
if (refresh)
stream = await getChatCompletionStreamCustom(
apiKey,
updatedMessages.slice(0, updatedMessages.length - 1)
);
else stream = await getChatCompletionStreamFree(messages);
}
if (stream) {
const reader = stream.getReader();
let reading = true;
while (reading) {
const { done, value } = await reader.read();
const result = parseEventSource(new TextDecoder().decode(value));
if (result === '[DONE]' || done) {
reading = false;
} else {
const resultString = result.reduce((output: string, curr) => {
if (typeof curr === 'string') return output;
else {
const content = curr.choices[0].delta.content;
if (content) output += content;
return output;
}
}, '');
const updatedMessages: MessageInterface[] = JSON.parse(
JSON.stringify(useStore.getState().messages)
);
updatedMessages[updatedMessages.length - 1].content += resultString;
setMessages(updatedMessages);
}
}
}
} catch (e: unknown) {
const err = (e as Error).message;
console.log(err);
setError(err);
setTimeout(() => {
setError(''), 10000;
});
}
setGenerating(false);
};
return { handleSubmit, error };
};
export default useSubmit;