chore: playground api request

This commit is contained in:
jialin
2024-06-12 18:13:23 +08:00
parent 405d95c81f
commit c9ea96e078
25 changed files with 594 additions and 147 deletions
+10
View File
@@ -0,0 +1,10 @@
import { request } from '@umijs/max';
export const CHAT_API = '/chat/completions';
export async function execChatCompletions(params: any) {
return request(`${CHAT_API}`, {
method: 'POST',
data: params
});
}
@@ -12,24 +12,30 @@ interface ChatFooterProps {
onClear: () => void;
onNewMessage: () => void;
onView: () => void;
disabled?: boolean;
feedback?: React.ReactNode;
}
const ChatFooter: React.FC<ChatFooterProps> = (props) => {
const { onSubmit, onClear, onNewMessage, onView, feedback } = props;
const { onSubmit, onClear, onNewMessage, onView, feedback, disabled } = props;
return (
<div className="chat-footer">
<Row style={{ width: '100%' }}>
<Col span={8}>
<Space size={20}>
<Button
disabled={disabled}
type="primary"
icon={<PlusOutlined />}
onClick={onNewMessage}
>
New Message
</Button>
<Button icon={<DeleteOutlined></DeleteOutlined>} onClick={onClear}>
<Button
icon={<DeleteOutlined></DeleteOutlined>}
onClick={onClear}
disabled={disabled}
>
Clear
</Button>
</Space>
@@ -37,10 +43,15 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
<Col span={8}>{feedback}</Col>
<Col span={8} style={{ textAlign: 'right' }}>
<Space size={20}>
<Button icon={<CodeOutlined></CodeOutlined>} onClick={onView}>
<Button
icon={<CodeOutlined></CodeOutlined>}
onClick={onView}
disabled={disabled}
>
View
</Button>
<Button
disabled={disabled}
type="primary"
icon={<SaveOutlined></SaveOutlined>}
onClick={onSubmit}
+81 -16
View File
@@ -1,8 +1,11 @@
import TransitionWrapper from '@/components/transition';
import { EyeInvisibleOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { Button, Input } from 'antd';
import { Button, Input, Spin } from 'antd';
import _ from 'lodash';
import { useRef, useState } from 'react';
import { execChatCompletions } from '../apis';
import { Roles } from '../config';
import '../style/ground-left.less';
import '../style/system-message-wrap.less';
import ChatFooter from './chat-footer';
@@ -10,37 +13,74 @@ import MessageItem from './message-item';
import ReferenceParams from './reference-params';
import ViewCodeModal from './view-code-modal';
const MessageList: React.FC = () => {
const [messageList, setMessageList] = useState<any[]>([
interface MessageProps {
parameters: any;
}
const MessageList: React.FC<MessageProps> = (props) => {
const { parameters } = props;
const [messageList, setMessageList] = useState<
{ role: string; content: string }[]
>([
{
role: 'User',
message: 'hello'
},
{
role: 'Assistant',
message: 'hello, nice to meet you!'
role: 'user',
content: ''
}
]);
const [systemMessage, setSystemMessage] = useState('');
const [show, setShow] = useState(false);
const [loading, setLoading] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1);
const [tokenResult, setTokenResult] = useState<any>(null);
const systemRef = useRef<any>(null);
const handleSystemMessageChange = (e: any) => {
setSystemMessage(e.target.value);
};
const handleNewMessage = () => {
console.log('new message');
messageList.push({
role: 'User',
message: 'hello'
role: 'user',
content: ''
});
setMessageList([...messageList]);
setActiveIndex(messageList.length - 1);
};
const submitMessage = async () => {
try {
setLoading(true);
const chatParams = {
messages: systemMessage
? [
{
role: 'system',
content: systemMessage
},
...messageList
]
: [...messageList],
...parameters
};
const data = await execChatCompletions(chatParams);
const assistant = _.get(data, ['choices', '0', 'message']);
setTokenResult({
...data.usage
});
setMessageList([
...messageList,
{
role: Roles.Assistant,
content: assistant.content
}
]);
setLoading(false);
} catch (error) {
setLoading(false);
}
};
const handleClear = () => {
console.log('clear');
setMessageList([]);
};
const handleView = () => {
@@ -49,6 +89,7 @@ const MessageList: React.FC = () => {
const handleSubmit = () => {
console.log('submit');
submitMessage();
};
const handleCloseViewCode = () => {
@@ -60,6 +101,15 @@ const MessageList: React.FC = () => {
setMessageList([...messageList]);
};
const handleUpdateMessage = (
index: number,
message: { role: string; content: string }
) => {
messageList[index] = message;
console.log('updatemessage========', index, message);
setMessageList([...messageList]);
};
const renderLabel = () => {
return (
<div className="system-message-wrap ">
@@ -94,13 +144,27 @@ const MessageList: React.FC = () => {
return (
<MessageItem
key={index}
role={item.role}
isFocus={index === activeIndex}
islast={index === messageList.length - 1}
loading={loading}
onDelete={() => handleDelete(index)}
message={item.message}
updateMessage={(message: { role: string; content: string }) =>
handleUpdateMessage(index, message)
}
message={item}
/>
);
})}
{loading && (
<Spin>
<MessageItem
message={{ role: Roles.Assistant, content: '' }}
isFocus={false}
onDelete={() => {}}
updateMessage={() => {}}
/>
</Spin>
)}
</div>
</PageContainer>
<div className="ground-left-footer">
@@ -109,7 +173,8 @@ const MessageList: React.FC = () => {
onNewMessage={handleNewMessage}
onSubmit={handleSubmit}
onView={handleView}
feedback={<ReferenceParams></ReferenceParams>}
disabled={loading}
feedback={<ReferenceParams usage={tokenResult}></ReferenceParams>}
></ChatFooter>
</div>
<ViewCodeModal
@@ -1,17 +1,26 @@
import { MinusCircleOutlined } from '@ant-design/icons';
import { Button, Input } from 'antd';
import { useEffect, useRef, useState } from 'react';
import _ from 'lodash';
import { memo, useEffect, useRef, useState } from 'react';
import { Roles } from '../config';
import '../style/message-item.less';
const MessageContent: React.FC<{
message: string;
role: string;
const MessageItem: React.FC<{
message: {
role: string;
content: string;
};
loading?: boolean;
islast?: boolean;
updateMessage: (message: { role: string; content: string }) => void;
isFocus: boolean;
onDelete: () => void;
}> = ({ message, role, isFocus, onDelete }) => {
const [roleType, setRoleType] = useState(role);
const [messageContent, setMessageContent] = useState(message);
}> = ({ message, isFocus, onDelete, updateMessage, loading, islast }) => {
const [roleType, setRoleType] = useState(message.role);
const [isTyping, setIsTyping] = useState(false);
const [messageContent, setMessageContent] = useState(message.content);
const isInitialRender = useRef(true);
const [isAnimating, setIsAnimating] = useState(false);
const inputRef = useRef<any>(null);
useEffect(() => {
@@ -20,17 +29,49 @@ const MessageContent: React.FC<{
}
}, [isFocus]);
useEffect(() => {
if (isTyping) return;
let index = 0;
const text = message.content;
if (!text.length) {
return;
}
setMessageContent('');
setIsAnimating(true);
const intervalId = setInterval(() => {
setMessageContent((prev) => prev + text[index]);
index += 1;
if (index === text.length) {
setIsAnimating(false);
clearInterval(intervalId);
}
}, 20);
return () => clearInterval(intervalId);
}, [message.content, isTyping]);
useEffect(() => {
if (!isAnimating && !isInitialRender.current) {
updateMessage({ role: roleType, content: messageContent });
} else {
isInitialRender.current = false;
}
}, [roleType, messageContent]);
const handleMessageChange = (e: any) => {
setIsTyping(true);
setMessageContent(e.target.value);
};
const handleBlur = () => {
setIsTyping(true);
};
const handleRoleChange = () => {
if (roleType === Roles.User) {
setRoleType(Roles.Assistant);
}
if (roleType === Roles.Assistant) {
setRoleType(Roles.User);
}
setRoleType((prevRoleType) => {
const newRoleType =
prevRoleType === Roles.User ? Roles.Assistant : Roles.User;
return newRoleType;
});
};
const handleDelete = () => {
@@ -40,7 +81,7 @@ const MessageContent: React.FC<{
<div className="message-item">
<div className="role-type">
<Button onClick={handleRoleChange} type="text">
{roleType}
{_.upperFirst(roleType)}
</Button>
</div>
<div className="message-content-input">
@@ -51,6 +92,7 @@ const MessageContent: React.FC<{
autoSize={true}
variant="filled"
onChange={handleMessageChange}
onBlur={handleBlur}
></Input.TextArea>
</div>
<div className="delete-btn">
@@ -66,4 +108,4 @@ const MessageContent: React.FC<{
);
};
export default MessageContent;
export default memo(MessageItem);
@@ -2,39 +2,85 @@ import FieldWrapper from '@/components/seal-form/field-wrapper';
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import { INPUT_WIDTH } from '@/constants';
import { queryModelsList } from '@/pages/llmodels/apis';
import { Form, Slider } from 'antd';
import { useState } from 'react';
import _ from 'lodash';
import { useEffect, useState } from 'react';
type ParamsSettingsFormProps = {
seed?: number;
stop?: number;
temperature?: number;
top_p?: number;
model?: string;
max_tokens?: number;
};
type ParamsSettingsProps = {
seed?: number;
stopSequence?: number;
temperature?: number;
topP?: number;
model?: string;
maxTokens?: number;
onClose?: () => void;
selectedModel?: string;
params?: ParamsSettingsFormProps;
setParams: (params: any) => void;
};
const dataList = [
{ value: 'llama3:latest', label: 'llama3:latest' },
{ value: 'wangfuyun/AnimateLCM', label: 'wangfuyun/AnimateLCM' },
{ value: 'Revanthraja/Text_to_Vision', label: 'Revanthraja/Text_to_Vision' }
];
// const dataList = [
// { value: 'llama3:latest', label: 'llama3:latest' },
// { value: 'wangfuyun/AnimateLCM', label: 'wangfuyun/AnimateLCM' },
// { value: 'Revanthraja/Text_to_Vision', label: 'Revanthraja/Text_to_Vision' }
// ];
const ParamsSettings: React.FC<{ onClose: () => void }> = ({ onClose }) => {
const [ModelList, setModelList] = useState(dataList);
const ParamsSettings: React.FC<ParamsSettingsProps> = ({
onClose,
selectedModel,
setParams
}) => {
const [ModelList, setModelList] = useState([]);
const initialValues = {
seed: 1,
stopSequence: 1,
seed: null,
stop: null,
temperature: 1,
topK: 1,
topP: 1,
repeatPenalty: 1,
repeatLastN: 1,
tfsZ: 1,
contextLength: 256,
maxTokens: 256
top_p: 1,
max_tokens: 1024
};
const [form] = Form.useForm();
useEffect(() => {
const getModelList = async () => {
try {
const params = {
page: 1,
perPage: 100
};
const res = await queryModelsList(params);
const list = _.map(res.items || [], (item: any) => {
return {
value: item.name,
label: item.name
};
});
setModelList(list);
form.setFieldsValue({
model: selectedModel || _.get(list, '[0].value'),
...initialValues
});
setParams({
model: selectedModel || _.get(list, '[0].value'),
...initialValues
});
} catch (error) {
setModelList([]);
form.setFieldsValue({
model: selectedModel || '',
...initialValues
});
setParams({
model: selectedModel || '',
...initialValues
});
}
};
getModelList();
}, []);
const handleOnFinish = (values: any) => {
console.log('handleOnFinish', values);
};
@@ -45,61 +91,79 @@ const ParamsSettings: React.FC<{ onClose: () => void }> = ({ onClose }) => {
const handleCancel = () => {
form.resetFields();
onClose();
onClose?.();
};
const handleValuesChange = (changedValues: any, allValues: any) => {
console.log('handleValuesChange', changedValues, allValues);
setParams?.(allValues);
};
return (
<Form
name="modelparams"
form={form}
onValuesChange={handleValuesChange}
onFinish={handleOnFinish}
onFinishFailed={handleOnFinishFailed}
>
<div>
<h3 className="m-b-20 m-l-10">Model</h3>
<Form.Item<ParamsSettingsProps>
<Form.Item<ParamsSettingsFormProps>
name="model"
rules={[{ required: true }]}
>
<SealSelect options={ModelList} label="Model"></SealSelect>
</Form.Item>
<h3 className="m-b-20 m-l-10">Parameters</h3>
<Form.Item<ParamsSettingsProps>
<Form.Item<ParamsSettingsFormProps>
name="temperature"
rules={[{ required: true }]}
>
<FieldWrapper label="Temperature">
<Slider defaultValue={50}></Slider>
<Slider
defaultValue={1}
max={2}
step={0.1}
style={{ marginBottom: 0 }}
tooltip={{ open: true }}
></Slider>
</FieldWrapper>
</Form.Item>
<Form.Item<ParamsSettingsProps>
name="maxTokens"
<Form.Item<ParamsSettingsFormProps>
name="max_tokens"
rules={[{ required: true }]}
>
<SealInput.Input
<SealInput.Number
label="Max Tokens"
style={{ width: INPUT_WIDTH.mini }}
></SealInput.Input>
></SealInput.Number>
</Form.Item>
<Form.Item<ParamsSettingsProps>
name="topP"
<Form.Item<ParamsSettingsFormProps>
name="top_p"
rules={[{ required: true }]}
>
<FieldWrapper label="Top P">
<Slider defaultValue={50}></Slider>
<Slider
defaultValue={1}
max={1}
step={0.1}
style={{ marginBottom: 0 }}
tooltip={{ open: true }}
></Slider>
</FieldWrapper>
</Form.Item>
<Form.Item<ParamsSettingsProps>
<Form.Item<ParamsSettingsFormProps>
name="seed"
rules={[{ required: true }]}
>
<SealInput.Input
<SealInput.Number
label="Seed"
style={{ width: INPUT_WIDTH.mini }}
></SealInput.Input>
></SealInput.Number>
</Form.Item>
<Form.Item<ParamsSettingsProps>
name="stopSequence"
<Form.Item<ParamsSettingsFormProps>
name="stop"
rules={[{ required: true }]}
>
<SealInput.Input
@@ -1,11 +1,31 @@
import { Space, Tooltip } from 'antd';
import '../style/reference-params.less';
const ReferenceParams = () => {
interface ReferenceParamsProps {
usage: {
completion_tokens: number;
prompt_tokens: number;
total_tokens: number;
};
}
const ReferenceParams = (props: ReferenceParamsProps) => {
const { usage } = props;
if (!usage) {
return null;
}
return (
<div className="reference-params">
<span>Inference: 597 ms</span>
<span style={{ padding: '10px' }}></span>
<span>Tokens/s: 561</span>
<Tooltip
title={
<Space>
<span>Completion: {usage.completion_tokens}</span>
<span>Prompt: {usage.prompt_tokens}</span>
</Space>
}
>
<span>Token Usage: {usage.total_tokens}</span>
</Tooltip>
</div>
);
};
+2 -2
View File
@@ -1,6 +1,6 @@
export const Roles = {
User: 'User',
Assistant: 'Assistant'
User: 'user',
Assistant: 'assistant'
};
export const playGroundRoles = [
{
+12 -14
View File
@@ -1,24 +1,22 @@
import { useSearchParams } from '@umijs/max';
import { Divider } from 'antd';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import GroundLeft from './components/ground-left';
import ParamsSettings from './components/params-settings';
import './style/play-ground.less';
const Playground: React.FC = () => {
const [messageList, setMessageList] = useState<any[]>([]);
const [searchParams] = useSearchParams();
const [selectedModel, setSelectedModel] = useState('llama3:latest');
const [showPopover, setShowPopover] = useState(false);
const selectModel = searchParams.get('model') || '';
const [params, setParams] = useState({});
console.log('query======', searchParams, selectModel);
const handleSelectChange = (value: string) => {
setSelectedModel(value);
};
const getMessageList = () => {
// fetch message list from server
console.log('getModelList');
setMessageList(['1']);
};
const handleTogglePopover = () => {
setShowPopover(!showPopover);
};
@@ -27,20 +25,20 @@ const Playground: React.FC = () => {
setShowPopover(false);
};
useEffect(() => {
getMessageList();
}, [selectedModel]);
return (
<div className="play-ground">
<div className="chat">
<GroundLeft></GroundLeft>
<GroundLeft parameters={params}></GroundLeft>
</div>
<div className="divider-line">
<Divider type="vertical" />
</div>
<div className="params">
<ParamsSettings onClose={handleClosePopover} />
<ParamsSettings
onClose={handleClosePopover}
setParams={setParams}
selectedModel={selectModel}
/>
</div>
</div>
);