improve chat page

This commit is contained in:
josc146 2023-05-19 20:10:30 +08:00
parent 7ba90ae7af
commit 752b72e2c9
3 changed files with 151 additions and 111 deletions

View File

@ -10,9 +10,10 @@ def set_torch():
if torch_path in paths: if torch_path in paths:
print("torch already set") print("torch already set")
else: else:
print("run:")
os.environ["PATH"] = paths + os.pathsep + torch_path + os.pathsep os.environ["PATH"] = paths + os.pathsep + torch_path + os.pathsep
print(f"set Path={paths + os.pathsep + torch_path + os.pathsep}") print("torch set")
# print("run:")
# print(f"set Path={paths + os.pathsep + torch_path + os.pathsep}")
else: else:
print("torch not found") print("torch not found")

View File

@ -6,6 +6,8 @@ export const ToolTipButton: FC<{
desc: string, desc: string,
icon?: ReactElement, icon?: ReactElement,
size?: 'small' | 'medium' | 'large', size?: 'small' | 'medium' | 'large',
shape?: 'rounded' | 'circular' | 'square';
appearance?: 'secondary' | 'primary' | 'outline' | 'subtle' | 'transparent';
disabled?: boolean, disabled?: boolean,
onClick?: MouseEventHandler onClick?: MouseEventHandler
}> = ({ }> = ({
@ -13,12 +15,15 @@ export const ToolTipButton: FC<{
desc, desc,
icon, icon,
size, size,
shape,
appearance,
disabled, disabled,
onClick onClick
}) => { }) => {
return ( return (
<Tooltip content={desc} showDelay={0} hideDelay={0} relationship="label"> <Tooltip content={desc} showDelay={0} hideDelay={0} relationship="label">
<Button disabled={disabled} icon={icon} onClick={onClick} size={size}>{text}</Button> <Button disabled={disabled} icon={icon} onClick={onClick} size={size} shape={shape}
appearance={appearance}>{text}</Button>
</Tooltip> </Tooltip>
); );
}; };

View File

@ -12,6 +12,8 @@ import {fetchEventSource} from '@microsoft/fetch-event-source';
import {ConversationPair, getConversationPairs, Record} from '../utils/get-conversation-pairs'; import {ConversationPair, getConversationPairs, Record} from '../utils/get-conversation-pairs';
import logo from '../../../build/appicon.png'; import logo from '../../../build/appicon.png';
import MarkdownRender from '../components/MarkdownRender'; import MarkdownRender from '../components/MarkdownRender';
import {ToolTipButton} from '../components/ToolTipButton';
import {ArrowCircleUp28Regular, Delete28Regular, RecordStop28Regular} from '@fluentui/react-icons';
const userName = 'M E'; const userName = 'M E';
const botName = 'A I'; const botName = 'A I';
@ -48,6 +50,16 @@ const ChatPanel: FC = observer(() => {
const bodyRef = useRef<HTMLDivElement>(null); const bodyRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null); const inputRef = useRef<HTMLTextAreaElement>(null);
const port = commonStore.getCurrentModelConfig().apiParameters.apiPort; const port = commonStore.getCurrentModelConfig().apiParameters.apiPort;
const sseControllerRef = useRef<AbortController | null>(null);
let lastMessageId: string;
let generating: boolean = false;
if (conversationsOrder.length > 0) {
lastMessageId = conversationsOrder[conversationsOrder.length - 1];
const lastMessage = conversations[lastMessageId];
if (lastMessage.sender === botName)
generating = !lastMessage.done;
}
useEffect(() => { useEffect(() => {
if (inputRef.current) if (inputRef.current)
@ -59,103 +71,111 @@ const ChatPanel: FC = observer(() => {
bodyRef.current.scrollTop = bodyRef.current.scrollHeight; bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
}; };
const handleSubmit = (e: React.ChangeEvent<HTMLFormElement>) => { const handleKeyDownOrClick = (e: any) => {
e.preventDefault(); e.stopPropagation();
if (message !== '') { if (e.type === 'click' || (e.keyCode === 13 && !e.shiftKey)) {
e.preventDefault();
if (!message) return;
onSubmit(message);
setMessage(''); setMessage('');
const newId = uuid(); }
conversations[newId] = { };
sender: userName,
type: MessageType.Normal,
color: 'brand',
time: new Date().toISOString(),
content: message,
side: 'right',
done: true
};
setConversations(conversations);
conversationsOrder.push(newId);
setConversationsOrder(conversationsOrder);
const records: Record[] = []; const onSubmit = (message: string) => {
conversationsOrder.forEach((uuid, index) => { const newId = uuid();
const conversation = conversations[uuid]; conversations[newId] = {
if (conversation.done && conversation.type === MessageType.Normal && conversation.sender === botName) { sender: userName,
if (index > 0) { type: MessageType.Normal,
const questionId = conversationsOrder[index - 1]; color: 'brand',
const question = conversations[questionId]; time: new Date().toISOString(),
if (question.done && question.type === MessageType.Normal && question.sender === userName) { content: message,
records.push({question: question.content, answer: conversation.content}); side: 'right',
} done: true
};
setConversations(conversations);
conversationsOrder.push(newId);
setConversationsOrder(conversationsOrder);
const records: Record[] = [];
conversationsOrder.forEach((uuid, index) => {
const conversation = conversations[uuid];
if (conversation.done && conversation.type === MessageType.Normal && conversation.sender === botName) {
if (index > 0) {
const questionId = conversationsOrder[index - 1];
const question = conversations[questionId];
if (question.done && question.type === MessageType.Normal && question.sender === userName) {
records.push({question: question.content, answer: conversation.content});
} }
} }
}); }
const messages = getConversationPairs(records, false); });
(messages as ConversationPair[]).push({role: 'user', content: message}); const messages = getConversationPairs(records, false);
(messages as ConversationPair[]).push({role: 'user', content: message});
const answerId = uuid(); const answerId = uuid();
conversations[answerId] = { conversations[answerId] = {
sender: botName, sender: botName,
type: MessageType.Normal, type: MessageType.Normal,
color: 'colorful', color: 'colorful',
avatarImg: logo, avatarImg: logo,
time: new Date().toISOString(), time: new Date().toISOString(),
content: '', content: '',
side: 'left', side: 'left',
done: false done: false
}; };
setConversations(conversations); setConversations(conversations);
conversationsOrder.push(answerId); conversationsOrder.push(answerId);
setConversationsOrder(conversationsOrder); setConversationsOrder(conversationsOrder);
setTimeout(scrollToBottom); setTimeout(scrollToBottom);
let answer = ''; let answer = '';
fetchEventSource(`http://127.0.0.1:${port}/chat/completions`, // https://api.openai.com/v1/chat/completions || http://127.0.0.1:${port}/chat/completions sseControllerRef.current = new AbortController();
{ fetchEventSource(`http://127.0.0.1:${port}/chat/completions`, // https://api.openai.com/v1/chat/completions || http://127.0.0.1:${port}/chat/completions
method: 'POST', {
headers: { method: 'POST',
'Content-Type': 'application/json', headers: {
Authorization: `Bearer sk-` 'Content-Type': 'application/json',
}, Authorization: `Bearer sk-`
body: JSON.stringify({ },
messages, body: JSON.stringify({
stream: true, messages,
model: 'gpt-3.5-turbo' stream: true,
}), model: 'gpt-3.5-turbo'
onmessage(e) { }),
console.log('sse message', e); signal: sseControllerRef.current?.signal,
scrollToBottom(); onmessage(e) {
if (e.data === '[DONE]') { console.log('sse message', e);
conversations[answerId].done = true; scrollToBottom();
setConversations(conversations); if (e.data === '[DONE]') {
setConversationsOrder([...conversationsOrder]);
return;
}
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 || '';
conversations[answerId].content = answer;
setConversations(conversations);
setConversationsOrder([...conversationsOrder]);
}
},
onclose() {
console.log('Connection closed');
},
onerror(err) {
conversations[answerId].type = MessageType.Error;
conversations[answerId].done = true; conversations[answerId].done = true;
setConversations(conversations); setConversations(conversations);
setConversationsOrder([...conversationsOrder]); setConversationsOrder([...conversationsOrder]);
throw err; return;
} }
}); 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 || '';
conversations[answerId].content = answer;
setConversations(conversations);
setConversationsOrder([...conversationsOrder]);
}
},
onclose() {
console.log('Connection closed');
},
onerror(err) {
conversations[answerId].type = MessageType.Error;
conversations[answerId].done = true;
setConversations(conversations);
setConversationsOrder([...conversationsOrder]);
throw err;
}
});
}; };
return ( return (
@ -192,26 +212,40 @@ const ChatPanel: FC = observer(() => {
</div>; </div>;
})} })}
</div> </div>
<div className="flex items-end"> <div className="flex items-end gap-2">
<button className="bg-blue-500 text-white rounded-lg py-2 px-4 mr-2" onClick={() => { <ToolTipButton desc={t('Clear')}
setConversations({}); icon={<Delete28Regular/>}
setConversationsOrder([]); size="large" shape="circular" appearance="subtle"
}}> onClick={(e) => {
{t('Clear')} setConversations({});
</button> setConversationsOrder([]);
<form onSubmit={handleSubmit} className="flex items-end grow gap-2"> }}
<Textarea />
ref={inputRef} <Textarea
className="grow" ref={inputRef}
resize="vertical" className="grow"
placeholder={t('Type your message here')!} resize="vertical"
value={message} placeholder={t('Type your message here')!}
onChange={(e) => setMessage(e.target.value)} value={message}
/> onChange={(e) => setMessage(e.target.value)}
<button type="submit" className="bg-blue-500 text-white rounded-lg py-2 px-4"> onKeyDown={handleKeyDownOrClick}
{t('Send')} />
</button> <ToolTipButton desc={generating ? t('Stop') : t('Send')}
</form> icon={generating ? <RecordStop28Regular/> : <ArrowCircleUp28Regular/>}
size="large" shape="circular" appearance="subtle"
onClick={(e) => {
if (generating) {
sseControllerRef.current?.abort();
if (lastMessageId) {
conversations[lastMessageId].type = MessageType.Error;
conversations[lastMessageId].done = true;
setConversations(conversations);
setConversationsOrder([...conversationsOrder]);
}
} else {
handleKeyDownOrClick(e);
}
}}/>
</div> </div>
</div> </div>
); );