refactor: model instance item
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import DropdownButtons from '@/components/drop-down-buttons';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { HandlerOptions } from '@/hooks/use-chunk-fetch';
|
||||
import useDownloadStream from '@/hooks/use-download-stream';
|
||||
import { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark';
|
||||
import { DeleteOutlined, DownloadOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Progress, notification } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import { MODEL_INSTANCE_API } from '../../apis';
|
||||
import { InstanceStatusMap, modelCategoriesMap } from '../../config';
|
||||
import { ListItem, ModelInstanceListItem } from '../../config/types';
|
||||
|
||||
const childActionList = [
|
||||
{
|
||||
label: 'common.button.viewlog',
|
||||
key: 'viewlog',
|
||||
status: [
|
||||
InstanceStatusMap.Initializing,
|
||||
InstanceStatusMap.Running,
|
||||
InstanceStatusMap.Error,
|
||||
InstanceStatusMap.Starting,
|
||||
InstanceStatusMap.Downloading
|
||||
],
|
||||
icon: <IconFont type="icon-logs" />
|
||||
},
|
||||
{
|
||||
label: 'common.button.downloadLog',
|
||||
key: 'download',
|
||||
status: [
|
||||
InstanceStatusMap.Initializing,
|
||||
InstanceStatusMap.Running,
|
||||
InstanceStatusMap.Error,
|
||||
InstanceStatusMap.Starting,
|
||||
InstanceStatusMap.Downloading
|
||||
],
|
||||
icon: <DownloadOutlined />
|
||||
},
|
||||
{
|
||||
label: 'models.table.instance.benchmark',
|
||||
key: 'benchmark',
|
||||
status: [InstanceStatusMap.Running],
|
||||
icon: <IconFont type="icon-speed" />
|
||||
},
|
||||
{
|
||||
label: 'common.button.delrecreate',
|
||||
key: 'delete',
|
||||
props: {
|
||||
danger: true
|
||||
},
|
||||
icon: <DeleteOutlined />
|
||||
}
|
||||
];
|
||||
|
||||
interface ActionsCellProps {
|
||||
record: ModelInstanceListItem;
|
||||
modelData: ListItem;
|
||||
onSelect: (val: string, record: ModelInstanceListItem) => void;
|
||||
}
|
||||
|
||||
const ActionsCell: React.FC<ActionsCellProps> = ({
|
||||
record,
|
||||
modelData,
|
||||
onSelect
|
||||
}) => {
|
||||
const { runBenchmarkOnInstance } = useBenchmarkTargetInstance();
|
||||
const [api, contextHolder] = notification.useNotification({
|
||||
stack: { threshold: 1 }
|
||||
});
|
||||
const { downloadStream } = useDownloadStream();
|
||||
const intl = useIntl();
|
||||
|
||||
const createFileName = (name: string) => {
|
||||
const timestamp = dayjs().format('YYYY-MM-DD_HH-mm-ss');
|
||||
const fileName = `${name}_${timestamp}.txt`;
|
||||
return fileName;
|
||||
};
|
||||
|
||||
const renderMessage = (title: string) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: 280,
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const downloadNotification = (
|
||||
data: HandlerOptions & {
|
||||
filename: string;
|
||||
duration?: number;
|
||||
chunkRequestRef: any;
|
||||
}
|
||||
) => {
|
||||
api.open({
|
||||
duration: data.duration,
|
||||
message: renderMessage(data.filename),
|
||||
key: data.filename,
|
||||
closeIcon: (
|
||||
<span>{intl.formatMessage({ id: 'common.button.cancel' })}</span>
|
||||
),
|
||||
description: <Progress percent={data.percent} size="small"></Progress>,
|
||||
onClose() {
|
||||
data.chunkRequestRef?.current?.abort();
|
||||
notification.destroy?.(data.filename);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnSelect = (val: string) => {
|
||||
if (val === 'benchmark') {
|
||||
runBenchmarkOnInstance(record);
|
||||
} else if (val === 'download') {
|
||||
downloadStream({
|
||||
url: `${MODEL_INSTANCE_API}/${record.id}/logs`,
|
||||
filename: createFileName(record.name),
|
||||
downloadNotification
|
||||
});
|
||||
} else {
|
||||
onSelect(val, record);
|
||||
}
|
||||
};
|
||||
|
||||
const actionItems = childActionList.filter((action: any) => {
|
||||
if (action.key === 'benchmark') {
|
||||
return (
|
||||
action.status.includes(record.state) &&
|
||||
modelData?.categories?.includes(modelCategoriesMap.llm)
|
||||
);
|
||||
}
|
||||
if (action.status && action.status.length > 0) {
|
||||
return action.status.includes(record.state);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextHolder}
|
||||
<DropdownButtons
|
||||
items={actionItems}
|
||||
onSelect={handleOnSelect}
|
||||
></DropdownButtons>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionsCell;
|
||||
@@ -0,0 +1,80 @@
|
||||
import InfoColumn from '@/components/simple-table/info-column';
|
||||
import ThemeTag from '@/components/tags-wrapper/theme-tag';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Tooltip } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { ModelInstanceListItem } from '../../config/types';
|
||||
|
||||
const fieldList = [
|
||||
{
|
||||
label: 'CPU',
|
||||
key: 'cpuoffload',
|
||||
locale: false
|
||||
},
|
||||
{
|
||||
label: 'GPU',
|
||||
key: 'gpuoffload',
|
||||
locale: false
|
||||
}
|
||||
];
|
||||
|
||||
interface CPUOffloadingCellProps {
|
||||
record: ModelInstanceListItem;
|
||||
}
|
||||
const CPUOffloadingCell: React.FC<CPUOffloadingCellProps> = ({ record }) => {
|
||||
const intl = useIntl();
|
||||
const { total_layers, offload_layers } =
|
||||
record?.computed_resource_claim || {};
|
||||
|
||||
if (total_layers === offload_layers || !total_layers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const offloadData = {
|
||||
cpuoffload: `${
|
||||
_.subtract(total_layers, offload_layers) || 0
|
||||
} ${intl.formatMessage({
|
||||
id: 'models.table.layers'
|
||||
})}`,
|
||||
gpuoffload: `${offload_layers} ${intl.formatMessage({
|
||||
id: 'models.table.layers'
|
||||
})}`
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
styles={{
|
||||
container: {
|
||||
paddingInline: 12
|
||||
}
|
||||
}}
|
||||
title={<InfoColumn fieldList={fieldList} data={offloadData}></InfoColumn>}
|
||||
>
|
||||
<span>
|
||||
<ThemeTag
|
||||
opacity={0.75}
|
||||
color="cyan"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
maxWidth: '100%',
|
||||
minWidth: 50,
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
borderRadius: 12
|
||||
}}
|
||||
>
|
||||
<InfoCircleOutlined className="m-r-5" />
|
||||
{intl.formatMessage({
|
||||
id: 'models.table.cpuoffload'
|
||||
})}
|
||||
</ThemeTag>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default CPUOffloadingCell;
|
||||
@@ -0,0 +1,184 @@
|
||||
import { TooltipOverlayScroller } from '@/components/overlay-scroller';
|
||||
import SimpleTabel, { ColumnProps } from '@/components/simple-table';
|
||||
import ThemeTag from '@/components/tags-wrapper/theme-tag';
|
||||
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
||||
import { convertFileSize } from '@/utils';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
DistributedServerItem,
|
||||
DistributedServers,
|
||||
ModelInstanceListItem
|
||||
} from '../../config/types';
|
||||
|
||||
const GPUIndexWrapper = styled.span`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
`;
|
||||
|
||||
interface DistributeInfoCellProps {
|
||||
record: ModelInstanceListItem;
|
||||
workerList: WorkerListItem[];
|
||||
}
|
||||
|
||||
const renderGpuIndexs = (gpuIndexes: number[]) => {
|
||||
return (
|
||||
<GPUIndexWrapper>
|
||||
{_.chunk(gpuIndexes, 8).map((item: number[], index: number) => {
|
||||
return <span key={index}>{item.join(',')}</span>;
|
||||
})}
|
||||
</GPUIndexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const distributeCols: ColumnProps[] = [
|
||||
{
|
||||
title: 'Worker',
|
||||
key: 'worker_name',
|
||||
style: {
|
||||
wordBreak: 'break-word'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'IP',
|
||||
key: 'worker_ip',
|
||||
render: ({ row }) => {
|
||||
return row.port ? `${row.worker_ip}:${row.port}` : row.worker_ip;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'models.table.gpuindex',
|
||||
locale: true,
|
||||
key: 'gpu_index',
|
||||
render: ({ row }) => {
|
||||
const list = row.gpu_index?.sort((a: number, b: number) => a - b) || [];
|
||||
return row.is_main ? (
|
||||
<>
|
||||
{renderGpuIndexs(list)}
|
||||
<span>(main)</span>
|
||||
</>
|
||||
) : (
|
||||
renderGpuIndexs(list)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'models.table.vram.allocated',
|
||||
locale: true,
|
||||
key: 'vram',
|
||||
render: ({ rowIndex, row, dataList }) => {
|
||||
return convertFileSize(row.vram, 1);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const calcTotalVram = (vram: Record<string, number>) => {
|
||||
return _.sum(_.values(vram));
|
||||
};
|
||||
|
||||
const DistributedServerList: React.FC<DistributeInfoCellProps> = ({
|
||||
record,
|
||||
workerList
|
||||
}) => {
|
||||
const severList: DistributedServerItem[] =
|
||||
record?.distributed_servers?.subordinate_workers || [];
|
||||
|
||||
const list = _.map(severList, (item: any) => {
|
||||
const data = _.find(workerList, { id: item.worker_id });
|
||||
return {
|
||||
worker_name: data?.name,
|
||||
worker_ip: data?.ip,
|
||||
port: '',
|
||||
is_main: false,
|
||||
vram: calcTotalVram(item.computed_resource_claim?.vram || {}),
|
||||
gpu_index: _.keys(item.computed_resource_claim?.vram)
|
||||
.map((i: string) => Number(i))
|
||||
.sort((a: number, b: number) => a - b)
|
||||
};
|
||||
});
|
||||
|
||||
const mainWorker = [
|
||||
{
|
||||
worker_name: `${record.worker_name}`,
|
||||
worker_ip: `${record.worker_ip}`,
|
||||
port: '',
|
||||
vram: calcTotalVram(record.computed_resource_claim?.vram || {}),
|
||||
is_main: true,
|
||||
gpu_index: record.gpu_indexes?.sort((a: number, b: number) => a - b)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SimpleTabel
|
||||
rowKey="worker_name"
|
||||
columns={distributeCols}
|
||||
dataSource={[...mainWorker, ...list]}
|
||||
></SimpleTabel>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DistributeInfoCell: React.FC<{
|
||||
record: ModelInstanceListItem;
|
||||
workerList: WorkerListItem[];
|
||||
}> = ({ record, workerList }) => {
|
||||
const intl = useIntl();
|
||||
const distributed_servers: DistributedServers | undefined =
|
||||
record?.distributed_servers;
|
||||
|
||||
const severList: DistributedServerItem[] =
|
||||
distributed_servers?.subordinate_workers || [];
|
||||
|
||||
if (!severList.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<TooltipOverlayScroller
|
||||
toolTipProps={{
|
||||
styles: {
|
||||
container: {
|
||||
width: 'max-content',
|
||||
maxWidth: '520px',
|
||||
minWidth: '400px'
|
||||
}
|
||||
}
|
||||
}}
|
||||
title={
|
||||
<DistributedServerList
|
||||
record={record}
|
||||
workerList={workerList}
|
||||
></DistributedServerList>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<ThemeTag
|
||||
opacity={0.75}
|
||||
color="processing"
|
||||
style={{
|
||||
marginRight: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
maxWidth: '100%',
|
||||
minWidth: 50,
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
borderRadius: 12
|
||||
}}
|
||||
>
|
||||
<InfoCircleOutlined className="m-r-5" />
|
||||
{intl.formatMessage({
|
||||
id: 'models.table.acrossworker'
|
||||
})}
|
||||
</ThemeTag>
|
||||
</span>
|
||||
</TooltipOverlayScroller>
|
||||
);
|
||||
};
|
||||
|
||||
export default DistributeInfoCell;
|
||||
@@ -0,0 +1,179 @@
|
||||
import SimpleTabel, { ColumnProps } from '@/components/simple-table';
|
||||
import StatusTag from '@/components/status-tag';
|
||||
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
||||
import { Progress, Tooltip } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import { InstanceStatusMap, status } from '../../config';
|
||||
import { generateSource } from '../../config/button-actions';
|
||||
import {
|
||||
DistributedServerItem,
|
||||
DistributedServers,
|
||||
ModelInstanceListItem
|
||||
} from '../../config/types';
|
||||
import { backendOptionsMap } from '../../constants/backend-parameters';
|
||||
|
||||
interface DownloadingStatusProps {
|
||||
distributed_servers?: DistributedServers;
|
||||
workerList: WorkerListItem[];
|
||||
record: ModelInstanceListItem;
|
||||
backend?: string;
|
||||
}
|
||||
|
||||
const statusColumn: ColumnProps[] = [
|
||||
{
|
||||
title: 'models.table.download.progress',
|
||||
locale: true,
|
||||
key: 'download_progress',
|
||||
render: ({ row }) => {
|
||||
return (
|
||||
<StatusTag
|
||||
download={{
|
||||
percent: row.download_progress
|
||||
}}
|
||||
statusValue={{
|
||||
status: row.download_progress
|
||||
? status[InstanceStatusMap.Running]
|
||||
: status[InstanceStatusMap.Initializing],
|
||||
text: row.download_progress,
|
||||
message: ''
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
const downloadList: ColumnProps[] = [
|
||||
{
|
||||
title: 'resources.worker',
|
||||
locale: true,
|
||||
key: 'worker_name',
|
||||
width: 280
|
||||
},
|
||||
...statusColumn
|
||||
];
|
||||
|
||||
const draftModelDownloadList: ColumnProps[] = [
|
||||
{
|
||||
title: 'models.form.draftModel',
|
||||
locale: true,
|
||||
key: 'draft_model',
|
||||
style: {
|
||||
wordBreak: 'break-word'
|
||||
},
|
||||
width: 280
|
||||
},
|
||||
...statusColumn
|
||||
];
|
||||
|
||||
const DownloadingTips = (props: {
|
||||
severList: any[];
|
||||
record: ModelInstanceListItem;
|
||||
workerList: WorkerListItem[];
|
||||
}) => {
|
||||
const { severList, record, workerList } = props;
|
||||
if (!severList.length && !record.draft_model_download_progress) {
|
||||
return null;
|
||||
}
|
||||
const list = _.map(severList, (item: any) => {
|
||||
const data = _.find(workerList, { id: item.worker_id });
|
||||
return {
|
||||
worker_name: data?.name,
|
||||
worker_ip: data?.ip,
|
||||
download_progress: _.round(item.download_progress, 2)
|
||||
};
|
||||
});
|
||||
|
||||
const mainWorker = [
|
||||
{
|
||||
worker_name: `${record.worker_name}`,
|
||||
worker_ip: `${record.worker_ip}`,
|
||||
download_progress: _.round(record.download_progress, 2)
|
||||
}
|
||||
];
|
||||
|
||||
const draftModelList = [];
|
||||
if (record.draft_model_download_progress > 0) {
|
||||
draftModelList.push({
|
||||
draft_model: generateSource(record.draft_model_source),
|
||||
download_progress: _.round(record.draft_model_download_progress, 2)
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{severList.length > 0 && (
|
||||
<SimpleTabel
|
||||
columns={downloadList}
|
||||
dataSource={[...mainWorker, ...list]}
|
||||
rowKey="worker_name"
|
||||
theme="light"
|
||||
></SimpleTabel>
|
||||
)}
|
||||
|
||||
{draftModelList.length > 0 && (
|
||||
<SimpleTabel
|
||||
columns={draftModelDownloadList}
|
||||
dataSource={[...draftModelList]}
|
||||
rowKey="worker_name"
|
||||
theme="light"
|
||||
></SimpleTabel>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DownloadingStatus: React.FC<DownloadingStatusProps> = (props) => {
|
||||
const { distributed_servers, workerList, record, backend } = props;
|
||||
|
||||
const severList: DistributedServerItem[] =
|
||||
distributed_servers?.subordinate_workers || [];
|
||||
|
||||
const isWorkerNotDownloading =
|
||||
record.state !== InstanceStatusMap.Downloading ||
|
||||
!severList.length ||
|
||||
backend === backendOptionsMap.llamaBox;
|
||||
|
||||
const isDraftModeNotDownloading =
|
||||
!record.draft_model_download_progress ||
|
||||
record.draft_model_download_progress >= 100;
|
||||
|
||||
if (isWorkerNotDownloading && isDraftModeNotDownloading) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
styles={{
|
||||
container: {
|
||||
width: 360,
|
||||
backgroundColor: 'var(--color-spotlight-bg)'
|
||||
}
|
||||
}}
|
||||
classNames={{
|
||||
root: 'light-downloading-tooltip'
|
||||
}}
|
||||
title={
|
||||
<DownloadingTips
|
||||
severList={severList}
|
||||
workerList={workerList}
|
||||
record={record}
|
||||
></DownloadingTips>
|
||||
}
|
||||
>
|
||||
<Progress
|
||||
showInfo={false}
|
||||
type="circle"
|
||||
size={16}
|
||||
strokeColor="var(--ant-color-success)"
|
||||
percent={
|
||||
_.find(severList, (item: any) => item.download_progress < 100)
|
||||
?.download_progress ||
|
||||
record.draft_model_download_progress ||
|
||||
0
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default DownloadingStatus;
|
||||
@@ -0,0 +1,61 @@
|
||||
import StatusTag from '@/components/status-tag';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button } from 'antd';
|
||||
import React from 'react';
|
||||
import {
|
||||
InstanceStatusMap,
|
||||
InstanceStatusMapValue,
|
||||
status
|
||||
} from '../../config';
|
||||
import { ModelInstanceListItem } from '../../config/types';
|
||||
|
||||
interface InstanceStatusProps {
|
||||
record: ModelInstanceListItem;
|
||||
onSelect: (val: string, record: ModelInstanceListItem) => void;
|
||||
}
|
||||
|
||||
const InstanceStatusTag: React.FC<InstanceStatusProps> = ({
|
||||
record,
|
||||
onSelect
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
if (!record.state) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<StatusTag
|
||||
download={
|
||||
record.state === InstanceStatusMap.Downloading
|
||||
? { percent: record.download_progress }
|
||||
: undefined
|
||||
}
|
||||
extra={
|
||||
record.state === InstanceStatusMap.Error && record.worker_id ? (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
style={{ paddingLeft: 0 }}
|
||||
onClick={() => onSelect('viewlog', record)}
|
||||
>
|
||||
{intl.formatMessage({ id: 'models.list.more.logs' })}
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
statusValue={{
|
||||
status:
|
||||
record.state === InstanceStatusMap.Downloading &&
|
||||
record.download_progress === 100
|
||||
? status[InstanceStatusMap.Running]
|
||||
: status[record.state],
|
||||
text: InstanceStatusMapValue[record.state],
|
||||
message:
|
||||
record.state === InstanceStatusMap.Downloading &&
|
||||
record.download_progress === 100
|
||||
? ''
|
||||
: record.state_message
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstanceStatusTag;
|
||||
@@ -0,0 +1,133 @@
|
||||
import AutoTooltip from '@/components/auto-tooltip';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { convertFileSize } from '@/utils';
|
||||
import {
|
||||
HddFilled,
|
||||
InfoCircleOutlined,
|
||||
PieChartFilled,
|
||||
ThunderboltFilled
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Tooltip } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import { ModelInstanceListItem } from '../../config/types';
|
||||
import '../../style/instance-item.less';
|
||||
|
||||
interface NameCellProps {
|
||||
record: ModelInstanceListItem;
|
||||
modelData: any;
|
||||
defaultOpenId?: string;
|
||||
}
|
||||
|
||||
const calcTotalVram = (vram: Record<string, number>) => {
|
||||
return _.sum(_.values(vram));
|
||||
};
|
||||
|
||||
const WorkerInfoContent: React.FC<NameCellProps> = ({ record, modelData }) => {
|
||||
const intl = useIntl();
|
||||
let workerIp = '-';
|
||||
if (record.worker_ip) {
|
||||
workerIp = record.port
|
||||
? `${record.worker_ip}:${record.port}`
|
||||
: record.worker_ip;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div>{record.worker_name}</div>
|
||||
<div className="flex-center">
|
||||
<HddFilled className="m-r-5" />
|
||||
{workerIp}
|
||||
</div>
|
||||
<div className="flex-center">
|
||||
<IconFont type="icon-filled-gpu" className="m-r-5" />
|
||||
{intl.formatMessage({ id: 'models.table.gpuindex' })}: [
|
||||
{_.join(
|
||||
record.gpu_indexes?.sort?.((a, b) => a - b),
|
||||
','
|
||||
)}
|
||||
]
|
||||
</div>
|
||||
<div className="flex-center">
|
||||
<ThunderboltFilled className="m-r-5" />
|
||||
{intl.formatMessage({ id: 'models.form.backend' })}:{' '}
|
||||
{record?.backend || modelData?.backend || ''}
|
||||
{record.backend_version || modelData?.backend_version
|
||||
? `(${record.backend_version || modelData?.backend_version})`
|
||||
: ''}
|
||||
</div>
|
||||
<div className="flex-center">
|
||||
<PieChartFilled className="m-r-5" />
|
||||
{intl.formatMessage({ id: 'models.table.vram.allocated' })}:{' '}
|
||||
{convertFileSize(
|
||||
record.computed_resource_claim?.vram
|
||||
? calcTotalVram(record.computed_resource_claim?.vram)
|
||||
: 0,
|
||||
1
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const WorkerInfo = (props: {
|
||||
title: React.ReactNode;
|
||||
defaultOpen: boolean;
|
||||
}) => {
|
||||
const [open, setOpen] = React.useState(props.defaultOpen);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.defaultOpen) {
|
||||
setTimeout(() => {
|
||||
setOpen(false);
|
||||
}, 1000);
|
||||
}
|
||||
}, [props.defaultOpen]);
|
||||
|
||||
return (
|
||||
<span className="server-info-wrapper">
|
||||
<Tooltip
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
title={props.title}
|
||||
styles={{
|
||||
container: {
|
||||
width: 'max-content',
|
||||
maxWidth: '400px'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="server-info">
|
||||
<InfoCircleOutlined />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const NameCell: React.FC<NameCellProps> = ({
|
||||
record,
|
||||
modelData,
|
||||
defaultOpenId
|
||||
}) => {
|
||||
return (
|
||||
<span className="flex-center instance-name">
|
||||
<AutoTooltip title={record.name} ghost>
|
||||
<span className="m-r-5">{record.name}</span>
|
||||
</AutoTooltip>
|
||||
{!!record.worker_id && (
|
||||
<WorkerInfo
|
||||
title={
|
||||
<WorkerInfoContent
|
||||
record={record}
|
||||
modelData={modelData}
|
||||
></WorkerInfoContent>
|
||||
}
|
||||
defaultOpen={defaultOpenId === record.name}
|
||||
></WorkerInfo>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default NameCell;
|
||||
Reference in New Issue
Block a user