chore: playground layout

This commit is contained in:
jialin
2024-05-28 19:22:06 +08:00
parent 3120f5ee3a
commit a99b58fbb4
26 changed files with 444 additions and 189 deletions
+1
View File
@@ -22,6 +22,7 @@ export default defineConfig({
token: {
colorPrimary: '#2fbf85',
borderRadius: 20,
fontSize: 12,
motion: true
}
}
+6
View File
@@ -0,0 +1,6 @@
.m-b-20 {
margin-bottom: 20px;
}
.m-l-10 {
margin-left: 10px;
}
+43
View File
@@ -0,0 +1,43 @@
import useCopyToClipboard from '@/hooks/use-copy-to-clipboard';
import { CheckCircleFilled, CopyOutlined } from '@ant-design/icons';
import { Button } from 'antd';
type CopyButtonProps = {
text: string;
disabled?: boolean;
size?: 'small' | 'middle' | 'large';
};
const CopyButton: React.FC<CopyButtonProps> = ({
text,
disabled,
size = 'middle'
}) => {
const { copied, copyToClipboard } = useCopyToClipboard();
const handleCopy = async (e: any) => {
e.stopPropagation();
await copyToClipboard(text);
};
return (
<Button
type="text"
shape="circle"
size={size}
onClick={handleCopy}
disabled={!!disabled}
>
{copied ? (
<CheckCircleFilled
style={{ color: 'var(--ant-color-primary)', fontSize: '14px' }}
/>
) : (
<CopyOutlined
style={{ color: 'var(--ant-color-primary)', fontSize: '14px' }}
/>
)}
</Button>
);
};
export default CopyButton;
@@ -9,7 +9,6 @@ interface NoteInfoProps {
description?: React.ReactNode;
}
const NoteInfo: React.FC<NoteInfoProps> = (props) => {
console.log('props+++++++++', props);
const { required, description, label } = props || {};
return (
<span className="label-text">
@@ -167,4 +167,7 @@
top: -20px;
height: @wrapheight - 2px;
}
:global(.ant-input-group-addon) {
border: none;
}
}
+3 -2
View File
@@ -1,7 +1,8 @@
export const DEFAULT_NAME = 'Umi Max';
export const DEFAULT_NAME = 'seal';
export const INPUT_WIDTH = {
default: 500,
large: 600,
small: 400
small: 400,
mini: 300
};
+1 -7
View File
@@ -71,12 +71,6 @@ html {
--ant-modal-footer-margin-top: 30px;
}
}
.css-var-ru.ant-btn {
--ant-button-content-font-size-lg: 13px;
--ant-button-content-font-size: 12px;
--ant-button-content-font-size-sm: 12px;
--ant-modal-content-padding: 24px 30px;
}
.css-var-rf.ant-menu-css-var,
.css-var-ri.ant-menu-css-var,
@@ -236,5 +230,5 @@ body {
}
}
// ======== menu style end ============
@import url('src/assets/styles/common.less');
@import url('src/assets/styles/menu.less');
+23
View File
@@ -0,0 +1,23 @@
import { useCallback, useState } from 'react';
const useCopyToClipboard = () => {
const [copied, setCopied] = useState(false);
const copyToClipboard = useCallback(async (text: string) => {
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 3000);
}
} catch (error) {
setCopied(false);
}
}, []);
return { copied, copyToClipboard };
};
export default useCopyToClipboard;
+5 -5
View File
@@ -1,7 +1,8 @@
import CopyButton from '@/components/copy-button';
import ModalFooter from '@/components/modal-footer';
import SealInput from '@/components/seal-form/seal-input';
import { PageActionType } from '@/config/types';
import { CopyOutlined, SyncOutlined } from '@ant-design/icons';
import { SyncOutlined } from '@ant-design/icons';
import { Form, Modal } from 'antd';
type AddModalProps = {
@@ -27,9 +28,6 @@ const AddModal: React.FC<AddModalProps> = ({
}}
/>
);
const RenderCopyButton = () => {
return <CopyOutlined></CopyOutlined>;
};
return (
<Modal
@@ -52,7 +50,9 @@ const AddModal: React.FC<AddModalProps> = ({
<Form.Item name="secretkey" rules={[{ required: true }]}>
<SealInput.Input
label="Secret Key"
addonAfter={<RenderCopyButton></RenderCopyButton>}
addonAfter={
<CopyButton text={form.getFieldValue('secretKey')}></CopyButton>
}
></SealInput.Input>
</Form.Item>
</Form>
@@ -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;
@@ -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;
@@ -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;
+14
View File
@@ -0,0 +1,14 @@
export const Roles = {
User: 'User',
Assistant: 'Assistant'
};
export const playGroundRoles = [
{
key: 'user',
label: 'User'
},
{
key: 'assistant',
label: 'Assistant'
}
];
+13 -54
View File
@@ -1,19 +1,10 @@
import { ControlOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { Button, Popover, Select, Space } from 'antd';
import { Divider } from 'antd';
import { useEffect, useState } from 'react';
import ChatContent from './components/chatContent';
import MessageInput from './components/messageInput';
import GroundLeft from './components/ground-left';
import ParamsSettings from './components/params-settings';
const dataList = [
{ value: 'llama3:latest', label: 'llama3:latest' },
{ value: 'wangfuyun/AnimateLCM', label: 'wangfuyun/AnimateLCM' },
{ value: 'Revanthraja/Text_to_Vision', label: 'Revanthraja/Text_to_Vision' }
];
import './style/play-ground.less';
const Playground: React.FC = () => {
const [ModelList, setModelList] = useState(dataList);
const [messageList, setMessageList] = useState<any[]>([]);
const [selectedModel, setSelectedModel] = useState('llama3:latest');
const [showPopover, setShowPopover] = useState(false);
@@ -41,49 +32,17 @@ const Playground: React.FC = () => {
}, [selectedModel]);
return (
<PageContainer
title={false}
extra={[
<Space size={20} key="space">
<Select
showSearch
options={ModelList}
style={{ width: 300 }}
value={selectedModel}
onChange={handleSelectChange}
variant="filled"
key="model-select"
></Select>
<Popover
content={
<ParamsSettings onClose={handleClosePopover}></ParamsSettings>
}
destroyTooltipOnHide={true}
title="Params Settings"
trigger="click"
arrow={false}
open={showPopover}
overlayStyle={{ top: 70 }}
overlayInnerStyle={{ paddingRight: 0 }}
placement="bottomRight"
key="popover"
>
<Button
onClick={handleTogglePopover}
type="primary"
shape="circle"
key="button"
icon={<ControlOutlined />}
></Button>
</Popover>
</Space>
]}
footer={[<MessageInput key="message" />]}
>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<ChatContent messageList={messageList}></ChatContent>
<div className="play-ground">
<div className="chat">
<GroundLeft></GroundLeft>
</div>
</PageContainer>
<div className="divider-line">
<Divider type="vertical" />
</div>
<div className="params">
<ParamsSettings onClose={handleClosePopover} />
</div>
</div>
);
};
@@ -0,0 +1,7 @@
.chat-footer {
display: flex;
align-items: center;
padding-inline: 26px;
height: 100px;
// box-shadow: 0px -1px 2px 0px var(--ant-color-border);
}
@@ -0,0 +1,14 @@
.ground-left {
position: relative;
height: 100vh;
padding-bottom: 120px;
.message-list-wrap {
max-height: calc(100vh - 152px);
overflow-y: auto;
}
.ground-left-footer {
position: absolute;
bottom: 20px;
width: 100%;
}
}
@@ -0,0 +1,16 @@
.message-item {
display: flex;
justify-content: flex-start;
align-items: flex-start;
margin-bottom: 12px;
.role-type {
margin-right: 12px;
.ant-btn {
text-align: left;
width: 100px;
}
}
.message-content-input {
flex: 1;
}
}
@@ -0,0 +1,20 @@
.play-ground {
display: flex;
align-items: flex-start;
.chat {
flex: 1;
display: flex;
flex-direction: column;
}
.params {
// width: 465px;
padding: 20px;
}
.divider-line {
width: 1px;
.ant-divider {
margin: 0;
min-height: 100vh;
}
}
}
@@ -0,0 +1,8 @@
.reference-params {
display: flex;
cursor: pointer;
height: 40px;
justify-content: center;
align-items: center;
color: var(--ant-orange);
}