chore: models list re-render

main
jialin 2 years ago
parent 8c66c482b2
commit b753528e44

@ -8,8 +8,9 @@ import {
xAxis,
yAxis
} from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import { memo, useEffect, useState } from 'react';
import { memo } from 'react';
import { ChartProps } from './types';
const BarChart: React.FC<ChartProps> = (props) => {
@ -22,7 +23,10 @@ const BarChart: React.FC<ChartProps> = (props) => {
legendData,
title
} = props;
const [dataOptions, setDataOptions] = useState({});
if (!seriesData.length) {
return <EmptyData height={height} title={title}></EmptyData>;
}
const options = {
title: {
@ -51,8 +55,7 @@ const BarChart: React.FC<ChartProps> = (props) => {
series: []
};
useEffect(() => {
const setDataOptions = () => {
const data = _.map(seriesData, (item: any) => {
return {
...item,
@ -63,7 +66,7 @@ const BarChart: React.FC<ChartProps> = (props) => {
}
};
});
const optionsConfig = {
return {
...options,
animation: false,
title: {
@ -79,9 +82,10 @@ const BarChart: React.FC<ChartProps> = (props) => {
},
series: data
};
console.log('optionsConfig=========', optionsConfig);
setDataOptions(optionsConfig);
}, [seriesData, xAxisData, title]);
};
const dataOptions: any = setDataOptions();
return (
<Chart
height={height}

@ -7,8 +7,9 @@ import {
xAxis,
yAxis
} from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import { memo, useEffect, useState } from 'react';
import { memo } from 'react';
import { ChartProps } from './types';
const BarChart: React.FC<ChartProps> = (props) => {
@ -21,7 +22,9 @@ const BarChart: React.FC<ChartProps> = (props) => {
legendData,
title
} = props;
const [dataOptions, setDataOptions] = useState({});
if (!seriesData.length) {
return <EmptyData height={height} title={title}></EmptyData>;
}
const options = {
title: titleConfig,
@ -45,7 +48,7 @@ const BarChart: React.FC<ChartProps> = (props) => {
series: []
};
useEffect(() => {
const setDataOptions = () => {
const data = _.map(seriesData, (item: any) => {
return {
...item,
@ -60,9 +63,8 @@ const BarChart: React.FC<ChartProps> = (props) => {
color: item.color
}
};
return item;
});
const optionsConfig = {
return {
...options,
animation: false,
title: {
@ -101,9 +103,10 @@ const BarChart: React.FC<ChartProps> = (props) => {
},
series: data
};
console.log('optionsConfig========h=', optionsConfig);
setDataOptions(optionsConfig);
}, [seriesData, xAxisData, title]);
};
const dataOptions: any = setDataOptions();
return (
<Chart
height={height}

@ -8,6 +8,7 @@ import {
xAxis,
yAxis
} from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import { memo } from 'react';
import { ChartProps } from './types';
@ -19,10 +20,13 @@ const LineChart: React.FC<ChartProps> = (props) => {
height,
width,
labelFormatter,
legendData,
legendData = [],
smooth,
title
} = props;
if (!seriesData.length) {
return <EmptyData height={height} title={title}></EmptyData>;
}
const options = {
title: {
@ -42,7 +46,7 @@ const LineChart: React.FC<ChartProps> = (props) => {
yAxis,
legend: {
...legend,
data: []
data: legendData
},
series: []

@ -15,8 +15,8 @@ const EmptyData: React.FC<{
>
{title && (
<h3
className="justify-center"
style={{ padding: '16px 0', marginBottom: 0 }}
className="justify-center font-size-12"
style={{ padding: '4px 0', marginBottom: 0 }}
>
{title}
</h3>
@ -25,7 +25,7 @@ const EmptyData: React.FC<{
className="flex-center justify-center flex-column"
style={{ height: '100%' }}
>
<Empty />
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
</div>
</div>
);

@ -8,14 +8,14 @@ import './index.less';
interface LogsViewerProps {
height: number;
content: string;
content?: string;
url: string;
params?: object;
}
const LogsViewer: React.FC<LogsViewerProps> = (props) => {
const { height, content, url } = props;
const [nowrap, setNowrap] = useState(false);
const [logsContent, setLogsContent] = useState(content);
const [logsContent, setLogsContent] = useState(content || '');
const { setChunkRequest } = useSetChunkRequest();
const chunkRequedtRef = useRef<any>(null);
const scroller = useRef<any>(null);

@ -14,6 +14,7 @@ const RenderProgress = memo(
if (download) {
return 'var(--ant-color-primary)';
}
if (percent <= 50) {
return 'var(--color-progress-green)';
}

@ -1,4 +1,4 @@
import { AutoComplete, Form } from 'antd';
import { AutoComplete, Form, Spin } from 'antd';
import type { AutoCompleteProps } from 'antd/lib';
import { useEffect, useRef, useState } from 'react';
import Wrapper from './components/wrapper';
@ -17,6 +17,7 @@ const SealAutoComplete: React.FC<AutoCompleteProps & SealFormItemProps> = (
extra,
style,
addAfter,
loading,
...rest
} = props;
const [isFocus, setIsFocus] = useState(false);
@ -63,6 +64,12 @@ const SealAutoComplete: React.FC<AutoCompleteProps & SealFormItemProps> = (
const handleOnSelect = (value: any, option: any) => {
onSelect?.(value, option);
};
const renderAfter = () => {
if (loading) {
return <Spin size="small"></Spin>;
}
return addAfter;
};
return (
<Wrapper
@ -73,12 +80,19 @@ const SealAutoComplete: React.FC<AutoCompleteProps & SealFormItemProps> = (
required={required}
description={description}
disabled={props.disabled}
addAfter={addAfter}
addAfter={renderAfter()}
onClick={handleClickWrapper}
>
<AutoComplete
{...rest}
ref={inputRef}
placeholder={
isFocus ? (
<span style={{ paddingLeft: '12px' }}>{placeholder}</span>
) : (
''
)
}
onSelect={handleOnSelect}
onFocus={handleOnFocus}
onBlur={handleOnBlur}

@ -7,4 +7,5 @@ export interface SealFormItemProps {
description?: React.ReactNode;
extra?: React.ReactNode;
addAfter?: React.ReactNode;
loading?: React.ReactNode;
}

@ -33,8 +33,8 @@ const TableRow: React.FC<
const [loading, setLoading] = useState(false);
const pollTimer = useRef<any>(null);
const chunkRequestRef = useRef<any>(null);
const childrenRendered = useRef<any>(null);
console.log('table row====');
const { updateChunkedList } = useUpdateChunkedList(childrenData, {
setDataList: setChildrenData
});
@ -228,7 +228,11 @@ const TableRow: React.FC<
{expanded && (
<div className="expanded-row">
<Spin spinning={loading}>
{childrenData.length ? renderChildrenData() : <Empty></Empty>}
{childrenData.length ? (
renderChildrenData()
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>
)}
</Spin>
</div>
)}

@ -1,7 +1,7 @@
import { RightOutlined } from '@ant-design/icons';
import { Button, Checkbox, Col, Empty, Row, Spin } from 'antd';
import _ from 'lodash';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import TableHeader from './components/table-header';
import TableRow from './components/table-row';
import './styles/index.less';
@ -21,9 +21,10 @@ const SealTable: React.FC<SealTableProps> = (props) => {
loadChildren,
loadChildrenAPI
} = props;
console.log('sealtable====');
const [selectAll, setSelectAll] = useState(false);
const [indeterminate, setIndeterminate] = useState(false);
const tableContent = useRef(null);
useEffect(() => {
if (rowSelection) {
@ -121,12 +122,12 @@ const SealTable: React.FC<SealTableProps> = (props) => {
if (!props.dataSource.length) {
return (
<div className="empty-wrapper">
<Empty></Empty>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>
</div>
);
}
return (
<div className="seal-table-content">
<div className="seal-table-content" ref={tableContent}>
{props.dataSource.map((item, index) => {
return (
<TableRow
@ -163,4 +164,4 @@ const SealTable: React.FC<SealTableProps> = (props) => {
);
};
export default SealTable;
export default React.memo(SealTable);

@ -36,6 +36,7 @@ export interface SealTableProps {
renderChildren?: (data: any) => React.ReactNode;
loadChildren?: (record: any) => Promise<any[]>;
loadChildrenAPI?: (record: any) => string;
contentRendered?: () => void;
rowKey: string;
}

@ -7,8 +7,13 @@ export default function useTableRowSelection() {
setSelectedRowKeys(selectedRowKeys);
};
const clearSelections = () => {
setSelectedRowKeys([]);
};
const rowSelection = {
selectedRowKeys,
clearSelections,
onChange: onSelectChange
};

@ -42,31 +42,38 @@ export function useUpdateChunkedList(
);
if (updateIndex === -1) {
const updateItem = _.cloneDeep(item);
const list = _.concat(updateItem, dataList);
options.setDataList(list);
options.setDataList((preDataList: any) => {
return _.concat(updateItem, preDataList);
});
}
});
}
// DELETE
if (data?.type === WatchEventType.DELETE) {
const list = _.filter(dataList, (item: any) => {
return !_.find(ids, (id: any) => id === item.id);
options.setDataList((prevDataList: any) => {
const updatedList = _.filter(prevDataList, (item: any) => {
return !_.find(ids, (id: any) => id === item.id);
});
return updatedList;
});
options.setDataList(list);
}
// UPDATE
if (data?.type === WatchEventType.UPDATE) {
_.each(collections, (item: any) => {
const updateIndex = _.findIndex(
dataList,
(sItem: any) => sItem.id === item.id
);
console.log('updateIndex===========', dataList, updateIndex, data);
if (updateIndex > -1) {
const updateItem = _.cloneDeep(item);
dataList[updateIndex] = updateItem;
}
options.setDataList(dataList);
options.setDataList((prevDataList: any[]) => {
const updatedDataList = _.cloneDeep(prevDataList);
_.each(collections, (item: any) => {
const updateIndex = _.findIndex(
updatedDataList,
(sItem: any) => sItem.id === item.id
);
if (updateIndex > -1) {
const updateItem = _.cloneDeep(item);
updatedDataList[updateIndex] = updateItem;
}
});
return updatedDataList;
});
}
if (options?.callback) {

@ -12,5 +12,6 @@ export default {
'models.form.s3address': 'S3 Address',
'models.openinplayground': 'Open in Playground',
'models.instances': 'instances',
'model.form.ollama.model': 'Ollama Model'
'model.form.ollama.model': 'Ollama Model',
'model.form.ollamaholder': 'Please select or input model name'
};

@ -14,5 +14,7 @@ export default {
'resources.table.temperature': 'Temperature',
'resources.table.core': 'Core',
'resources.table.gpuutilization': 'GPU Utilization',
'resources.table.vramutilization': 'VRAM Utilization'
'resources.table.vramutilization': 'VRAM Utilization',
'resources.table.total': 'Total',
'resources.table.used': 'Used'
};

@ -12,5 +12,6 @@ export default {
'models.form.s3address': 'S3 地址',
'models.openinplayground': '在 Playground 中打开',
'models.instances': '实例',
'model.form.ollama.model': 'Ollama 模型'
'model.form.ollama.model': 'Ollama 模型',
'model.form.ollamaholder': '请选择或输入模型名称'
};

@ -13,6 +13,6 @@ export default {
'playground.completion': '补全',
'playground.prompt': '提示',
'playground.tokenusage': 'Token 使用量',
'models.openinplayground': '试验场打开',
'models.openinplayground': '打开试验场',
'playground.tokenoutput': '输出'
};

@ -14,5 +14,7 @@ export default {
'resources.table.temperature': '温度',
'resources.table.core': '核数',
'resources.table.gpuutilization': 'GPU 利用率',
'resources.table.vramutilization': '显存利用率'
'resources.table.vramutilization': '显存利用率',
'resources.table.total': '总量',
'resources.table.used': '已用'
};

@ -142,6 +142,7 @@ const Models: React.FC = () => {
async onOk() {
await handleBatchRequest(rowSelection.selectedRowKeys, deleteApisKey);
message.success(intl.formatMessage({ id: 'common.message.success' }));
rowSelection.clearSelections();
fetchData();
},
onCancel() {

@ -1,5 +1,5 @@
import Chart from '@/components/echarts/chart';
import { getLocale, useIntl } from '@umijs/max';
import LineChart from '@/components/echarts/line-chart';
import { useIntl } from '@umijs/max';
import dayjs from 'dayjs';
import _ from 'lodash';
import { memo, useContext } from 'react';
@ -112,8 +112,6 @@ const option = {
const UtilizationOvertime: React.FC = () => {
console.log('systemload=====================');
const intl = useIntl();
const locale = getLocale();
let dataOptions: any = {};
const data = useContext(DashboardContext)?.system_load?.history || {};
const typeList = ['gpu', 'cpu', 'memory', 'gpu_memory'];
@ -121,9 +119,8 @@ const UtilizationOvertime: React.FC = () => {
const generateData = () => {
const legendData: string[] = [];
const xAxisData: string[] = [];
let list: { value: number; time: string; type: string }[] = [];
list = _.map(typeList, (item: string) => {
let seriesData: { value: number; time: string; type: string }[] = [];
seriesData = _.map(typeList, (item: string) => {
const itemConfig = _.get(TypeKeyMap, item, {});
const name = itemConfig.intl
? intl.formatMessage({ id: itemConfig.label })
@ -131,18 +128,8 @@ const UtilizationOvertime: React.FC = () => {
legendData.push(name);
const itemDataList = _.get(data, item, []);
return {
type: 'line',
smooth: true,
showSymbol: false,
itemStyle: {
color: itemConfig.color
},
lineStyle: {
width: 1.5,
opacity: 0.7,
color: itemConfig.color
},
name: name,
color: itemConfig.color,
data: _.map(itemDataList, (item: any) => {
xAxisData.push(dayjs(item.timestamp * 1000).format('HH:mm:ss'));
return {
@ -152,26 +139,24 @@ const UtilizationOvertime: React.FC = () => {
})
};
});
dataOptions = {
...option,
animation: false,
legend: {
...option.legend,
data: legendData
},
xAxis: {
...option.xAxis,
data: _.uniq(xAxisData)
},
series: list
return {
seriesData,
legendData,
xAxisData: _.uniq(xAxisData)
};
};
generateData();
const { seriesData, legendData, xAxisData } = generateData();
return (
<>
<Chart height={390} options={dataOptions} width="100%"></Chart>
<LineChart
height={390}
seriesData={seriesData}
legendData={legendData}
xAxisData={xAxisData}
smooth={true}
width="100%"
></LineChart>
</>
);
};

@ -3,7 +3,7 @@ import PageTools from '@/components/page-tools';
import breakpoints from '@/config/breakpoints';
import useWindowResize from '@/hooks/use-window-resize';
import { useIntl } from '@umijs/max';
import { Col, DatePicker, Row } from 'antd';
import { Col, Row } from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useContext, useEffect, useState } from 'react';
@ -40,13 +40,6 @@ const SystemLoad = () => {
left={
<span>{intl.formatMessage({ id: 'dashboard.systemload' })}</span>
}
right={
<DatePicker
onChange={handleSelectDate}
style={{ width: 300 }}
defaultValue={dayjs()}
/>
}
/>
<Row style={{ width: '100%' }} gutter={[0, 20]}>
<Col

@ -1,7 +1,7 @@
import PageTools from '@/components/page-tools';
import { useIntl } from '@umijs/max';
import { Col, DatePicker, Row } from 'antd';
import { Col, Row } from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import { memo, useCallback, useContext } from 'react';
@ -87,7 +87,6 @@ const UsageInner: React.FC<{ paddingRight: string }> = ({ paddingRight }) => {
color: baseColorMap.base,
data: []
};
const users: string[] = [];
_.each(xAxisData, (date: string) => {
// tokens data
@ -140,22 +139,27 @@ const UsageInner: React.FC<{ paddingRight: string }> = ({ paddingRight }) => {
});
// ========== top users ============
_.each(data.top_users, (item: any) => {
users.push(item.username);
topUserPrompt.data.push({
name: item.username,
value: item.prompt_token_count
});
topUserCompletion.data.push({
name: item.username,
value: item.completion_token_count
if (!data.top_users?.length) {
userData = [];
topUserList = [];
} else {
const users: string[] = [];
_.each(data.top_users, (item: any) => {
users.push(item.username);
topUserPrompt.data.push({
name: item.username,
value: item.prompt_token_count
});
topUserCompletion.data.push({
name: item.username,
value: item.completion_token_count
});
});
});
topUserList = _.uniq(users);
userData = [topUserCompletion, topUserPrompt];
}
requestData = [requestList];
userData = [topUserCompletion, topUserPrompt];
topUserList = users;
tokenData = [completionData, prompData];
}, [data, xAxisData]);
@ -166,14 +170,6 @@ const UsageInner: React.FC<{ paddingRight: string }> = ({ paddingRight }) => {
<PageTools
style={{ margin: '26px 0px' }}
left={<span>{intl.formatMessage({ id: 'dashboard.usage' })}</span>}
right={
<DatePicker
onChange={handleSelectDate}
style={{ width: 300 }}
picker="month"
defaultValue={currentDate}
/>
}
/>
<Row style={{ width: '100%' }} gutter={[0, 20]}>
<Col

@ -18,8 +18,8 @@ export async function queryModelsList(
) {
return request<Global.PageResponse<ListItem>>(`${MODELS_API}`, {
methos: 'GET',
params,
...options
...options,
params
});
}

@ -9,7 +9,7 @@ import { SearchOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Form, Input, Modal } from 'antd';
import _ from 'lodash';
import { useEffect, useState } from 'react';
import { memo, useEffect, useState } from 'react';
import {
callHuggingfaceQuickSearch,
queryHuggingfaceModelFiles
@ -27,14 +27,16 @@ type AddModalProps = {
};
const sourceOptions = [
{ label: 'Huggingface', value: 'huggingface', key: 'huggingface' },
{ label: 'Hugging Face', value: 'huggingface', key: 'huggingface' },
{ label: 'Ollama Library', value: 'ollama_library', key: 'ollama_library' }
];
const AddModal: React.FC<AddModalProps> = (props) => {
console.log('addmodel====');
const { title, action, open, onOk, onCancel } = props || {};
const [form] = Form.useForm();
const intl = useIntl();
const [loading, setLoading] = useState(false);
const modelSource = Form.useWatch('source', form);
const [repoOptions, setRepoOptions] = useState<
{ label: string; value: string }[]
@ -77,6 +79,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
};
const handleFetchModelFiles = async (repo: string) => {
try {
setLoading(true);
const res = await queryHuggingfaceModelFiles({ repo });
const list = _.filter(res, (file: any) => {
return _.endsWith(file.path, '.gguf');
@ -88,8 +91,10 @@ const AddModal: React.FC<AddModalProps> = (props) => {
};
});
setFileOptions(list);
setLoading(false);
} catch (error) {
setFileOptions([]);
setLoading(false);
}
};
@ -143,6 +148,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
required
showSearch
onBlur={handleRepoOnBlur}
onSelect={handleRepoOnBlur}
onChange={handleInputRepoChange}
onSearch={debounceSearch}
options={repoOptions}
@ -176,6 +182,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
label={intl.formatMessage({ id: 'models.form.filename' })}
required
options={fileOptions}
loading={loading}
onFocus={handleRepoOnBlur}
disabled={action === PageAction.EDIT}
></SealAutoComplete>
@ -233,6 +240,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
filterOption
disabled={action === PageAction.EDIT}
label={intl.formatMessage({ id: 'model.form.ollama.model' })}
placeholder={intl.formatMessage({ id: 'model.form.ollamaholder' })}
required
options={ollamaModelOptions}
></SealAutoComplete>
@ -362,4 +370,4 @@ const AddModal: React.FC<AddModalProps> = (props) => {
);
};
export default AddModal;
export default memo(AddModal);

@ -0,0 +1,104 @@
import DropdownButtons from '@/components/drop-down-buttons';
import RowChildren from '@/components/seal-table/components/row-children';
import StatusTag from '@/components/status-tag';
import { DeleteOutlined, FieldTimeOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Col, Row, Space } from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import React from 'react';
import { status } from '../config';
import { ModelInstanceListItem } from '../config/types';
interface InstanceItemProps {
list: ModelInstanceListItem[];
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
}
const InstanceItem: React.FC<InstanceItemProps> = ({
list,
handleChildSelect
}) => {
const intl = useIntl();
const childActionList = [
{
label: intl.formatMessage({ id: 'common.button.viewlog' }),
key: 'viewlog',
icon: <FieldTimeOutlined />
},
{
label: intl.formatMessage({ id: 'common.button.delete' }),
key: 'delete',
danger: true,
icon: <DeleteOutlined />
}
];
return (
<Space size={16} direction="vertical" style={{ width: '100%' }}>
{_.map(list, (item: ModelInstanceListItem, index: number) => {
return (
<div
key={`${item.id}`}
style={{ borderRadius: 'var(--ant-table-header-border-radius)' }}
className={
item.download_progress !== 100 && item.state !== 'Running'
? 'skeleton-loading'
: ''
}
>
<RowChildren key={`${item.id}_row`}>
<Row style={{ width: '100%' }} align="middle">
<Col span={4}>{item.name}</Col>
<Col span={3}>
{item.worker_ip ? `${item.worker_ip}:${item.port}` : '-'}
</Col>
<Col span={4}>
<span>
{item.source === 'huggingface'
? item.huggingface_filename
: item.ollama_library_model_name}
</span>
</Col>
<Col span={3}>
<span
style={{ paddingLeft: '0px' }}
className="flex justify-center"
>
{item.state && (
<StatusTag
download={
item.state !== 'Running'
? { percent: item.download_progress }
: undefined
}
statusValue={{
status: status[item.state] as any,
text: item.state
}}
></StatusTag>
)}
</span>
</Col>
<Col span={5}>
<span style={{ paddingLeft: 36 }}>
{dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss')}
</span>
</Col>
<Col span={5}>
<div style={{ paddingLeft: 39 }}>
<DropdownButtons
items={childActionList}
onSelect={(val: string) => handleChildSelect(val, item)}
></DropdownButtons>
</div>
</Col>
</Row>
</RowChildren>
</div>
);
})}
</Space>
);
};
export default React.memo(InstanceItem);

@ -0,0 +1,399 @@
import DropdownButtons from '@/components/drop-down-buttons';
import PageTools from '@/components/page-tools';
import SealTable from '@/components/seal-table';
import SealColumn from '@/components/seal-table/components/seal-column';
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 { handleBatchRequest } from '@/utils';
import {
DeleteOutlined,
EditOutlined,
PlusOutlined,
SyncOutlined,
WechatWorkOutlined
} from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { Access, useAccess, useIntl, useNavigate } from '@umijs/max';
import { Button, Input, Modal, Space, message } from 'antd';
import dayjs from 'dayjs';
import { memo, useCallback, useEffect, useState } from 'react';
import {
MODELS_API,
MODEL_INSTANCE_API,
createModel,
deleteModel,
deleteModelInstance,
queryModelInstancesList,
updateModel
} from '../apis';
import { modelSourceMap } from '../config';
import { FormData, ListItem, ModelInstanceListItem } from '../config/types';
import AddModal from './add-modal';
import InstanceItem from './instance-item';
import ViewLogsModal from './view-logs-modal';
interface ModelsProps {
handleSearch: (e: any) => void;
handleNameChange: (e: any) => void;
fetchData: () => Promise<any>;
handleShowSizeChange: (page: number, size: number) => void;
handlePageChange: (page: number, pageSize: number | undefined) => void;
createModelsChunkRequest: () => void;
queryParams: {
page: number;
perPage: number;
query?: string;
};
dataSource: ListItem[];
loading: boolean;
total: number;
}
const Models: React.FC<ModelsProps> = ({
dataSource,
handleNameChange,
handleSearch,
handleShowSizeChange,
handlePageChange,
createModelsChunkRequest,
queryParams,
loading,
total,
fetchData
}) => {
// const { modal } = App.useApp();
console.log('model list====2');
const access = useAccess();
const intl = useIntl();
const navigate = useNavigate();
const rowSelection = useTableRowSelection();
const { sortOrder, setSortOrder } = useTableSort({
defaultSortOrder: 'descend'
});
const [openLogModal, setOpenLogModal] = useState(false);
const [openAddModal, setOpenAddModal] = useState(false);
const [action, setAction] = useState<PageActionType>(PageAction.CREATE);
const [title, setTitle] = useState<string>('');
const [currentData, setCurrentData] = useState<ListItem | undefined>(
undefined
);
const [currentInstanceUrl, setCurrentInstanceUrl] = useState<string>('');
const ActionList = [
{
label: intl.formatMessage({ id: 'common.button.edit' }),
key: 'edit',
icon: <EditOutlined />
},
{
label: intl.formatMessage({ id: 'models.openinplayground' }),
key: 'chat',
icon: <WechatWorkOutlined />
},
{
label: intl.formatMessage({ id: 'common.button.delete' }),
key: 'delete',
danger: true,
icon: <DeleteOutlined />
}
];
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
console.log('handleTableChange=======', pagination, filters, sorter);
setSortOrder(sorter.order);
};
const handleAddModal = () => {
setOpenAddModal(true);
setAction(PageAction.CREATE);
setTitle(intl.formatMessage({ id: 'models.button.deploy' }));
};
const handleModalOk = useCallback(
async (data: FormData) => {
try {
if (action === PageAction.CREATE) {
await createModel({ data });
}
if (action === PageAction.EDIT) {
await updateModel({ data, id: currentData?.id as number });
}
setOpenAddModal(false);
message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) {}
},
[action, currentData]
);
const handleModalCancel = useCallback(() => {
console.log('handleModalCancel');
setOpenAddModal(false);
}, []);
const handleLogModalCancel = useCallback(() => {
setOpenLogModal(false);
}, []);
const handleDelete = async (row: any) => {
Modal.confirm({
title: '',
content: intl.formatMessage(
{ id: 'common.delete.confirm' },
{ type: intl.formatMessage({ id: 'models.table.models' }) }
),
async onOk() {
await deleteModel(row.id);
message.success(intl.formatMessage({ id: 'common.message.success' }));
},
onCancel() {
console.log('Cancel');
}
});
};
const handleDeleteBatch = () => {
Modal.confirm({
title: '',
content: intl.formatMessage(
{ id: 'common.delete.confirm' },
{ type: intl.formatMessage({ id: 'models.table.models' }) }
),
async onOk() {
await handleBatchRequest(rowSelection.selectedRowKeys, deleteModel);
message.success(intl.formatMessage({ id: 'common.message.success' }));
rowSelection.clearSelections();
},
onCancel() {
console.log('Cancel');
}
});
};
const handleOpenPlayGround = (row: any) => {
navigate(`/playground?model=${row.name}`);
};
const handleViewLogs = async (row: any) => {
try {
setCurrentInstanceUrl(`${MODEL_INSTANCE_API}/${row.id}/logs`);
setOpenLogModal(true);
} catch (error) {
console.log('error:', error);
}
};
const handleDeleteInstace = (row: any) => {
Modal.confirm({
title: '',
content: intl.formatMessage(
{ id: 'common.delete.confirm' },
{ type: intl.formatMessage({ id: 'models.instances' }) }
),
async onOk() {
await deleteModelInstance(row.id);
message.success(intl.formatMessage({ id: 'common.message.success' }));
},
onCancel() {
console.log('Cancel');
}
});
};
const getModelInstances = useCallback(async (row: any) => {
const params = {
id: row.id,
page: 1,
perPage: 100
};
const data = await queryModelInstancesList(params);
return data.items || [];
}, []);
const generateChildrenRequestAPI = (params: any) => {
return `${MODELS_API}/${params.id}/instances`;
};
const handleEdit = (row: ListItem) => {
setCurrentData(row);
setOpenAddModal(true);
setAction(PageAction.EDIT);
setTitle(intl.formatMessage({ id: 'models.title.edit' }));
};
const handleSelect = useCallback((val: any, row: ListItem) => {
if (val === 'edit') {
handleEdit(row);
}
if (val === 'chat') {
handleOpenPlayGround(row);
}
if (val === 'delete') {
handleDelete(row);
}
}, []);
const handleChildSelect = useCallback(
(val: any, row: ModelInstanceListItem) => {
if (val === 'delete') {
handleDeleteInstace(row);
}
if (val === 'viewlog') {
handleViewLogs(row);
}
},
[]
);
const renderChildren = (list: any) => {
return (
<InstanceItem
list={list}
handleChildSelect={handleChildSelect}
></InstanceItem>
);
};
useEffect(() => {
createModelsChunkRequest();
}, []);
return (
<>
<PageContainer
ghost
header={{
title: intl.formatMessage({ id: 'models.title' })
}}
extra={[]}
>
<PageTools
marginBottom={22}
left={
<Space>
<Input
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 300 }}
size="large"
allowClear
onChange={handleNameChange}
></Input>
<Button
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
onClick={handleSearch}
icon={<SyncOutlined></SyncOutlined>}
></Button>
</Space>
}
right={
<Space size={20}>
<Button
icon={<PlusOutlined></PlusOutlined>}
type="primary"
onClick={handleAddModal}
>
{intl?.formatMessage?.({ id: 'models.button.deploy' })}
</Button>
<Access accessible={access.canDelete}>
<Button
icon={<DeleteOutlined />}
danger
onClick={handleDeleteBatch}
disabled={!rowSelection.selectedRowKeys.length}
>
{intl?.formatMessage?.({ id: 'common.button.delete' })}
</Button>
</Access>
</Space>
}
></PageTools>
<SealTable
dataSource={dataSource}
rowSelection={rowSelection}
loading={loading}
rowKey="id"
expandable={true}
onChange={handleTableChange}
pollingChildren={false}
watchChildren={true}
loadChildren={getModelInstances}
loadChildrenAPI={generateChildrenRequestAPI}
renderChildren={renderChildren}
pagination={{
showSizeChanger: true,
pageSize: queryParams.perPage,
current: queryParams.page,
total: total,
hideOnSinglePage: true,
onShowSizeChange: handleShowSizeChange,
onChange: handlePageChange
}}
>
<SealColumn
title={intl.formatMessage({ id: 'common.table.name' })}
dataIndex="name"
key="name"
width={400}
span={6}
/>
<SealColumn
title={intl.formatMessage({ id: 'models.form.source' })}
dataIndex="source"
key="source"
span={4}
render={(text) => {
return modelSourceMap[text] || '-';
}}
/>
<SealColumn
title={intl.formatMessage({ id: 'models.form.replicas' })}
dataIndex="replicas"
key="replicas"
align="center"
span={4}
/>
<SealColumn
span={5}
title={intl.formatMessage({ id: 'common.table.createTime' })}
dataIndex="created_at"
key="createTime"
defaultSortOrder="descend"
sortOrder={sortOrder}
showSorterTooltip={false}
sorter={true}
render={(val, row) => {
return dayjs(val).format('YYYY-MM-DD HH:mm:ss');
}}
/>
<SealColumn
span={5}
title={intl.formatMessage({ id: 'common.table.operation' })}
key="operation"
render={(text, record) => {
return !record.transition ? (
<DropdownButtons
items={ActionList}
onSelect={(val) => handleSelect(val, record)}
></DropdownButtons>
) : null;
}}
/>
</SealTable>
</PageContainer>
<AddModal
open={openAddModal}
action={action}
title={title}
data={currentData}
onCancel={handleModalCancel}
onOk={handleModalOk}
></AddModal>
<ViewLogsModal
url={currentInstanceUrl}
open={openLogModal}
onCancel={handleLogModalCancel}
></ViewLogsModal>
</>
);
};
export default memo(Models);

@ -1,24 +1,25 @@
import LogsViewer from '@/components/logs-viewer';
import { useIntl } from '@umijs/max';
import { Modal } from 'antd';
import React from 'react';
type ViewModalProps = {
content: string;
title: string;
open: boolean;
url: string;
onCancel: () => void;
};
const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const { title, open, url, onCancel, content = '' } = props || {};
console.log('viewlogs======');
const { open, url, onCancel } = props || {};
const intl = useIntl();
if (!open) {
return null;
}
return (
<Modal
title={title}
title={intl.formatMessage({ id: 'common.button.viewlog' })}
open={open}
onCancel={onCancel}
destroyOnClose={true}
@ -30,15 +31,14 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
footer={null}
>
<LogsViewer
content={content}
height={500}
url={url}
params={{
follow: false
follow: true
}}
></LogsViewer>
</Modal>
);
};
export default ViewCodeModal;
export default React.memo(ViewCodeModal);

@ -12,10 +12,10 @@ export const ollamaModelOptions = [
{ label: 'deepseek-coder', value: 'deepseek-coder' }
];
export const modelSourceMap = {
huggingface: 'huggingface',
ollama_library: 'ollama_library',
s3: 's3'
export const modelSourceMap: Record<string, string> = {
huggingface: 'Hugging Face',
ollama_library: 'Ollama Library',
s3: 'S3'
};
export const status: any = {
@ -40,3 +40,12 @@ export const ActionList = [
icon: EditOutlined
}
];
export const InstanceStatusMap = {
Initializing: 'Initializing',
Pending: 'Pending',
Running: 'Running',
Scheduled: 'Scheduled',
Error: 'Error',
Downloading: 'Downloading'
};

@ -25,6 +25,7 @@ export interface ModelInstanceListItem {
source: string;
huggingface_repo_id: string;
huggingface_filename: string;
ollama_library_model_name: string;
s3_address: string;
worker_id: number;
worker_ip: string;

@ -1,105 +1,23 @@
import DropdownButtons from '@/components/drop-down-buttons';
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 useSetChunkRequest, {
createAxiosToken
} from '@/hooks/use-chunk-request';
import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort';
import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
import { handleBatchRequest } from '@/utils';
import {
DeleteOutlined,
EditOutlined,
FieldTimeOutlined,
PlusOutlined,
SyncOutlined,
WechatWorkOutlined
} from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { Access, useAccess, useIntl, useNavigate } from '@umijs/max';
import { Button, Col, Input, Modal, Row, Space, message } from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useCallback, useEffect, useRef, useState } from 'react';
import {
MODELS_API,
MODEL_INSTANCE_API,
createModel,
deleteModel,
deleteModelInstance,
queryModelInstancesList,
queryModelsList,
updateModel
} from './apis';
import AddModal from './components/add-modal';
import ViewLogsModal from './components/view-logs-modal';
import { status } from './config';
import { FormData, ListItem, ModelInstanceListItem } from './config/types';
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { MODELS_API, queryModelsList } from './apis';
import TableList from './components/table-list';
import { ListItem } from './config/types';
const Models: React.FC = () => {
// const { modal } = App.useApp();
const access = useAccess();
const intl = useIntl();
const navigate = useNavigate();
console.log('model list====1');
const { setChunkRequest } = useSetChunkRequest();
const rowSelection = useTableRowSelection();
const { sortOrder, setSortOrder } = useTableSort({
defaultSortOrder: 'descend'
});
const [logContent, setLogContent] = useState('');
const [openLogModal, setOpenLogModal] = useState(false);
const [hoverChildIndex, setHoverChildIndex] = useState<string | number>(-1);
const [total, setTotal] = useState(100);
const [openAddModal, setOpenAddModal] = useState(false);
const [loading, setLoading] = useState(false);
const [action, setAction] = useState<PageActionType>(PageAction.CREATE);
const [title, setTitle] = useState<string>('');
const [dataSource, setDataSource] = useState<ListItem[]>([]);
const [currentData, setCurrentData] = useState<ListItem | undefined>(
undefined
);
const [currentInstanceUrl, setCurrentInstanceUrl] = useState<string>('');
const ActionList = [
{
label: intl.formatMessage({ id: 'common.button.edit' }),
key: 'edit',
icon: <EditOutlined />
},
{
label: intl.formatMessage({ id: 'models.openinplayground' }),
key: 'chat',
icon: <WechatWorkOutlined />
},
{
label: intl.formatMessage({ id: 'common.button.delete' }),
key: 'delete',
danger: true,
icon: <DeleteOutlined />
}
];
const childActionList = [
{
label: intl.formatMessage({ id: 'common.button.viewlog' }),
key: 'viewlog',
icon: <FieldTimeOutlined />
},
{
label: intl.formatMessage({ id: 'common.button.delete' }),
key: 'delete',
danger: true,
icon: <DeleteOutlined />
}
];
const chunkRequedtRef = useRef<any>();
const timer = useRef<any>();
let axiosToken = createAxiosToken();
const [queryParams, setQueryParams] = useState({
page: 1,
@ -112,10 +30,10 @@ const Models: React.FC = () => {
setDataList: setDataSource
});
const fetchData = async (polling?: boolean) => {
const fetchData = useCallback(async () => {
axiosToken?.cancel?.();
axiosToken = createAxiosToken();
setLoading(!polling);
setLoading(true);
try {
const params = {
..._.pickBy(queryParams, (val: any) => !!val)
@ -127,44 +45,39 @@ const Models: React.FC = () => {
setDataSource(res.items);
setTotal(res.pagination.total);
} catch (error) {
setDataSource([]);
console.log('error', error);
} finally {
setLoading(false);
}
};
const handleShowSizeChange = (page: number, size: number) => {
console.log(page, size);
setQueryParams({
...queryParams,
perPage: size
});
};
const handlePageChange = (page: number, pageSize: number | undefined) => {
console.log(page, pageSize);
setQueryParams({
...queryParams,
page: page
});
};
}, [queryParams]);
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
console.log('handleTableChange=======', pagination, filters, sorter);
setSortOrder(sorter.order);
};
const handleShowSizeChange = useCallback(
(page: number, size: number) => {
console.log(page, size);
setQueryParams({
...queryParams,
perPage: size
});
},
[queryParams]
);
const handleFilter = () => {
fetchData();
};
const handlePageChange = useCallback(
(page: number, pageSize: number | undefined) => {
console.log(page, pageSize);
setQueryParams({
...queryParams,
page: page
});
},
[queryParams]
);
const updateHandler = (list: any) => {
_.each(list, (data: any) => {
updateChunkedList(data);
});
if (!dataSource.length) {
fetchData();
}
};
const createModelsChunkRequest = () => {
@ -182,374 +95,42 @@ const Models: React.FC = () => {
}
};
const handleSearch = (e: any) => {
const handleSearch = useCallback((e: any) => {
fetchData();
};
const handleNameChange = (e: any) => {
setQueryParams({
...queryParams,
query: e.target.value
});
};
const handleAddModal = () => {
setOpenAddModal(true);
setAction(PageAction.CREATE);
setTitle(intl.formatMessage({ id: 'models.button.deploy' }));
};
const handleModalOk = async (data: FormData) => {
try {
if (action === PageAction.CREATE) {
await createModel({ data });
}
if (action === PageAction.EDIT) {
await updateModel({ data, id: currentData?.id as number });
}
setOpenAddModal(false);
message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) {}
};
const handleModalCancel = () => {
console.log('handleModalCancel');
setOpenAddModal(false);
};
const handleLogModalCancel = () => {
setOpenLogModal(false);
};
const handleDelete = async (row: any) => {
Modal.confirm({
title: '',
content: intl.formatMessage(
{ id: 'common.delete.confirm' },
{ type: intl.formatMessage({ id: 'models.table.models' }) }
),
async onOk() {
await deleteModel(row.id);
message.success(intl.formatMessage({ id: 'common.message.success' }));
fetchData();
},
onCancel() {
console.log('Cancel');
}
});
};
const handleDeleteBatch = () => {
Modal.confirm({
title: '',
content: intl.formatMessage(
{ id: 'common.delete.confirm' },
{ type: intl.formatMessage({ id: 'models.table.models' }) }
),
async onOk() {
await handleBatchRequest(rowSelection.selectedRowKeys, deleteModel);
message.success(intl.formatMessage({ id: 'common.message.success' }));
fetchData();
},
onCancel() {
console.log('Cancel');
}
});
};
const handleOpenPlayGround = (row: any) => {
navigate(`/playground?model=${row.name}`);
};
const handleViewLogs = async (row: any) => {
try {
setCurrentInstanceUrl(`${MODEL_INSTANCE_API}/${row.id}/logs`);
setOpenLogModal(true);
} catch (error) {
console.log('error:', error);
}
};
const handleDeleteInstace = (row: any) => {
Modal.confirm({
title: '',
content: intl.formatMessage(
{ id: 'common.delete.confirm' },
{ type: intl.formatMessage({ id: 'models.instances' }) }
),
async onOk() {
await deleteModelInstance(row.id);
message.success(intl.formatMessage({ id: 'common.message.success' }));
fetchData();
},
onCancel() {
console.log('Cancel');
}
});
};
const handleOnMouseEnter = (id: number, index: number) => {
setHoverChildIndex(`${id}-${index}`);
};
const handleOnMouseLeave = () => {
setHoverChildIndex(-1);
};
const getModelInstances = useCallback(async (row: any) => {
const params = {
id: row.id,
page: 1,
perPage: 100
};
const data = await queryModelInstancesList(params);
return data.items || [];
}, []);
const generateChildrenRequestAPI = (params: any) => {
return `${MODELS_API}/${params.id}/instances`;
};
const handleEdit = (row: ListItem) => {
setCurrentData(row);
setOpenAddModal(true);
setAction(PageAction.EDIT);
setTitle(intl.formatMessage({ id: 'models.title.edit' }));
};
const handleSelect = (val: any, row: ListItem) => {
if (val === 'edit') {
handleEdit(row);
}
if (val === 'chat') {
handleOpenPlayGround(row);
}
if (val === 'delete') {
handleDelete(row);
}
};
const handleNameChange = useCallback(
(e: any) => {
setQueryParams({
...queryParams,
query: e.target.value
});
},
[queryParams]
);
const handleChildSelect = (val: any, row: ModelInstanceListItem) => {
if (val === 'delete') {
handleDeleteInstace(row);
}
if (val === 'viewlog') {
handleViewLogs(row);
}
};
useEffect(() => {
fetchData();
return () => {
axiosToken?.cancel?.();
};
}, [queryParams]);
useEffect(() => {
createModelsChunkRequest();
return () => {
chunkRequedtRef.current?.current?.cancel?.();
axiosToken?.cancel?.();
};
}, [queryParams]);
const renderChildren = (list: any) => {
return (
<Space size={16} direction="vertical" style={{ width: '100%' }}>
{_.map(list, (item: ModelInstanceListItem, index: number) => {
return (
<div
key={`${item.id}`}
onMouseEnter={() => handleOnMouseEnter(item.id, index)}
onMouseLeave={handleOnMouseLeave}
style={{ borderRadius: 'var(--ant-table-header-border-radius)' }}
className={
item.download_progress !== 100 && item.state !== 'Running'
? 'skeleton-loading'
: ''
}
>
<RowChildren key={`${item.id}_row`}>
<Row style={{ width: '100%' }} align="middle">
<Col span={6}>{item.name}</Col>
<Col span={4}>
<span>{item.huggingface_filename}</span>
</Col>
<Col span={4}>
<span
style={{ paddingLeft: '68px' }}
className="flex justify-center"
>
{item.state && (
<StatusTag
download={
item.state !== 'Running'
? { percent: item.download_progress }
: undefined
}
statusValue={{
status: status[item.state] as any,
text: item.state
}}
></StatusTag>
)}
</span>
</Col>
<Col span={5}>
<span style={{ paddingLeft: 36 }}>
{dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss')}
</span>
</Col>
<Col span={5}>
<div style={{ paddingLeft: 39 }}>
<DropdownButtons
items={childActionList}
onSelect={(val) => handleChildSelect(val, item)}
></DropdownButtons>
</div>
</Col>
</Row>
</RowChildren>
</div>
);
})}
</Space>
);
};
return (
<>
<PageContainer
ghost
header={{
title: intl.formatMessage({ id: 'models.title' })
}}
extra={[]}
>
<PageTools
marginBottom={22}
left={
<Space>
<Input
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 300 }}
size="large"
allowClear
onChange={handleNameChange}
></Input>
<Button
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
onClick={handleSearch}
icon={<SyncOutlined></SyncOutlined>}
></Button>
</Space>
}
right={
<Space size={20}>
<Button
icon={<PlusOutlined></PlusOutlined>}
type="primary"
onClick={handleAddModal}
>
{intl?.formatMessage?.({ id: 'models.button.deploy' })}
</Button>
<Access accessible={access.canDelete}>
<Button
icon={<DeleteOutlined />}
danger
onClick={handleDeleteBatch}
disabled={!rowSelection.selectedRowKeys.length}
>
{intl?.formatMessage?.({ id: 'common.button.delete' })}
</Button>
</Access>
</Space>
}
></PageTools>
<SealTable
dataSource={dataSource}
rowSelection={rowSelection}
loading={loading}
rowKey="id"
expandable={true}
onChange={handleTableChange}
pollingChildren={false}
watchChildren={true}
loadChildren={getModelInstances}
loadChildrenAPI={generateChildrenRequestAPI}
renderChildren={renderChildren}
pagination={{
showSizeChanger: true,
pageSize: queryParams.perPage,
current: queryParams.page,
total: total,
hideOnSinglePage: true,
onShowSizeChange: handleShowSizeChange,
onChange: handlePageChange
}}
>
<SealColumn
title={intl.formatMessage({ id: 'common.table.name' })}
dataIndex="name"
key="name"
width={400}
span={6}
/>
<SealColumn
title={intl.formatMessage({ id: 'models.form.source' })}
dataIndex="source"
key="source"
span={4}
/>
<SealColumn
title={intl.formatMessage({ id: 'models.form.replicas' })}
dataIndex="replicas"
key="replicas"
align="center"
span={4}
/>
<SealColumn
span={5}
title={intl.formatMessage({ id: 'common.table.createTime' })}
dataIndex="created_at"
key="createTime"
defaultSortOrder="descend"
sortOrder={sortOrder}
showSorterTooltip={false}
sorter={true}
render={(val, row) => {
return dayjs(val).format('YYYY-MM-DD HH:mm:ss');
}}
/>
<SealColumn
span={5}
title={intl.formatMessage({ id: 'common.table.operation' })}
key="operation"
render={(text, record) => {
return !record.transition ? (
<DropdownButtons
items={ActionList}
onSelect={(val) => handleSelect(val, record)}
></DropdownButtons>
) : null;
}}
/>
</SealTable>
</PageContainer>
<AddModal
open={openAddModal}
action={action}
title={title}
data={currentData}
onCancel={handleModalCancel}
onOk={handleModalOk}
></AddModal>
<ViewLogsModal
url={currentInstanceUrl}
title={intl.formatMessage({ id: 'common.button.viewlog' })}
open={openLogModal}
content={logContent}
onCancel={handleLogModalCancel}
></ViewLogsModal>
</>
<TableList
dataSource={dataSource}
handleNameChange={handleNameChange}
handleSearch={handleSearch}
handleShowSizeChange={handleShowSizeChange}
handlePageChange={handlePageChange}
createModelsChunkRequest={createModelsChunkRequest}
queryParams={queryParams}
loading={loading}
total={total}
fetchData={fetchData}
></TableList>
);
};
export default Models;
export default memo(Models);

@ -19,29 +19,31 @@ const ReferenceParams = (props: ReferenceParamsProps) => {
}
return (
<div className="reference-params">
<Tooltip
title={
<Space>
<span>
{intl.formatMessage({ id: 'playground.completion' })}:{' '}
{usage.completion_tokens}
</span>
<span>
{intl.formatMessage({ id: 'playground.prompt' })}:{' '}
{usage.prompt_tokens}
</span>
</Space>
}
>
<span>
{intl.formatMessage({ id: 'playground.tokenusage' })}:{' '}
{usage.total_tokens}
</span>
</Tooltip>
<span className="usage">
<Tooltip
title={
<Space>
<span>
{intl.formatMessage({ id: 'playground.completion' })}:{' '}
{usage.completion_tokens}
</span>
<span>
{intl.formatMessage({ id: 'playground.prompt' })}:{' '}
{usage.prompt_tokens}
</span>
</Space>
}
>
<span>
{intl.formatMessage({ id: 'playground.tokenusage' })}:{' '}
{usage.total_tokens}
</span>
</Tooltip>
</span>
<span>
{intl.formatMessage({ id: 'playground.tokenoutput' })}:{' '}
{usage.time_per_output_token_ms} token/ms
{usage.time_per_output_token_ms * 1000} Tokens/s
</span>
</div>
);

@ -1,4 +1,6 @@
.chat-footer {
display: flex;
align-items: center;
padding-block: 10px;
padding-right: 32px;
}

@ -4,17 +4,10 @@
flex-direction: column;
position: relative;
height: calc(100vh - 136px);
// padding-bottom: 120px;
padding-right: 32px;
.message-list-wrap {
max-height: calc(100vh - 152px);
overflow-y: auto;
}
.ground-left-footer {
// position: absolute;
// bottom: 20px;
// width: 100%;
padding-right: 32px;
}
}

@ -1,9 +1,12 @@
.reference-params {
display: flex;
cursor: pointer;
height: 40px;
justify-content: center;
align-items: center;
color: var(--ant-orange);
gap: 20px;
.usage {
cursor: pointer;
}
}

@ -1,20 +1,18 @@
import PageTools from '@/components/page-tools';
import ProgressBar from '@/components/progress-bar';
import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort';
import { convertFileSize } from '@/utils';
import { SyncOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Input, Space, Table } from 'antd';
import _ from 'lodash';
import { useEffect, useState } from 'react';
import { memo, useEffect, useState } from 'react';
import { queryGpuDevicesList } from '../apis';
import { GPUDeviceItem } from '../config/types';
const { Column } = Table;
const Models: React.FC = () => {
const GPUList: React.FC = () => {
const intl = useIntl();
const rowSelection = useTableRowSelection();
const { sortOrder, setSortOrder } = useTableSort({
defaultSortOrder: 'descend'
});
@ -56,6 +54,7 @@ const Models: React.FC = () => {
setDataSource(res.items);
setTotal(res.pagination.total);
} catch (error) {
setDataSource([]);
console.log('error', error);
} finally {
setLoading(false);
@ -193,4 +192,4 @@ const Models: React.FC = () => {
);
};
export default Models;
export default memo(GPUList);

@ -1,20 +1,20 @@
import PageTools from '@/components/page-tools';
import ProgressBar from '@/components/progress-bar';
import StatusTag from '@/components/status-tag';
import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort';
import { convertFileSize } from '@/utils';
import { SyncOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Input, Space, Table } from 'antd';
import _ from 'lodash';
import { useEffect, useState } from 'react';
import { memo, useEffect, useState } from 'react';
import { queryWorkersList } from '../apis';
import { Filesystem, GPUDeviceItem, ListItem } from '../config/types';
const { Column } = Table;
const Models: React.FC = () => {
const rowSelection = useTableRowSelection();
const Resources: React.FC = () => {
console.log('resources======workers');
const { sortOrder, setSortOrder } = useTableSort({
defaultSortOrder: 'descend'
});
@ -39,6 +39,7 @@ const Models: React.FC = () => {
setDataSource(res.items);
setTotal(res.pagination.total);
} catch (error) {
setDataSource([]);
console.log('error', error);
} finally {
setLoading(false);
@ -96,8 +97,14 @@ const Models: React.FC = () => {
);
return mountRoot ? (
<span className="flex-column">
<span>Total: {convertFileSize(mountRoot?.total, 0)}</span>
<span>Used: {convertFileSize(mountRoot?.used, 0)}</span>
<span>
{intl.formatMessage({ id: 'resources.table.total' })}:{' '}
{convertFileSize(mountRoot?.total, 0)}
</span>
<span>
{intl.formatMessage({ id: 'resources.table.used' })}:{' '}
{convertFileSize(mountRoot?.used, 0)}
</span>
</span>
) : (
0
@ -195,10 +202,12 @@ const Models: React.FC = () => {
label={
<span className="flex-column">
<span>
Total: {convertFileSize(record?.status?.memory.total, 0)}
{intl.formatMessage({ id: 'resources.table.total' })}:{' '}
{convertFileSize(record?.status?.memory.total, 0)}
</span>
<span>
Used: {convertFileSize(record?.status?.memory.used, 0)}
{intl.formatMessage({ id: 'resources.table.used' })}:{' '}
{convertFileSize(record?.status?.memory.used, 0)}
</span>
</span>
}
@ -216,7 +225,12 @@ const Models: React.FC = () => {
{record?.status?.gpu_devices.map((item, index) => {
return (
<span className="flex-center" key={index}>
<span className="m-r-5">[{index}]</span>
<span
className="m-r-5"
style={{ display: 'flex', width: 25 }}
>
[{index}]
</span>
<ProgressBar
key={index}
percent={_.round(item.core.utilization_rate, 0)}
@ -244,19 +258,28 @@ const Models: React.FC = () => {
'Unified Memory'
) : (
<span className="flex-center">
<span className="m-r-5">[{index}]</span>
<span
className="m-r-5"
style={{ display: 'flex', width: 25 }}
>
[{index}]
</span>
<ProgressBar
key={index}
percent={_.round(item.memory.utilization_rate, 0)}
label={
<span className="flex-column">
<span>
Total:{' '}
{convertFileSize(item.memory?.total, 0)}
{intl.formatMessage({
id: 'resources.table.total'
})}
: {convertFileSize(item.memory?.total, 0)}
</span>
<span>
Used:{' '}
{convertFileSize(item.memory?.used, 0)}
{intl.formatMessage({
id: 'resources.table.used'
})}
: {convertFileSize(item.memory?.used, 0)}
</span>
</span>
}
@ -289,4 +312,4 @@ const Models: React.FC = () => {
);
};
export default Models;
export default memo(Resources);

@ -19,7 +19,8 @@ const items: TabsProps['items'] = [
}
];
const Resources = () => {
const [activeKey, setActiveKey] = useState('test');
console.log('resources======');
const [activeKey, setActiveKey] = useState('workers');
const intl = useIntl();

@ -69,6 +69,7 @@ const Models: React.FC = () => {
setDataSource(res.items);
setTotal(res.pagination.total);
} catch (error) {
setDataSource([]);
console.log('error', error);
} finally {
setLoading(false);
@ -170,6 +171,7 @@ const Models: React.FC = () => {
async onOk() {
await handleBatchRequest(rowSelection.selectedRowKeys, deleteUser);
message.success(intl.formatMessage({ id: 'common.message.success' }));
rowSelection.clearSelections();
fetchData();
},
onCancel() {

Loading…
Cancel
Save