chore: playground api request
This commit is contained in:
@@ -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 { Button, Checkbox, Col, Row } from 'antd';
|
||||
import { Button, Checkbox, Col, Empty, Row, Spin } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
@@ -17,11 +17,15 @@ const TableRow: React.FC<
|
||||
rowSelection,
|
||||
rowKey,
|
||||
columns,
|
||||
onExpand
|
||||
onExpand,
|
||||
renderChildren,
|
||||
loadChildren
|
||||
} = props;
|
||||
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [checked, setChecked] = useState(false);
|
||||
const [childrenData, setChildrenData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (rowSelection) {
|
||||
@@ -34,9 +38,18 @@ const TableRow: React.FC<
|
||||
}
|
||||
}, [rowSelection]);
|
||||
|
||||
const handleRowExpand = () => {
|
||||
setExpanded(!expanded);
|
||||
onExpand?.(!expanded, record);
|
||||
const handleRowExpand = async () => {
|
||||
try {
|
||||
setExpanded(!expanded);
|
||||
onExpand?.(!expanded, record);
|
||||
setLoading(true);
|
||||
const data = await loadChildren?.(record);
|
||||
setChildrenData(data || []);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setChildrenData([]);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectChange = (e: any) => {
|
||||
@@ -126,7 +139,13 @@ const TableRow: React.FC<
|
||||
</div>
|
||||
{expanded && (
|
||||
<div className="expanded-row">
|
||||
<div className="expanded-row-content">rowchildren</div>
|
||||
<Spin spinning={loading}>
|
||||
{childrenData.length ? (
|
||||
renderChildren?.(childrenData)
|
||||
) : (
|
||||
<Empty></Empty>
|
||||
)}
|
||||
</Spin>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,16 @@ import './styles/index.less';
|
||||
import { SealColumnProps, SealTableProps } from './types';
|
||||
|
||||
const SealTable: React.FC<SealTableProps> = (props) => {
|
||||
const { children, rowKey, onExpand, loading, expandable, rowSelection } =
|
||||
props;
|
||||
const {
|
||||
children,
|
||||
rowKey,
|
||||
onExpand,
|
||||
loading,
|
||||
expandable,
|
||||
rowSelection,
|
||||
renderChildren,
|
||||
loadChildren
|
||||
} = props;
|
||||
|
||||
const [selectAll, setSelectAll] = useState(false);
|
||||
const [indeterminate, setIndeterminate] = useState(false);
|
||||
@@ -121,6 +129,8 @@ const SealTable: React.FC<SealTableProps> = (props) => {
|
||||
rowSelection={rowSelection}
|
||||
expandable={expandable}
|
||||
rowKey={rowKey}
|
||||
renderChildren={renderChildren}
|
||||
loadChildren={loadChildren}
|
||||
onExpand={onExpand}
|
||||
></TableRow>
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
}
|
||||
.expanded-row {
|
||||
background-color: var(--color-white-1);
|
||||
padding: 16px 20px;
|
||||
padding: 16px 16px;
|
||||
border: 1px solid var(--color-fill-1);
|
||||
border-top: 0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ export interface SealTableProps {
|
||||
dataSource: any[];
|
||||
loading?: boolean;
|
||||
onExpand?: (expanded: boolean, record: any) => void;
|
||||
renderChildren?: (data: any) => React.ReactNode;
|
||||
loadChildren?: (record: any) => Promise<any[]>;
|
||||
rowKey: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
||||
declare namespace Global {
|
||||
interface Pagination {
|
||||
page: number;
|
||||
perPage: number;
|
||||
perPage?: number;
|
||||
watch?: boolean;
|
||||
}
|
||||
interface PageResponse<T> {
|
||||
|
||||
@@ -29,3 +29,11 @@ export const StatusColorMap: Record<StatusType, { text: string; bg: string }> =
|
||||
bg: `var(--ant-color-fill)`
|
||||
}
|
||||
};
|
||||
|
||||
export const StatusMaps = {
|
||||
error: 'error',
|
||||
warning: 'warning',
|
||||
transitioning: 'transitioning',
|
||||
success: 'success',
|
||||
inactive: 'inactive'
|
||||
};
|
||||
|
||||
@@ -164,12 +164,21 @@ const Models: React.FC = () => {
|
||||
dataIndex="GPU"
|
||||
key="GPU"
|
||||
render={(text, record: ListItem) => {
|
||||
return <RenderProgress percent={0}></RenderProgress>;
|
||||
return (
|
||||
<RenderProgress
|
||||
percent={_.get(record, [
|
||||
'status',
|
||||
'gpu',
|
||||
'0',
|
||||
'core_utilization_rate'
|
||||
])}
|
||||
></RenderProgress>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Column
|
||||
title="VRAM"
|
||||
dataIndex="GRAM"
|
||||
dataIndex="VRAM"
|
||||
key="VRAM"
|
||||
render={(text, record: ListItem) => {
|
||||
return <RenderProgress percent={0}></RenderProgress>;
|
||||
|
||||
@@ -3,27 +3,7 @@ import { memo, useMemo } from 'react';
|
||||
|
||||
const RenderProgress = memo((props: { percent: number }) => {
|
||||
const { percent } = props;
|
||||
// const { record, dataIndex } = props;
|
||||
// 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;
|
||||
console.log('percent====', percent);
|
||||
const strokeColor = useMemo(() => {
|
||||
if (percent <= 50) {
|
||||
return 'var(--ant-color-primary)';
|
||||
@@ -35,7 +15,7 @@ const RenderProgress = memo((props: { percent: number }) => {
|
||||
}, [percent]);
|
||||
return (
|
||||
<Progress
|
||||
steps={5}
|
||||
steps={10}
|
||||
format={() => {
|
||||
return (
|
||||
<span style={{ color: 'var(--ant-color-text)' }}>{percent}%</span>
|
||||
|
||||
@@ -4,14 +4,8 @@ import { Tabs } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import GPUs from './components/gpus';
|
||||
import Nodes from './components/nodes';
|
||||
import Test from './components/test';
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
{
|
||||
key: 'test',
|
||||
label: 'Test',
|
||||
children: <Test />
|
||||
},
|
||||
{
|
||||
key: 'nodes',
|
||||
label: 'Nodes',
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
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 MODEL_INSTANCE_API = '/model_instances';
|
||||
|
||||
// ===================== Models =====================
|
||||
export async function queryModelsList(
|
||||
params: Global.Pagination & { query?: string }
|
||||
) {
|
||||
@@ -31,3 +34,60 @@ export async function updateModel(params: { id: number; data: FormData }) {
|
||||
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 =====================
|
||||
|
||||
@@ -47,17 +47,14 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
name="huggingface_repo_id"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<SealInput.Input label="Huggingface ID" required></SealInput.Input>
|
||||
<SealInput.Input label="Repo ID" required></SealInput.Input>
|
||||
</Form.Item>
|
||||
{/* <Form.Item<FormData>
|
||||
<Form.Item<FormData>
|
||||
name="huggingface_filename"
|
||||
rules={[{ required: true }]}
|
||||
rules={[{ required: false }]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="Huggingface File Name"
|
||||
required
|
||||
></SealInput.Input>
|
||||
</Form.Item> */}
|
||||
<SealInput.Input label="File Name" required></SealInput.Input>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { StatusMaps } from '@/config';
|
||||
|
||||
export const modelInstanceCols = [
|
||||
{
|
||||
title: 'Name',
|
||||
@@ -29,3 +31,7 @@ export const modelInstanceCols = [
|
||||
key: 'Operation'
|
||||
}
|
||||
];
|
||||
|
||||
export const status: any = {
|
||||
Running: StatusMaps.success
|
||||
};
|
||||
|
||||
@@ -18,3 +18,21 @@ export interface FormData {
|
||||
name: 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;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import PageTools from '@/components/page-tools';
|
||||
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 StatusTag from '@/components/status-tag';
|
||||
import { PageAction } from '@/config';
|
||||
import type { PageActionType } from '@/config/types';
|
||||
import useTableRowSelection from '@/hooks/use-table-row-selection';
|
||||
import useTableSort from '@/hooks/use-table-sort';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
FieldTimeOutlined,
|
||||
PlusOutlined,
|
||||
SyncOutlined,
|
||||
WechatWorkOutlined
|
||||
@@ -16,21 +19,28 @@ import { Access, useAccess, useIntl, useNavigate } from '@umijs/max';
|
||||
import {
|
||||
App,
|
||||
Button,
|
||||
Col,
|
||||
Input,
|
||||
Modal,
|
||||
Progress,
|
||||
Row,
|
||||
Space,
|
||||
Table,
|
||||
Tooltip,
|
||||
message
|
||||
} from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import _ from 'lodash';
|
||||
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 { FormData, ListItem } from './config/types';
|
||||
const { Column } = Table;
|
||||
import { status } from './config';
|
||||
import { FormData, ListItem, ModelInstanceListItem } from './config/types';
|
||||
|
||||
const Models: React.FC = () => {
|
||||
const { modal } = App.useApp();
|
||||
@@ -152,7 +162,85 @@ const Models: React.FC = () => {
|
||||
|
||||
const handleOpenPlayGround = (row: any) => {
|
||||
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
|
||||
@@ -217,6 +305,8 @@ const Models: React.FC = () => {
|
||||
rowKey="id"
|
||||
expandable={true}
|
||||
onChange={handleTableChange}
|
||||
loadChildren={getModelInstances}
|
||||
renderChildren={renderChildren}
|
||||
pagination={{
|
||||
showSizeChanger: true,
|
||||
pageSize: queryParams.perPage,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const Roles = {
|
||||
User: 'User',
|
||||
Assistant: 'Assistant'
|
||||
User: 'user',
|
||||
Assistant: 'assistant'
|
||||
};
|
||||
export const playGroundRoles = [
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user