chore: playground layout
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
CodeOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
SaveOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Col, Row, Space } from 'antd';
|
||||
import '../style/chat-footer.less';
|
||||
|
||||
interface ChatFooterProps {
|
||||
onSubmit: () => void;
|
||||
onClear: () => void;
|
||||
onNewMessage: () => void;
|
||||
onView: () => void;
|
||||
feedback?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ChatFooter: React.FC<ChatFooterProps> = (props) => {
|
||||
const { onSubmit, onClear, onNewMessage, onView, feedback } = props;
|
||||
return (
|
||||
<div className="chat-footer">
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Col span={8}>
|
||||
<Space size={20}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={onNewMessage}
|
||||
>
|
||||
New Message
|
||||
</Button>
|
||||
<Button icon={<DeleteOutlined></DeleteOutlined>} onClick={onClear}>
|
||||
Clear
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={8}>{feedback}</Col>
|
||||
<Col span={8} style={{ textAlign: 'right' }}>
|
||||
<Space size={20}>
|
||||
<Button icon={<CodeOutlined></CodeOutlined>} onClick={onView}>
|
||||
View
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined></SaveOutlined>}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatFooter;
|
||||
@@ -1,68 +0,0 @@
|
||||
.chatEmpty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 200px;
|
||||
flex-direction: column;
|
||||
.logo {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
margin-block: 20px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.chatContent {
|
||||
width: max(50%, 500px);
|
||||
}
|
||||
.chatMessageItem {
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
padding-inline: 44px 16px;
|
||||
.name {
|
||||
padding-block: 8px 16px;
|
||||
}
|
||||
.content {
|
||||
padding: 8px 12px;
|
||||
border-radius: var(--border-radius-base);
|
||||
background-color: var(--color-fill-3);
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
.contentWrap {
|
||||
position: relative;
|
||||
padding-right: 45px;
|
||||
&:hover .edit {
|
||||
display: block;
|
||||
}
|
||||
.edit {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: 0;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
.img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
SyncOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Space, Tooltip } from 'antd';
|
||||
import chatStyle from './chat-style.less';
|
||||
import chatStyle from '../style/chat-style.less';
|
||||
import ChatEmpty from './chatEmpty';
|
||||
|
||||
export type ChatContentProps = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Logo from '@/assets/images/logo.png';
|
||||
import chatStyle from './chat-style.less';
|
||||
import chatStyle from '../style/chat-style.less';
|
||||
|
||||
const ChatEmpty: React.FC = () => {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useState } from 'react';
|
||||
import '../style/ground-left.less';
|
||||
import ChatFooter from './chat-footer';
|
||||
import MessageItem from './message-item';
|
||||
import ReferenceParams from './reference-params';
|
||||
|
||||
const MessageList: React.FC = () => {
|
||||
const [messageList, setMessageList] = useState<any[]>([
|
||||
{
|
||||
role: 'User',
|
||||
message: 'hello'
|
||||
},
|
||||
{
|
||||
role: 'Assistant',
|
||||
message: 'hello, nice to meet you!'
|
||||
}
|
||||
]);
|
||||
const [activeIndex, setActiveIndex] = useState(-1);
|
||||
|
||||
const handleNewMessage = () => {
|
||||
console.log('new message');
|
||||
messageList.push({
|
||||
role: 'User',
|
||||
message: 'hello'
|
||||
});
|
||||
setMessageList([...messageList]);
|
||||
setActiveIndex(messageList.length - 1);
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
console.log('clear');
|
||||
};
|
||||
|
||||
const handleView = () => {
|
||||
console.log('view');
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log('submit');
|
||||
};
|
||||
|
||||
const handleDelete = (index: number) => {
|
||||
messageList.splice(index, 1);
|
||||
setMessageList([...messageList]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ground-left">
|
||||
<PageContainer title={false} className="message-list-wrap">
|
||||
<div>
|
||||
{messageList.map((item, index) => {
|
||||
return (
|
||||
<MessageItem
|
||||
key={index}
|
||||
role={item.role}
|
||||
isFocus={index === activeIndex}
|
||||
onDelete={() => handleDelete(index)}
|
||||
message={item.message}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</PageContainer>
|
||||
<div className="ground-left-footer">
|
||||
<ChatFooter
|
||||
onClear={handleClear}
|
||||
onNewMessage={handleNewMessage}
|
||||
onSubmit={handleSubmit}
|
||||
onView={handleView}
|
||||
feedback={<ReferenceParams></ReferenceParams>}
|
||||
></ChatFooter>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageList;
|
||||
@@ -1,63 +0,0 @@
|
||||
.ant-pro-footer-bar {
|
||||
padding-block: 20px;
|
||||
padding-bottom: 30px;
|
||||
border: none;
|
||||
.ant-pro-footer-bar-right {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.messageInput {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: max(50%, 500px);
|
||||
background-color: var(--color-fill-1);
|
||||
border-radius: 30px;
|
||||
padding-right: 50px;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
&:focus-within {
|
||||
border-color: var(--ant-input-active-border-color);
|
||||
box-shadow: var(--ant-input-active-shadow);
|
||||
background-color: var(--color-white-1);
|
||||
transition: all 0.2s ease;
|
||||
&:hover {
|
||||
background-color: var(--color-white-1);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--ant-color-fill-secondary);
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
textarea {
|
||||
padding: 16px;
|
||||
border-radius: 30px;
|
||||
background-color: transparent;
|
||||
}
|
||||
textarea::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
textarea::-webkit-scrollbar-track {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
textarea::-webkit-scrollbar-thumb {
|
||||
background-color: #d9d9d9;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.send-btn {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 11px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transform: rotate(-30deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { MinusCircleOutlined } from '@ant-design/icons';
|
||||
import { Button, Input } from 'antd';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Roles } from '../config';
|
||||
import '../style/message-item.less';
|
||||
|
||||
const MessageContent: React.FC<{
|
||||
message: string;
|
||||
role: string;
|
||||
isFocus: boolean;
|
||||
onDelete: () => void;
|
||||
}> = ({ message, role, isFocus, onDelete }) => {
|
||||
const [roleType, setRoleType] = useState(role);
|
||||
const [messageContent, setMessageContent] = useState(message);
|
||||
const inputRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current && isFocus) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [isFocus]);
|
||||
|
||||
const handleMessageChange = (e: any) => {
|
||||
setMessageContent(e.target.value);
|
||||
};
|
||||
|
||||
const handleRoleChange = () => {
|
||||
if (roleType === Roles.User) {
|
||||
setRoleType(Roles.Assistant);
|
||||
}
|
||||
if (roleType === Roles.Assistant) {
|
||||
setRoleType(Roles.User);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
onDelete();
|
||||
};
|
||||
return (
|
||||
<div className="message-item">
|
||||
<div className="role-type">
|
||||
<Button onClick={handleRoleChange} type="text">
|
||||
{roleType}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="message-content-input">
|
||||
<Input.TextArea
|
||||
ref={inputRef}
|
||||
style={{ paddingBlock: '12px' }}
|
||||
value={messageContent}
|
||||
autoSize={true}
|
||||
variant="filled"
|
||||
onChange={handleMessageChange}
|
||||
></Input.TextArea>
|
||||
</div>
|
||||
<div className="delete-btn">
|
||||
<Button
|
||||
type="text"
|
||||
shape="circle"
|
||||
style={{ color: 'var(--ant-color-primary' }}
|
||||
onClick={handleDelete}
|
||||
icon={<MinusCircleOutlined />}
|
||||
></Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageContent;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SendOutlined } from '@ant-design/icons';
|
||||
import { Button, Input } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import './message-input.less';
|
||||
import '../style/message-input.less';
|
||||
|
||||
const MessageInput: React.FC = () => {
|
||||
const { TextArea } = Input;
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
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 { Button, Form, Select, Space } from 'antd';
|
||||
import { Form, Slider } from 'antd';
|
||||
import { useState } from 'react';
|
||||
|
||||
type ParamsSettingsProps = {
|
||||
seed?: number;
|
||||
stopSequence?: number;
|
||||
temperature?: number;
|
||||
topK?: number;
|
||||
topP?: number;
|
||||
repeatPenalty?: number;
|
||||
repeatLastN?: number;
|
||||
tfsZ?: number;
|
||||
contextLength?: number;
|
||||
model?: string;
|
||||
maxTokens?: number;
|
||||
};
|
||||
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 initialValues = {
|
||||
seed: 1,
|
||||
stopSequence: 1,
|
||||
@@ -51,134 +55,60 @@ const ParamsSettings: React.FC<{ onClose: () => void }> = ({ onClose }) => {
|
||||
onFinish={handleOnFinish}
|
||||
onFinishFailed={handleOnFinishFailed}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
maxHeight: '450px',
|
||||
overflow: 'auto',
|
||||
paddingRight: 'var(--ant-popover-inner-padding)'
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h3 className="m-b-20 m-l-10">Model</h3>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
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>
|
||||
name="temperature"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="Temperature"
|
||||
style={{ width: INPUT_WIDTH.mini }}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="maxTokens"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="Max Tokens"
|
||||
style={{ width: INPUT_WIDTH.mini }}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="topP"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<FieldWrapper label="TopP">
|
||||
<Slider defaultValue={50}></Slider>
|
||||
</FieldWrapper>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="seed"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="Seed"
|
||||
style={{ width: INPUT_WIDTH.default }}
|
||||
style={{ width: INPUT_WIDTH.mini }}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="select"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealSelect label="Select" style={{ width: INPUT_WIDTH.default }}>
|
||||
<Select.Option value={1}>1</Select.Option>
|
||||
<Select.Option value={2}>2</Select.Option>
|
||||
<Select.Option value={3}>3</Select.Option>
|
||||
</SealSelect>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="stopSequence"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="Stop Sequence"
|
||||
style={{ width: INPUT_WIDTH.default }}
|
||||
style={{ width: INPUT_WIDTH.mini }}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="temperature"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="Temperature"
|
||||
style={{ width: INPUT_WIDTH.default }}
|
||||
disabled={true}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="topK"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="TopK"
|
||||
style={{ width: INPUT_WIDTH.default }}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="topP"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="TopP"
|
||||
style={{ width: INPUT_WIDTH.default }}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="repeatPenalty"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="Repeat Penalty"
|
||||
style={{ width: INPUT_WIDTH.default }}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="repeatLastN"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Search
|
||||
label="Repeat Last N"
|
||||
style={{ width: INPUT_WIDTH.default }}
|
||||
disabled={true}
|
||||
></SealInput.Search>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="tfsZ"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="TFSZ"
|
||||
style={{ width: INPUT_WIDTH.default }}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="contextLength"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Number
|
||||
label="Context Length"
|
||||
style={{ width: INPUT_WIDTH.default }}
|
||||
></SealInput.Number>
|
||||
</Form.Item>
|
||||
<Form.Item<ParamsSettingsProps>
|
||||
name="maxTokens"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.TextArea
|
||||
label="Max Tokens"
|
||||
style={{ width: INPUT_WIDTH.default }}
|
||||
></SealInput.TextArea>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item noStyle>
|
||||
<Space
|
||||
size={20}
|
||||
align="end"
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
paddingRight: 'var(--ant-popover-inner-padding)',
|
||||
paddingTop: 'var(--ant-popover-inner-padding)'
|
||||
}}
|
||||
>
|
||||
<Button onClick={handleCancel}>Cancel</Button>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import '../style/reference-params.less';
|
||||
|
||||
const ReferenceParams = () => {
|
||||
return (
|
||||
<div className="reference-params">
|
||||
<span>Inference: 597 ms</span>
|
||||
<span style={{ padding: '10px' }}></span>
|
||||
<span>Tokens/s: 561</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReferenceParams;
|
||||
Reference in New Issue
Block a user