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
@@ -0,0 +1,9 @@
import React from 'react';
import '../styles/row-children.less';
const RowChildren = (props: any) => {
const { children } = props;
return <div className="row-children">{children}</div>;
};
export default React.memo(RowChildren);
@@ -1,5 +1,5 @@
import { DownOutlined, RightOutlined } from '@ant-design/icons'; import { DownOutlined, RightOutlined } from '@ant-design/icons';
import { Button, Checkbox, Col, Row } from 'antd'; import { Button, Checkbox, Col, Empty, Row, Spin } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import _ from 'lodash'; import _ from 'lodash';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -17,11 +17,15 @@ const TableRow: React.FC<
rowSelection, rowSelection,
rowKey, rowKey,
columns, columns,
onExpand onExpand,
renderChildren,
loadChildren
} = props; } = props;
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const [checked, setChecked] = useState(false); const [checked, setChecked] = useState(false);
const [childrenData, setChildrenData] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => { useEffect(() => {
if (rowSelection) { if (rowSelection) {
@@ -34,9 +38,18 @@ const TableRow: React.FC<
} }
}, [rowSelection]); }, [rowSelection]);
const handleRowExpand = () => { const handleRowExpand = async () => {
try {
setExpanded(!expanded); setExpanded(!expanded);
onExpand?.(!expanded, record); onExpand?.(!expanded, record);
setLoading(true);
const data = await loadChildren?.(record);
setChildrenData(data || []);
setLoading(false);
} catch (error) {
setChildrenData([]);
setLoading(false);
}
}; };
const handleSelectChange = (e: any) => { const handleSelectChange = (e: any) => {
@@ -126,7 +139,13 @@ const TableRow: React.FC<
</div> </div>
{expanded && ( {expanded && (
<div className="expanded-row"> <div className="expanded-row">
<div className="expanded-row-content">rowchildren</div> <Spin spinning={loading}>
{childrenData.length ? (
renderChildren?.(childrenData)
) : (
<Empty></Empty>
)}
</Spin>
</div> </div>
)} )}
</div> </div>
+12 -2
View File
@@ -8,8 +8,16 @@ import './styles/index.less';
import { SealColumnProps, SealTableProps } from './types'; import { SealColumnProps, SealTableProps } from './types';
const SealTable: React.FC<SealTableProps> = (props) => { const SealTable: React.FC<SealTableProps> = (props) => {
const { children, rowKey, onExpand, loading, expandable, rowSelection } = const {
props; children,
rowKey,
onExpand,
loading,
expandable,
rowSelection,
renderChildren,
loadChildren
} = props;
const [selectAll, setSelectAll] = useState(false); const [selectAll, setSelectAll] = useState(false);
const [indeterminate, setIndeterminate] = useState(false); const [indeterminate, setIndeterminate] = useState(false);
@@ -121,6 +129,8 @@ const SealTable: React.FC<SealTableProps> = (props) => {
rowSelection={rowSelection} rowSelection={rowSelection}
expandable={expandable} expandable={expandable}
rowKey={rowKey} rowKey={rowKey}
renderChildren={renderChildren}
loadChildren={loadChildren}
onExpand={onExpand} onExpand={onExpand}
></TableRow> ></TableRow>
); );
+1 -1
View File
@@ -22,7 +22,7 @@
} }
.expanded-row { .expanded-row {
background-color: var(--color-white-1); background-color: var(--color-white-1);
padding: 16px 20px; padding: 16px 16px;
border: 1px solid var(--color-fill-1); border: 1px solid var(--color-fill-1);
border-top: 0; border-top: 0;
border-radius: 0 0 var(--ant-table-header-border-radius) border-radius: 0 0 var(--ant-table-header-border-radius)
@@ -0,0 +1,13 @@
.row-children {
display: flex;
align-items: center;
height: 54px;
padding: 0 16px;
border-radius: var(--ant-table-header-border-radius);
background-color: var(--color-fill-1);
transition: all 0.2s ease;
&:hover {
background-color: var(--ant-table-row-hover-bg);
transition: all 0.2s ease;
}
}
+2
View File
@@ -31,6 +31,8 @@ export interface SealTableProps {
dataSource: any[]; dataSource: any[];
loading?: boolean; loading?: boolean;
onExpand?: (expanded: boolean, record: any) => void; onExpand?: (expanded: boolean, record: any) => void;
renderChildren?: (data: any) => React.ReactNode;
loadChildren?: (record: any) => Promise<any[]>;
rowKey: string; rowKey: string;
} }
+22
View File
@@ -0,0 +1,22 @@
import { useEffect, useState } from 'react';
const TypingEffect: React.FC<{ text?: string }> = ({ text = '' }) => {
const [displayedText, setDisplayedText] = useState('');
useEffect(() => {
let index = 0;
const intervalId = setInterval(() => {
setDisplayedText((prev) => prev + text[index]);
index += 1;
if (index === text.length) {
clearInterval(intervalId);
}
}, 20);
return () => clearInterval(intervalId);
}, [text]);
return <div>{displayedText}</div>;
};
export default TypingEffect;
+1 -1
View File
@@ -1,7 +1,7 @@
declare namespace Global { declare namespace Global {
interface Pagination { interface Pagination {
page: number; page: number;
perPage: number; perPage?: number;
watch?: boolean; watch?: boolean;
} }
interface PageResponse<T> { interface PageResponse<T> {
+8
View File
@@ -29,3 +29,11 @@ export const StatusColorMap: Record<StatusType, { text: string; bg: string }> =
bg: `var(--ant-color-fill)` bg: `var(--ant-color-fill)`
} }
}; };
export const StatusMaps = {
error: 'error',
warning: 'warning',
transitioning: 'transitioning',
success: 'success',
inactive: 'inactive'
};
+11 -2
View File
@@ -164,12 +164,21 @@ const Models: React.FC = () => {
dataIndex="GPU" dataIndex="GPU"
key="GPU" key="GPU"
render={(text, record: ListItem) => { render={(text, record: ListItem) => {
return <RenderProgress percent={0}></RenderProgress>; return (
<RenderProgress
percent={_.get(record, [
'status',
'gpu',
'0',
'core_utilization_rate'
])}
></RenderProgress>
);
}} }}
/> />
<Column <Column
title="VRAM" title="VRAM"
dataIndex="GRAM" dataIndex="VRAM"
key="VRAM" key="VRAM"
render={(text, record: ListItem) => { render={(text, record: ListItem) => {
return <RenderProgress percent={0}></RenderProgress>; return <RenderProgress percent={0}></RenderProgress>;
@@ -3,27 +3,7 @@ import { memo, useMemo } from 'react';
const RenderProgress = memo((props: { percent: number }) => { const RenderProgress = memo((props: { percent: number }) => {
const { percent } = props; const { percent } = props;
// const { record, dataIndex } = props; console.log('percent====', percent);
// const value1 = useMemo(() => {
// let value = _.get(record, ['resources', 'allocable', dataIndex]);
// if (['gram', 'memory'].includes(dataIndex)) {
// value = _.toNumber(value.replace(/GiB|Gib/, ''));
// }
// return value;
// }, [record, dataIndex]);
// const value2 = useMemo(() => {
// let value = _.get(record, ['resources', 'capacity', dataIndex]);
// if (['gram', 'memory'].includes(dataIndex)) {
// value = _.toNumber(value.replace(/GiB|Gib/, ''));
// }
// return value;
// }, [record, dataIndex]);
// if (!value1 || !value2) {
// return <Progress percent={0} strokeColor="var(--ant-color-primary)" />;
// }
// const percent = _.round(value1 / value2, 2) * 100;
const strokeColor = useMemo(() => { const strokeColor = useMemo(() => {
if (percent <= 50) { if (percent <= 50) {
return 'var(--ant-color-primary)'; return 'var(--ant-color-primary)';
@@ -35,7 +15,7 @@ const RenderProgress = memo((props: { percent: number }) => {
}, [percent]); }, [percent]);
return ( return (
<Progress <Progress
steps={5} steps={10}
format={() => { format={() => {
return ( return (
<span style={{ color: 'var(--ant-color-text)' }}>{percent}%</span> <span style={{ color: 'var(--ant-color-text)' }}>{percent}%</span>
-6
View File
@@ -4,14 +4,8 @@ import { Tabs } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import GPUs from './components/gpus'; import GPUs from './components/gpus';
import Nodes from './components/nodes'; import Nodes from './components/nodes';
import Test from './components/test';
const items: TabsProps['items'] = [ const items: TabsProps['items'] = [
{
key: 'test',
label: 'Test',
children: <Test />
},
{ {
key: 'nodes', key: 'nodes',
label: 'Nodes', label: 'Nodes',
+61 -1
View File
@@ -1,8 +1,11 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { FormData, ListItem } from '../config/types'; import { FormData, ListItem, ModelInstanceListItem } from '../config/types';
export const MODELS_API = '/models'; export const MODELS_API = '/models';
export const MODEL_INSTANCE_API = '/model_instances';
// ===================== Models =====================
export async function queryModelsList( export async function queryModelsList(
params: Global.Pagination & { query?: string } params: Global.Pagination & { query?: string }
) { ) {
@@ -31,3 +34,60 @@ export async function updateModel(params: { id: number; data: FormData }) {
data: params.data data: params.data
}); });
} }
export async function queryModelDetail(id: number) {
return request(`${MODELS_API}/${id}`, {
method: 'GET'
});
}
// ===================== Model Instances start =====================
export async function queryModelInstancesList(
params: Global.Pagination & { query?: string; id: number }
) {
return request<Global.PageResponse<ModelInstanceListItem>>(
`${MODELS_API}/${params.id}/instances`,
{
method: 'GET',
params
}
);
}
export async function createModelInstance(params: { data: FormData }) {
return request(`${MODEL_INSTANCE_API}`, {
method: 'POST',
data: params.data
});
}
export async function deleteModelInstance(id: number) {
return request(`${MODEL_INSTANCE_API}/${id}`, {
method: 'DELETE'
});
}
export async function updateModelInstance(params: {
id: number;
data: FormData;
}) {
return request(`${MODEL_INSTANCE_API}/${params.id}`, {
method: 'PUT',
data: params.data
});
}
export async function queryModelInstanceDetail(id: number) {
return request(`${MODEL_INSTANCE_API}/${id}`, {
method: 'GET'
});
}
export async function queryModelInstanceLogs(id: number) {
return request(`${MODEL_INSTANCE_API}/${id}/logs`, {
method: 'GET'
});
}
// ===================== Model Instances end =====================
+5 -8
View File
@@ -47,17 +47,14 @@ const AddModal: React.FC<AddModalProps> = (props) => {
name="huggingface_repo_id" name="huggingface_repo_id"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<SealInput.Input label="Huggingface ID" required></SealInput.Input> <SealInput.Input label="Repo ID" required></SealInput.Input>
</Form.Item> </Form.Item>
{/* <Form.Item<FormData> <Form.Item<FormData>
name="huggingface_filename" name="huggingface_filename"
rules={[{ required: true }]} rules={[{ required: false }]}
> >
<SealInput.Input <SealInput.Input label="File Name" required></SealInput.Input>
label="Huggingface File Name" </Form.Item>
required
></SealInput.Input>
</Form.Item> */}
</> </>
); );
}; };
+6
View File
@@ -1,3 +1,5 @@
import { StatusMaps } from '@/config';
export const modelInstanceCols = [ export const modelInstanceCols = [
{ {
title: 'Name', title: 'Name',
@@ -29,3 +31,7 @@ export const modelInstanceCols = [
key: 'Operation' key: 'Operation'
} }
]; ];
export const status: any = {
Running: StatusMaps.success
};
+18
View File
@@ -18,3 +18,21 @@ export interface FormData {
name: string; name: string;
description: string; description: string;
} }
export interface ModelInstanceListItem {
source: string;
huggingface_repo_id: string;
huggingface_filename: string;
s3_address: string;
node_id: number;
node_ip: string;
pid: number;
port: number;
state: string;
download_progress: number;
model_id: number;
model_name: string;
id: number;
created_at: string;
updated_at: string;
}
+95 -5
View File
@@ -1,12 +1,15 @@
import PageTools from '@/components/page-tools'; import PageTools from '@/components/page-tools';
import SealTable from '@/components/seal-table'; import SealTable from '@/components/seal-table';
import RowChildren from '@/components/seal-table/components/row-children';
import SealColumn from '@/components/seal-table/components/seal-column'; import SealColumn from '@/components/seal-table/components/seal-column';
import StatusTag from '@/components/status-tag';
import { PageAction } from '@/config'; import { PageAction } from '@/config';
import type { PageActionType } from '@/config/types'; import type { PageActionType } from '@/config/types';
import useTableRowSelection from '@/hooks/use-table-row-selection'; import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort'; import useTableSort from '@/hooks/use-table-sort';
import { import {
DeleteOutlined, DeleteOutlined,
FieldTimeOutlined,
PlusOutlined, PlusOutlined,
SyncOutlined, SyncOutlined,
WechatWorkOutlined WechatWorkOutlined
@@ -16,21 +19,28 @@ import { Access, useAccess, useIntl, useNavigate } from '@umijs/max';
import { import {
App, App,
Button, Button,
Col,
Input, Input,
Modal, Modal,
Progress, Progress,
Row,
Space, Space,
Table,
Tooltip, Tooltip,
message message
} from 'antd'; } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import _ from 'lodash'; import _ from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { createModel, deleteModel, queryModelsList } from './apis'; import {
createModel,
deleteModel,
deleteModelInstance,
queryModelInstancesList,
queryModelsList
} from './apis';
import AddModal from './components/add-modal'; import AddModal from './components/add-modal';
import { FormData, ListItem } from './config/types'; import { status } from './config';
const { Column } = Table; import { FormData, ListItem, ModelInstanceListItem } from './config/types';
const Models: React.FC = () => { const Models: React.FC = () => {
const { modal } = App.useApp(); const { modal } = App.useApp();
@@ -152,7 +162,85 @@ const Models: React.FC = () => {
const handleOpenPlayGround = (row: any) => { const handleOpenPlayGround = (row: any) => {
console.log('handleOpenPlayGround', row); console.log('handleOpenPlayGround', row);
navigate('/playground'); navigate(`/playground?model=${row.name}`);
};
const handleViewLogs = (row: any) => {
console.log('handleViewLogs', row);
};
const handleDeleteInstace = (row: any) => {
Modal.confirm({
title: '',
content: 'Are you sure you want to delete the instance?',
async onOk() {
console.log('OK');
await deleteModelInstance(row.id);
message.success('successfully!');
fetchData();
},
onCancel() {
console.log('Cancel');
}
});
};
const getModelInstances = async (row: any) => {
const params = {
id: row.id,
page: 1,
perPage: 100
};
const data = await queryModelInstancesList(params);
return data.items || [];
};
const renderChildren = (list: any) => {
return (
<>
{_.map(list, (item: ModelInstanceListItem) => {
return (
<RowChildren key={item.id}>
<Row style={{ width: '100%' }} align="middle">
<Col span={4}>
{item.node_ip}:{item.port}
</Col>
<Col span={5}>{item.huggingface_repo_id}</Col>
<Col span={4}>
{dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss')}
</Col>
<Col span={4}>
<StatusTag
statusValue={{
status: status[item.state] as any,
text: item.state
}}
></StatusTag>
</Col>
<Col span={7}>
<Space>
<Tooltip title="Delete">
<Button
size="small"
danger
onClick={() => handleDeleteInstace(item)}
icon={<DeleteOutlined></DeleteOutlined>}
></Button>
</Tooltip>
<Tooltip title="View Logs">
<Button
size="small"
onClick={() => handleViewLogs(item)}
icon={<FieldTimeOutlined />}
></Button>
</Tooltip>
</Space>
</Col>
</Row>
</RowChildren>
);
})}
</>
);
}; };
// request data // request data
@@ -217,6 +305,8 @@ const Models: React.FC = () => {
rowKey="id" rowKey="id"
expandable={true} expandable={true}
onChange={handleTableChange} onChange={handleTableChange}
loadChildren={getModelInstances}
renderChildren={renderChildren}
pagination={{ pagination={{
showSizeChanger: true, showSizeChanger: true,
pageSize: queryParams.perPage, pageSize: queryParams.perPage,
+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; onClear: () => void;
onNewMessage: () => void; onNewMessage: () => void;
onView: () => void; onView: () => void;
disabled?: boolean;
feedback?: React.ReactNode; feedback?: React.ReactNode;
} }
const ChatFooter: React.FC<ChatFooterProps> = (props) => { const ChatFooter: React.FC<ChatFooterProps> = (props) => {
const { onSubmit, onClear, onNewMessage, onView, feedback } = props; const { onSubmit, onClear, onNewMessage, onView, feedback, disabled } = props;
return ( return (
<div className="chat-footer"> <div className="chat-footer">
<Row style={{ width: '100%' }}> <Row style={{ width: '100%' }}>
<Col span={8}> <Col span={8}>
<Space size={20}> <Space size={20}>
<Button <Button
disabled={disabled}
type="primary" type="primary"
icon={<PlusOutlined />} icon={<PlusOutlined />}
onClick={onNewMessage} onClick={onNewMessage}
> >
New Message New Message
</Button> </Button>
<Button icon={<DeleteOutlined></DeleteOutlined>} onClick={onClear}> <Button
icon={<DeleteOutlined></DeleteOutlined>}
onClick={onClear}
disabled={disabled}
>
Clear Clear
</Button> </Button>
</Space> </Space>
@@ -37,10 +43,15 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
<Col span={8}>{feedback}</Col> <Col span={8}>{feedback}</Col>
<Col span={8} style={{ textAlign: 'right' }}> <Col span={8} style={{ textAlign: 'right' }}>
<Space size={20}> <Space size={20}>
<Button icon={<CodeOutlined></CodeOutlined>} onClick={onView}> <Button
icon={<CodeOutlined></CodeOutlined>}
onClick={onView}
disabled={disabled}
>
View View
</Button> </Button>
<Button <Button
disabled={disabled}
type="primary" type="primary"
icon={<SaveOutlined></SaveOutlined>} icon={<SaveOutlined></SaveOutlined>}
onClick={onSubmit} onClick={onSubmit}
+81 -16
View File
@@ -1,8 +1,11 @@
import TransitionWrapper from '@/components/transition'; import TransitionWrapper from '@/components/transition';
import { EyeInvisibleOutlined } from '@ant-design/icons'; import { EyeInvisibleOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components'; 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 { useRef, useState } from 'react';
import { execChatCompletions } from '../apis';
import { Roles } from '../config';
import '../style/ground-left.less'; import '../style/ground-left.less';
import '../style/system-message-wrap.less'; import '../style/system-message-wrap.less';
import ChatFooter from './chat-footer'; import ChatFooter from './chat-footer';
@@ -10,37 +13,74 @@ import MessageItem from './message-item';
import ReferenceParams from './reference-params'; import ReferenceParams from './reference-params';
import ViewCodeModal from './view-code-modal'; import ViewCodeModal from './view-code-modal';
const MessageList: React.FC = () => { interface MessageProps {
const [messageList, setMessageList] = useState<any[]>([ parameters: any;
}
const MessageList: React.FC<MessageProps> = (props) => {
const { parameters } = props;
const [messageList, setMessageList] = useState<
{ role: string; content: string }[]
>([
{ {
role: 'User', role: 'user',
message: 'hello' content: ''
},
{
role: 'Assistant',
message: 'hello, nice to meet you!'
} }
]); ]);
const [systemMessage, setSystemMessage] = useState(''); const [systemMessage, setSystemMessage] = useState('');
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const [loading, setLoading] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1); const [activeIndex, setActiveIndex] = useState(-1);
const [tokenResult, setTokenResult] = useState<any>(null);
const systemRef = useRef<any>(null); const systemRef = useRef<any>(null);
const handleSystemMessageChange = (e: any) => { const handleSystemMessageChange = (e: any) => {
setSystemMessage(e.target.value); setSystemMessage(e.target.value);
}; };
const handleNewMessage = () => { const handleNewMessage = () => {
console.log('new message');
messageList.push({ messageList.push({
role: 'User', role: 'user',
message: 'hello' content: ''
}); });
setMessageList([...messageList]); setMessageList([...messageList]);
setActiveIndex(messageList.length - 1); 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 = () => { const handleClear = () => {
console.log('clear'); setMessageList([]);
}; };
const handleView = () => { const handleView = () => {
@@ -49,6 +89,7 @@ const MessageList: React.FC = () => {
const handleSubmit = () => { const handleSubmit = () => {
console.log('submit'); console.log('submit');
submitMessage();
}; };
const handleCloseViewCode = () => { const handleCloseViewCode = () => {
@@ -60,6 +101,15 @@ const MessageList: React.FC = () => {
setMessageList([...messageList]); setMessageList([...messageList]);
}; };
const handleUpdateMessage = (
index: number,
message: { role: string; content: string }
) => {
messageList[index] = message;
console.log('updatemessage========', index, message);
setMessageList([...messageList]);
};
const renderLabel = () => { const renderLabel = () => {
return ( return (
<div className="system-message-wrap "> <div className="system-message-wrap ">
@@ -94,13 +144,27 @@ const MessageList: React.FC = () => {
return ( return (
<MessageItem <MessageItem
key={index} key={index}
role={item.role}
isFocus={index === activeIndex} isFocus={index === activeIndex}
islast={index === messageList.length - 1}
loading={loading}
onDelete={() => handleDelete(index)} 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> </div>
</PageContainer> </PageContainer>
<div className="ground-left-footer"> <div className="ground-left-footer">
@@ -109,7 +173,8 @@ const MessageList: React.FC = () => {
onNewMessage={handleNewMessage} onNewMessage={handleNewMessage}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onView={handleView} onView={handleView}
feedback={<ReferenceParams></ReferenceParams>} disabled={loading}
feedback={<ReferenceParams usage={tokenResult}></ReferenceParams>}
></ChatFooter> ></ChatFooter>
</div> </div>
<ViewCodeModal <ViewCodeModal
@@ -1,17 +1,26 @@
import { MinusCircleOutlined } from '@ant-design/icons'; import { MinusCircleOutlined } from '@ant-design/icons';
import { Button, Input } from 'antd'; 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 { Roles } from '../config';
import '../style/message-item.less'; import '../style/message-item.less';
const MessageContent: React.FC<{ const MessageItem: React.FC<{
message: string; message: {
role: string; role: string;
content: string;
};
loading?: boolean;
islast?: boolean;
updateMessage: (message: { role: string; content: string }) => void;
isFocus: boolean; isFocus: boolean;
onDelete: () => void; onDelete: () => void;
}> = ({ message, role, isFocus, onDelete }) => { }> = ({ message, isFocus, onDelete, updateMessage, loading, islast }) => {
const [roleType, setRoleType] = useState(role); const [roleType, setRoleType] = useState(message.role);
const [messageContent, setMessageContent] = useState(message); 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); const inputRef = useRef<any>(null);
useEffect(() => { useEffect(() => {
@@ -20,17 +29,49 @@ const MessageContent: React.FC<{
} }
}, [isFocus]); }, [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) => { const handleMessageChange = (e: any) => {
setIsTyping(true);
setMessageContent(e.target.value); setMessageContent(e.target.value);
}; };
const handleBlur = () => {
setIsTyping(true);
};
const handleRoleChange = () => { const handleRoleChange = () => {
if (roleType === Roles.User) { setRoleType((prevRoleType) => {
setRoleType(Roles.Assistant); const newRoleType =
} prevRoleType === Roles.User ? Roles.Assistant : Roles.User;
if (roleType === Roles.Assistant) { return newRoleType;
setRoleType(Roles.User); });
}
}; };
const handleDelete = () => { const handleDelete = () => {
@@ -40,7 +81,7 @@ const MessageContent: React.FC<{
<div className="message-item"> <div className="message-item">
<div className="role-type"> <div className="role-type">
<Button onClick={handleRoleChange} type="text"> <Button onClick={handleRoleChange} type="text">
{roleType} {_.upperFirst(roleType)}
</Button> </Button>
</div> </div>
<div className="message-content-input"> <div className="message-content-input">
@@ -51,6 +92,7 @@ const MessageContent: React.FC<{
autoSize={true} autoSize={true}
variant="filled" variant="filled"
onChange={handleMessageChange} onChange={handleMessageChange}
onBlur={handleBlur}
></Input.TextArea> ></Input.TextArea>
</div> </div>
<div className="delete-btn"> <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 SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
import { INPUT_WIDTH } from '@/constants'; import { INPUT_WIDTH } from '@/constants';
import { queryModelsList } from '@/pages/llmodels/apis';
import { Form, Slider } from 'antd'; 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 = { type ParamsSettingsProps = {
seed?: number; onClose?: () => void;
stopSequence?: number; selectedModel?: string;
temperature?: number; params?: ParamsSettingsFormProps;
topP?: number; setParams: (params: any) => void;
model?: string;
maxTokens?: number;
}; };
const dataList = [ // const dataList = [
{ value: 'llama3:latest', label: 'llama3:latest' }, // { value: 'llama3:latest', label: 'llama3:latest' },
{ value: 'wangfuyun/AnimateLCM', label: 'wangfuyun/AnimateLCM' }, // { value: 'wangfuyun/AnimateLCM', label: 'wangfuyun/AnimateLCM' },
{ value: 'Revanthraja/Text_to_Vision', label: 'Revanthraja/Text_to_Vision' } // { value: 'Revanthraja/Text_to_Vision', label: 'Revanthraja/Text_to_Vision' }
]; // ];
const ParamsSettings: React.FC<{ onClose: () => void }> = ({ onClose }) => { const ParamsSettings: React.FC<ParamsSettingsProps> = ({
const [ModelList, setModelList] = useState(dataList); onClose,
selectedModel,
setParams
}) => {
const [ModelList, setModelList] = useState([]);
const initialValues = { const initialValues = {
seed: 1, seed: null,
stopSequence: 1, stop: null,
temperature: 1, temperature: 1,
topK: 1, top_p: 1,
topP: 1, max_tokens: 1024
repeatPenalty: 1,
repeatLastN: 1,
tfsZ: 1,
contextLength: 256,
maxTokens: 256
}; };
const [form] = Form.useForm(); 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) => { const handleOnFinish = (values: any) => {
console.log('handleOnFinish', values); console.log('handleOnFinish', values);
}; };
@@ -45,61 +91,79 @@ const ParamsSettings: React.FC<{ onClose: () => void }> = ({ onClose }) => {
const handleCancel = () => { const handleCancel = () => {
form.resetFields(); form.resetFields();
onClose(); onClose?.();
};
const handleValuesChange = (changedValues: any, allValues: any) => {
console.log('handleValuesChange', changedValues, allValues);
setParams?.(allValues);
}; };
return ( return (
<Form <Form
name="modelparams" name="modelparams"
form={form} form={form}
onValuesChange={handleValuesChange}
onFinish={handleOnFinish} onFinish={handleOnFinish}
onFinishFailed={handleOnFinishFailed} onFinishFailed={handleOnFinishFailed}
> >
<div> <div>
<h3 className="m-b-20 m-l-10">Model</h3> <h3 className="m-b-20 m-l-10">Model</h3>
<Form.Item<ParamsSettingsProps> <Form.Item<ParamsSettingsFormProps>
name="model" name="model"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<SealSelect options={ModelList} label="Model"></SealSelect> <SealSelect options={ModelList} label="Model"></SealSelect>
</Form.Item> </Form.Item>
<h3 className="m-b-20 m-l-10">Parameters</h3> <h3 className="m-b-20 m-l-10">Parameters</h3>
<Form.Item<ParamsSettingsProps> <Form.Item<ParamsSettingsFormProps>
name="temperature" name="temperature"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<FieldWrapper label="Temperature"> <FieldWrapper label="Temperature">
<Slider defaultValue={50}></Slider> <Slider
defaultValue={1}
max={2}
step={0.1}
style={{ marginBottom: 0 }}
tooltip={{ open: true }}
></Slider>
</FieldWrapper> </FieldWrapper>
</Form.Item> </Form.Item>
<Form.Item<ParamsSettingsProps> <Form.Item<ParamsSettingsFormProps>
name="maxTokens" name="max_tokens"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<SealInput.Input <SealInput.Number
label="Max Tokens" label="Max Tokens"
style={{ width: INPUT_WIDTH.mini }} style={{ width: INPUT_WIDTH.mini }}
></SealInput.Input> ></SealInput.Number>
</Form.Item> </Form.Item>
<Form.Item<ParamsSettingsProps> <Form.Item<ParamsSettingsFormProps>
name="topP" name="top_p"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<FieldWrapper label="Top P"> <FieldWrapper label="Top P">
<Slider defaultValue={50}></Slider> <Slider
defaultValue={1}
max={1}
step={0.1}
style={{ marginBottom: 0 }}
tooltip={{ open: true }}
></Slider>
</FieldWrapper> </FieldWrapper>
</Form.Item> </Form.Item>
<Form.Item<ParamsSettingsProps> <Form.Item<ParamsSettingsFormProps>
name="seed" name="seed"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<SealInput.Input <SealInput.Number
label="Seed" label="Seed"
style={{ width: INPUT_WIDTH.mini }} style={{ width: INPUT_WIDTH.mini }}
></SealInput.Input> ></SealInput.Number>
</Form.Item> </Form.Item>
<Form.Item<ParamsSettingsProps> <Form.Item<ParamsSettingsFormProps>
name="stopSequence" name="stop"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<SealInput.Input <SealInput.Input
@@ -1,11 +1,31 @@
import { Space, Tooltip } from 'antd';
import '../style/reference-params.less'; 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 ( return (
<div className="reference-params"> <div className="reference-params">
<span>Inference: 597 ms</span> <Tooltip
<span style={{ padding: '10px' }}></span> title={
<span>Tokens/s: 561</span> <Space>
<span>Completion: {usage.completion_tokens}</span>
<span>Prompt: {usage.prompt_tokens}</span>
</Space>
}
>
<span>Token Usage: {usage.total_tokens}</span>
</Tooltip>
</div> </div>
); );
}; };
+2 -2
View File
@@ -1,6 +1,6 @@
export const Roles = { export const Roles = {
User: 'User', User: 'user',
Assistant: 'Assistant' Assistant: 'assistant'
}; };
export const playGroundRoles = [ export const playGroundRoles = [
{ {
+12 -14
View File
@@ -1,24 +1,22 @@
import { useSearchParams } from '@umijs/max';
import { Divider } from 'antd'; import { Divider } from 'antd';
import { useEffect, useState } from 'react'; import { useState } from 'react';
import GroundLeft from './components/ground-left'; import GroundLeft from './components/ground-left';
import ParamsSettings from './components/params-settings'; import ParamsSettings from './components/params-settings';
import './style/play-ground.less'; import './style/play-ground.less';
const Playground: React.FC = () => { const Playground: React.FC = () => {
const [messageList, setMessageList] = useState<any[]>([]); const [searchParams] = useSearchParams();
const [selectedModel, setSelectedModel] = useState('llama3:latest'); const [selectedModel, setSelectedModel] = useState('llama3:latest');
const [showPopover, setShowPopover] = useState(false); const [showPopover, setShowPopover] = useState(false);
const selectModel = searchParams.get('model') || '';
const [params, setParams] = useState({});
console.log('query======', searchParams, selectModel);
const handleSelectChange = (value: string) => { const handleSelectChange = (value: string) => {
setSelectedModel(value); setSelectedModel(value);
}; };
const getMessageList = () => {
// fetch message list from server
console.log('getModelList');
setMessageList(['1']);
};
const handleTogglePopover = () => { const handleTogglePopover = () => {
setShowPopover(!showPopover); setShowPopover(!showPopover);
}; };
@@ -27,20 +25,20 @@ const Playground: React.FC = () => {
setShowPopover(false); setShowPopover(false);
}; };
useEffect(() => {
getMessageList();
}, [selectedModel]);
return ( return (
<div className="play-ground"> <div className="play-ground">
<div className="chat"> <div className="chat">
<GroundLeft></GroundLeft> <GroundLeft parameters={params}></GroundLeft>
</div> </div>
<div className="divider-line"> <div className="divider-line">
<Divider type="vertical" /> <Divider type="vertical" />
</div> </div>
<div className="params"> <div className="params">
<ParamsSettings onClose={handleClosePopover} /> <ParamsSettings
onClose={handleClosePopover}
setParams={setParams}
selectedModel={selectModel}
/>
</div> </div>
</div> </div>
); );