chat page
This commit is contained in:
parent
bb8d121991
commit
1105fbf6ec
@ -56,7 +56,7 @@ async def completions(body: CompletionBody, request: Request):
|
|||||||
set_rwkv_config(model, body)
|
set_rwkv_config(model, body)
|
||||||
if body.stream:
|
if body.stream:
|
||||||
for response, delta in rwkv_generate(
|
for response, delta in rwkv_generate(
|
||||||
model, completion_text, stop="Bob:"
|
model, completion_text, stop="\n\nBob"
|
||||||
):
|
):
|
||||||
if await request.is_disconnected():
|
if await request.is_disconnected():
|
||||||
break
|
break
|
||||||
@ -90,7 +90,7 @@ async def completions(body: CompletionBody, request: Request):
|
|||||||
else:
|
else:
|
||||||
response = None
|
response = None
|
||||||
for response, delta in rwkv_generate(
|
for response, delta in rwkv_generate(
|
||||||
model, completion_text, stop="Bob:"
|
model, completion_text, stop="\n\nBob"
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
yield {
|
yield {
|
||||||
|
30
frontend/package-lock.json
generated
30
frontend/package-lock.json
generated
@ -10,6 +10,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluentui/react-components": "^9.20.0",
|
"@fluentui/react-components": "^9.20.0",
|
||||||
"@fluentui/react-icons": "^2.0.201",
|
"@fluentui/react-icons": "^2.0.201",
|
||||||
|
"@microsoft/fetch-event-source": "^2.0.1",
|
||||||
|
"classnames": "^2.3.2",
|
||||||
"i18next": "^22.4.15",
|
"i18next": "^22.4.15",
|
||||||
"mobx": "^6.9.0",
|
"mobx": "^6.9.0",
|
||||||
"mobx-react-lite": "^3.4.3",
|
"mobx-react-lite": "^3.4.3",
|
||||||
@ -19,11 +21,13 @@
|
|||||||
"react-router": "^6.11.1",
|
"react-router": "^6.11.1",
|
||||||
"react-router-dom": "^6.11.1",
|
"react-router-dom": "^6.11.1",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"usehooks-ts": "^2.9.1"
|
"usehooks-ts": "^2.9.1",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.6",
|
"@types/react": "^18.2.6",
|
||||||
"@types/react-dom": "^18.2.4",
|
"@types/react-dom": "^18.2.4",
|
||||||
|
"@types/uuid": "^9.0.1",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.23",
|
||||||
@ -1885,6 +1889,11 @@
|
|||||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@microsoft/fetch-event-source": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA=="
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -1964,6 +1973,12 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.3.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.3.tgz",
|
||||||
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
|
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/uuid": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/uuid/-/uuid-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.0.0.tgz",
|
||||||
@ -2168,6 +2183,11 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/classnames": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
|
||||||
|
},
|
||||||
"node_modules/cliui": {
|
"node_modules/cliui": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz",
|
||||||
@ -3505,6 +3525,14 @@
|
|||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "9.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.0.tgz",
|
||||||
|
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.6",
|
||||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.3.6.tgz",
|
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.3.6.tgz",
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluentui/react-components": "^9.20.0",
|
"@fluentui/react-components": "^9.20.0",
|
||||||
"@fluentui/react-icons": "^2.0.201",
|
"@fluentui/react-icons": "^2.0.201",
|
||||||
|
"@microsoft/fetch-event-source": "^2.0.1",
|
||||||
|
"classnames": "^2.3.2",
|
||||||
"i18next": "^22.4.15",
|
"i18next": "^22.4.15",
|
||||||
"mobx": "^6.9.0",
|
"mobx": "^6.9.0",
|
||||||
"mobx-react-lite": "^3.4.3",
|
"mobx-react-lite": "^3.4.3",
|
||||||
@ -20,11 +22,13 @@
|
|||||||
"react-router": "^6.11.1",
|
"react-router": "^6.11.1",
|
||||||
"react-router-dom": "^6.11.1",
|
"react-router-dom": "^6.11.1",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"usehooks-ts": "^2.9.1"
|
"usehooks-ts": "^2.9.1",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.6",
|
"@types/react": "^18.2.6",
|
||||||
"@types/react-dom": "^18.2.4",
|
"@types/react-dom": "^18.2.4",
|
||||||
|
"@types/uuid": "^9.0.1",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.23",
|
||||||
|
@ -63,5 +63,8 @@
|
|||||||
"Convert Success": "转换成功",
|
"Convert Success": "转换成功",
|
||||||
"Convert Failed": "转换失败",
|
"Convert Failed": "转换失败",
|
||||||
"Model Not Found": "模型不存在",
|
"Model Not Found": "模型不存在",
|
||||||
"Model Status": "模型状态"
|
"Model Status": "模型状态",
|
||||||
|
"Clear": "清除",
|
||||||
|
"Send": "发送",
|
||||||
|
"Type your message here": "在此输入消息"
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import {FC, ReactElement} from 'react';
|
import {FC, ReactElement} from 'react';
|
||||||
import {Label, Tooltip} from '@fluentui/react-components';
|
import {Label, Tooltip} from '@fluentui/react-components';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
export const Labeled: FC<{
|
export const Labeled: FC<{
|
||||||
label: string; desc?: string, content: ReactElement, flex?: boolean, spaceBetween?: boolean
|
label: string; desc?: string, content: ReactElement, flex?: boolean, spaceBetween?: boolean
|
||||||
@ -11,10 +12,10 @@ export const Labeled: FC<{
|
|||||||
spaceBetween
|
spaceBetween
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={
|
<div className={classnames(
|
||||||
(flex ? 'flex' : 'grid grid-cols-2') + ' ' +
|
'items-center',
|
||||||
(spaceBetween ? 'justify-between' : '') + ' ' +
|
flex ? 'flex' : 'grid grid-cols-2',
|
||||||
'items-center'
|
spaceBetween && 'justify-between')
|
||||||
}>
|
}>
|
||||||
{desc ?
|
{desc ?
|
||||||
<Tooltip content={desc} showDelay={0} hideDelay={0} relationship="description">
|
<Tooltip content={desc} showDelay={0} hideDelay={0} relationship="description">
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, * as React_2 from 'react';
|
import React, {CSSProperties, FC} from 'react';
|
||||||
import {CSSProperties, FC} from 'react';
|
|
||||||
import {Input} from '@fluentui/react-components';
|
import {Input} from '@fluentui/react-components';
|
||||||
import {SliderOnChangeData} from '@fluentui/react-slider';
|
import {SliderOnChangeData} from '@fluentui/react-slider';
|
||||||
|
|
||||||
@ -8,7 +7,7 @@ export const NumberInput: FC<{
|
|||||||
min: number,
|
min: number,
|
||||||
max: number,
|
max: number,
|
||||||
step?: number,
|
step?: number,
|
||||||
onChange?: (ev: React_2.ChangeEvent<HTMLInputElement>, data: SliderOnChangeData) => void
|
onChange?: (ev: React.ChangeEvent<HTMLInputElement>, data: SliderOnChangeData) => void
|
||||||
style?: CSSProperties
|
style?: CSSProperties
|
||||||
}> = ({value, min, max, step, onChange, style}) => {
|
}> = ({value, min, max, step, onChange, style}) => {
|
||||||
return (
|
return (
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, * as React_2 from 'react';
|
import React, {FC, useEffect, useRef} from 'react';
|
||||||
import {FC, useEffect, useRef} from 'react';
|
|
||||||
import {Slider, Text} from '@fluentui/react-components';
|
import {Slider, Text} from '@fluentui/react-components';
|
||||||
import {SliderOnChangeData} from '@fluentui/react-slider';
|
import {SliderOnChangeData} from '@fluentui/react-slider';
|
||||||
import {NumberInput} from './NumberInput';
|
import {NumberInput} from './NumberInput';
|
||||||
@ -10,7 +9,7 @@ export const ValuedSlider: FC<{
|
|||||||
max: number,
|
max: number,
|
||||||
step?: number,
|
step?: number,
|
||||||
input?: boolean
|
input?: boolean
|
||||||
onChange?: (ev: React_2.ChangeEvent<HTMLInputElement>, data: SliderOnChangeData) => void
|
onChange?: (ev: React.ChangeEvent<HTMLInputElement>, data: SliderOnChangeData) => void
|
||||||
}> = ({value, min, max, step, input, onChange}) => {
|
}> = ({value, min, max, step, input, onChange}) => {
|
||||||
const sliderRef = useRef<HTMLInputElement>(null);
|
const sliderRef = useRef<HTMLInputElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,17 +1,213 @@
|
|||||||
import React, {FC} from 'react';
|
import React, {FC, useRef, useState} from 'react';
|
||||||
import {useTranslation} from 'react-i18next';
|
import {useTranslation} from 'react-i18next';
|
||||||
import {RunButton} from '../components/RunButton';
|
import {RunButton} from '../components/RunButton';
|
||||||
import {Divider, PresenceBadge, Text} from '@fluentui/react-components';
|
import {Avatar, Divider, Input, PresenceBadge, Text} from '@fluentui/react-components';
|
||||||
import commonStore, {ModelStatus} from '../stores/commonStore';
|
import commonStore, {ModelStatus} from '../stores/commonStore';
|
||||||
import {observer} from 'mobx-react-lite';
|
import {observer} from 'mobx-react-lite';
|
||||||
import {PresenceBadgeStatus} from '@fluentui/react-badge';
|
import {PresenceBadgeStatus} from '@fluentui/react-badge';
|
||||||
import {ConfigSelector} from '../components/ConfigSelector';
|
import {ConfigSelector} from '../components/ConfigSelector';
|
||||||
|
import {v4 as uuid} from 'uuid';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import {fetchEventSource} from '@microsoft/fetch-event-source';
|
||||||
|
import {ConversationPair, getConversationPairs, Record} from '../utils/get-conversation-pairs';
|
||||||
|
import logo from '../../../build/appicon.png';
|
||||||
|
|
||||||
|
const userName = 'M E';
|
||||||
|
const botName = 'A I';
|
||||||
|
|
||||||
|
enum MessageType {
|
||||||
|
Normal,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Side = 'left' | 'right'
|
||||||
|
|
||||||
|
type Color = 'neutral' | 'brand' | 'colorful'
|
||||||
|
|
||||||
|
type MessageItem = {
|
||||||
|
sender: string,
|
||||||
|
type: MessageType,
|
||||||
|
color: Color,
|
||||||
|
avatarImg?: string,
|
||||||
|
time: string,
|
||||||
|
content: string,
|
||||||
|
side: Side,
|
||||||
|
done: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type Conversations = {
|
||||||
|
[uuid: string]: MessageItem
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChatPanel: FC = observer(() => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
const [conversations, setConversations] = useState<Conversations>({});
|
||||||
|
const [conversationsOrder, setConversationsOrder] = useState<string[]>([]);
|
||||||
|
const bodyRef = useRef<HTMLDivElement>(null);
|
||||||
|
const port = commonStore.getCurrentModelConfig().apiParameters.apiPort;
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
if (bodyRef.current)
|
||||||
|
bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.ChangeEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (message !== '') {
|
||||||
|
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[] = [];
|
||||||
|
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 answerId = uuid();
|
||||||
|
conversations[answerId] = {
|
||||||
|
sender: botName,
|
||||||
|
type: MessageType.Normal,
|
||||||
|
color: 'colorful',
|
||||||
|
avatarImg: logo,
|
||||||
|
time: new Date().toISOString(),
|
||||||
|
content: '',
|
||||||
|
side: 'left',
|
||||||
|
done: false
|
||||||
|
};
|
||||||
|
setConversations(conversations);
|
||||||
|
conversationsOrder.push(answerId);
|
||||||
|
setConversationsOrder(conversationsOrder);
|
||||||
|
setTimeout(scrollToBottom);
|
||||||
|
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
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer sk-`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
messages,
|
||||||
|
stream: true,
|
||||||
|
model: 'gpt-3.5-turbo'
|
||||||
|
}),
|
||||||
|
onmessage(e) {
|
||||||
|
console.log('sse message', e);
|
||||||
|
scrollToBottom();
|
||||||
|
if (e.data === '[DONE]') {
|
||||||
|
conversations[answerId].done = true;
|
||||||
|
setConversations(conversations);
|
||||||
|
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;
|
||||||
|
setConversations(conversations);
|
||||||
|
setConversationsOrder([...conversationsOrder]);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const ChatPanel: FC = () => {
|
|
||||||
return (
|
return (
|
||||||
<div></div>
|
<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">
|
||||||
|
{conversationsOrder.map((uuid, index) => {
|
||||||
|
const conversation = conversations[uuid];
|
||||||
|
return <div
|
||||||
|
key={uuid}
|
||||||
|
className={classnames(
|
||||||
|
'flex gap-2 mb-2',
|
||||||
|
conversation.side === 'left' ? 'flex-row' : 'flex-row-reverse'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
color={conversation.color}
|
||||||
|
name={conversation.sender}
|
||||||
|
image={conversation.avatarImg ? {src: conversation.avatarImg} : undefined}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'p-2 rounded-lg',
|
||||||
|
conversation.side === 'left' ? 'bg-gray-200' : 'bg-blue-500',
|
||||||
|
conversation.side === 'left' ? 'text-gray-600' : 'text-white'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{conversation.content}
|
||||||
|
</div>
|
||||||
|
{(conversation.type === MessageType.Error || !conversation.done) &&
|
||||||
|
<PresenceBadge status={
|
||||||
|
conversation.type === MessageType.Error ? 'busy' : 'away'
|
||||||
|
}/>
|
||||||
|
}
|
||||||
|
</div>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<button className="bg-blue-500 text-white rounded-lg py-2 px-4 mr-2" onClick={() => {
|
||||||
|
setConversations({});
|
||||||
|
setConversationsOrder([]);
|
||||||
|
}}>
|
||||||
|
{t('Clear')}
|
||||||
|
</button>
|
||||||
|
<form onSubmit={handleSubmit} className="flex grow">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
className="grow mr-2"
|
||||||
|
placeholder={t('Type your message here')!}
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button type="submit" className="bg-blue-500 text-white rounded-lg py-2 px-4">
|
||||||
|
{t('Send')}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const statusText = {
|
const statusText = {
|
||||||
[ModelStatus.Offline]: 'Offline',
|
[ModelStatus.Offline]: 'Offline',
|
||||||
|
27
frontend/src/utils/get-conversation-pairs.ts
Normal file
27
frontend/src/utils/get-conversation-pairs.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export type Record = {
|
||||||
|
question: string;
|
||||||
|
answer: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConversationPair = {
|
||||||
|
role: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConversationPairs(records: Record[], isCompletion: boolean): string | ConversationPair[] {
|
||||||
|
let pairs;
|
||||||
|
if (isCompletion) {
|
||||||
|
pairs = '';
|
||||||
|
for (const record of records) {
|
||||||
|
pairs += 'Human: ' + record.question + '\nAI: ' + record.answer + '\n';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pairs = [];
|
||||||
|
for (const record of records) {
|
||||||
|
pairs.push({role: 'user', content: record.question});
|
||||||
|
pairs.push({role: 'assistant', content: record.answer});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pairs;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user