refactor: model instance item
This commit is contained in:
Vendored
+3
-1
@@ -69,7 +69,7 @@ declare namespace Global {
|
|||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActionItem {
|
interface ActionItem<T> {
|
||||||
label: string;
|
label: string;
|
||||||
key: string;
|
key: string;
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
@@ -77,6 +77,8 @@ declare namespace Global {
|
|||||||
props?: {
|
props?: {
|
||||||
danger?: boolean;
|
danger?: boolean;
|
||||||
};
|
};
|
||||||
|
visible?: (record: T) => boolean;
|
||||||
|
disabled?: (record: T) => boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export default function useActions<T>(actions: Global.ActionItem<T>[], ctx: T) {
|
||||||
|
return actions
|
||||||
|
.filter((action) => {
|
||||||
|
return action.visible ? action.visible(ctx) : true;
|
||||||
|
})
|
||||||
|
.map((action) => ({
|
||||||
|
...action,
|
||||||
|
disabled: action.disabled?.(ctx)
|
||||||
|
}));
|
||||||
|
}
|
||||||
+8
-8
@@ -5,14 +5,14 @@ import { useIntl } from '@umijs/max';
|
|||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import ColumnWrapper from '../../_components/column-wrapper';
|
import ColumnWrapper from '../../../_components/column-wrapper';
|
||||||
import HFModelFile from '../components/model-source/hf-model-file';
|
import { modelSourceMap } from '../../config';
|
||||||
import ModelCard from '../components/model-source/model-card';
|
import { FormData } from '../../config/types';
|
||||||
import SearchModel from '../components/model-source/search-model';
|
import HFModelFile from '../model-source/hf-model-file';
|
||||||
import Separator from '../components/separator';
|
import ModelCard from '../model-source/model-card';
|
||||||
import TitleWrapper from '../components/title-wrapper';
|
import SearchModel from '../model-source/search-model';
|
||||||
import { modelSourceMap } from '../config';
|
import Separator from '../separator';
|
||||||
import { FormData } from '../config/types';
|
import TitleWrapper from '../title-wrapper';
|
||||||
import TargetForm from './target-form';
|
import TargetForm from './target-form';
|
||||||
|
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
+2
-2
@@ -14,8 +14,8 @@ import React, {
|
|||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useMemo
|
useMemo
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { localPathTipsList, modelSourceMap, sourceOptions } from '../config';
|
import { localPathTipsList, modelSourceMap, sourceOptions } from '../../config';
|
||||||
import { useGenerateWorkersModelFileOptions } from '../hooks';
|
import { useGenerateWorkersModelFileOptions } from '../../hooks';
|
||||||
|
|
||||||
type EmptyObject = Record<never, never>;
|
type EmptyObject = Record<never, never>;
|
||||||
|
|
||||||
@@ -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;
|
||||||
@@ -1,48 +1,17 @@
|
|||||||
import { systemConfigAtom } from '@/atoms/system';
|
|
||||||
import AutoTooltip from '@/components/auto-tooltip';
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
import DropdownButtons from '@/components/drop-down-buttons';
|
|
||||||
import IconFont from '@/components/icon-font';
|
|
||||||
import { TooltipOverlayScroller } from '@/components/overlay-scroller';
|
|
||||||
import RowChildren from '@/components/seal-table/components/row-children';
|
import RowChildren from '@/components/seal-table/components/row-children';
|
||||||
import SimpleTabel, { ColumnProps } from '@/components/simple-table';
|
|
||||||
import InfoColumn from '@/components/simple-table/info-column';
|
|
||||||
import StatusTag from '@/components/status-tag';
|
|
||||||
import ThemeTag from '@/components/tags-wrapper/theme-tag';
|
|
||||||
import { HandlerOptions } from '@/hooks/use-chunk-fetch';
|
|
||||||
import useDownloadStream from '@/hooks/use-download-stream';
|
|
||||||
import { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark';
|
|
||||||
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
||||||
import { convertFileSize } from '@/utils';
|
import { Col, Row } from 'antd';
|
||||||
import {
|
|
||||||
DeleteOutlined,
|
|
||||||
DownloadOutlined,
|
|
||||||
HddFilled,
|
|
||||||
InfoCircleOutlined,
|
|
||||||
PieChartFilled,
|
|
||||||
ThunderboltFilled
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import { useIntl } from '@umijs/max';
|
|
||||||
import { Button, Col, Progress, Row, Tooltip, notification } from 'antd';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useAtomValue } from 'jotai';
|
import React from 'react';
|
||||||
import _ from 'lodash';
|
import { ModelInstanceListItem } from '../../config/types';
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { MODEL_INSTANCE_API } from '../../apis';
|
|
||||||
import {
|
|
||||||
InstanceStatusMap,
|
|
||||||
InstanceStatusMapValue,
|
|
||||||
modelCategoriesMap,
|
|
||||||
status
|
|
||||||
} from '../../config';
|
|
||||||
import { generateSource } from '../../config/button-actions';
|
|
||||||
import {
|
|
||||||
DistributedServerItem,
|
|
||||||
DistributedServers,
|
|
||||||
ModelInstanceListItem
|
|
||||||
} from '../../config/types';
|
|
||||||
import { backendOptionsMap } from '../../constants/backend-parameters';
|
|
||||||
import '../../style/instance-item.less';
|
import '../../style/instance-item.less';
|
||||||
|
import ActionsCell from '../instance-cells/actions-cell';
|
||||||
|
import CPUOffloadingCell from '../instance-cells/cpu-offloading-cell';
|
||||||
|
import DistributeInfoCell from '../instance-cells/distribute-info-cell';
|
||||||
|
import DownloadingStatusCell from '../instance-cells/downloading-status-cell';
|
||||||
|
import InstanceStatusCell from '../instance-cells/instance-status-cell';
|
||||||
|
import NameCell from '../instance-cells/name-cell';
|
||||||
|
|
||||||
interface InstanceItemProps {
|
interface InstanceItemProps {
|
||||||
instanceData: ModelInstanceListItem;
|
instanceData: ModelInstanceListItem;
|
||||||
@@ -52,377 +21,6 @@ interface InstanceItemProps {
|
|||||||
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
|
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldList = [
|
|
||||||
{
|
|
||||||
label: 'CPU',
|
|
||||||
key: 'cpuoffload',
|
|
||||||
locale: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'GPU',
|
|
||||||
key: 'gpuoffload',
|
|
||||||
locale: false
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
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 calcTotalVram = (vram: Record<string, number>) => {
|
|
||||||
return _.sum(_.values(vram));
|
|
||||||
};
|
|
||||||
|
|
||||||
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 GPUIndexWrapper = styled.span`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
`;
|
|
||||||
const RenderRayactorDownloading = (props: {
|
|
||||||
severList: any[];
|
|
||||||
instanceData: any;
|
|
||||||
workerList: WorkerListItem[];
|
|
||||||
}) => {
|
|
||||||
const { severList, instanceData, workerList } = props;
|
|
||||||
if (!severList.length && !instanceData.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: `${instanceData.worker_name}`,
|
|
||||||
worker_ip: `${instanceData.worker_ip}`,
|
|
||||||
download_progress: _.round(instanceData.download_progress, 2)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const draftModelList = [];
|
|
||||||
if (instanceData.draft_model_download_progress > 0) {
|
|
||||||
draftModelList.push({
|
|
||||||
draft_model: generateSource(instanceData.draft_model_source),
|
|
||||||
download_progress: _.round(instanceData.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 RenderWorkerDownloading = (props: {
|
|
||||||
distributed_servers?: DistributedServers;
|
|
||||||
workerList: WorkerListItem[];
|
|
||||||
instanceData: ModelInstanceListItem;
|
|
||||||
backend?: string;
|
|
||||||
}) => {
|
|
||||||
const { distributed_servers, workerList, instanceData, backend } = props;
|
|
||||||
|
|
||||||
const severList: DistributedServerItem[] =
|
|
||||||
distributed_servers?.subordinate_workers || [];
|
|
||||||
|
|
||||||
const isWorkerNotDownloading =
|
|
||||||
instanceData.state !== InstanceStatusMap.Downloading ||
|
|
||||||
!severList.length ||
|
|
||||||
backend === backendOptionsMap.llamaBox;
|
|
||||||
|
|
||||||
const isDraftModeNotDownloading =
|
|
||||||
!instanceData.draft_model_download_progress ||
|
|
||||||
instanceData.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={
|
|
||||||
<RenderRayactorDownloading
|
|
||||||
severList={severList}
|
|
||||||
workerList={workerList}
|
|
||||||
instanceData={instanceData}
|
|
||||||
></RenderRayactorDownloading>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Progress
|
|
||||||
showInfo={false}
|
|
||||||
type="circle"
|
|
||||||
size={16}
|
|
||||||
strokeColor="var(--ant-color-success)"
|
|
||||||
percent={
|
|
||||||
_.find(severList, (item: any) => item.download_progress < 100)
|
|
||||||
?.download_progress ||
|
|
||||||
instanceData.draft_model_download_progress ||
|
|
||||||
0
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const InstanceStatusTag = (
|
|
||||||
props: Pick<InstanceItemProps, 'instanceData' | 'handleChildSelect'>
|
|
||||||
) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const { instanceData, handleChildSelect } = props;
|
|
||||||
if (!instanceData.state) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<StatusTag
|
|
||||||
download={
|
|
||||||
instanceData.state === InstanceStatusMap.Downloading
|
|
||||||
? { percent: instanceData.download_progress }
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
extra={
|
|
||||||
instanceData.state === InstanceStatusMap.Error &&
|
|
||||||
instanceData.worker_id ? (
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
style={{ paddingLeft: 0 }}
|
|
||||||
onClick={() => handleChildSelect('viewlog', instanceData)}
|
|
||||||
>
|
|
||||||
{intl.formatMessage({ id: 'models.list.more.logs' })}
|
|
||||||
</Button>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
statusValue={{
|
|
||||||
status:
|
|
||||||
instanceData.state === InstanceStatusMap.Downloading &&
|
|
||||||
instanceData.download_progress === 100
|
|
||||||
? status[InstanceStatusMap.Running]
|
|
||||||
: status[instanceData.state],
|
|
||||||
text: InstanceStatusMapValue[instanceData.state],
|
|
||||||
message:
|
|
||||||
instanceData.state === InstanceStatusMap.Downloading &&
|
|
||||||
instanceData.download_progress === 100
|
|
||||||
? ''
|
|
||||||
: instanceData.state_message
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 />
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
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 renderMessage = (title: string) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 280,
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const InstanceItem: React.FC<InstanceItemProps> = ({
|
const InstanceItem: React.FC<InstanceItemProps> = ({
|
||||||
instanceData,
|
instanceData,
|
||||||
workerList,
|
workerList,
|
||||||
@@ -430,350 +28,74 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
|||||||
defaultOpenId,
|
defaultOpenId,
|
||||||
handleChildSelect
|
handleChildSelect
|
||||||
}) => {
|
}) => {
|
||||||
const systemConfig = useAtomValue(systemConfigAtom);
|
|
||||||
const { runBenchmarkOnInstance } = useBenchmarkTargetInstance();
|
|
||||||
const [api, contextHolder] = notification.useNotification({
|
|
||||||
stack: { threshold: 1 }
|
|
||||||
});
|
|
||||||
const { downloadStream } = useDownloadStream();
|
|
||||||
const intl = useIntl();
|
|
||||||
const actionItems = useMemo(() => {
|
|
||||||
return _.filter(childActionList, (action: any) => {
|
|
||||||
if (action.key === 'benchmark') {
|
|
||||||
return (
|
|
||||||
action.status.includes(instanceData.state) &&
|
|
||||||
modelData?.categories?.includes(modelCategoriesMap.llm)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (action.status && action.status.length > 0) {
|
|
||||||
return action.status.includes(instanceData.state);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}, [instanceData.state, modelData, systemConfig.showMonitoring]);
|
|
||||||
|
|
||||||
const createFileName = (name: string) => {
|
|
||||||
const timestamp = dayjs().format('YYYY-MM-DD_HH-mm-ss');
|
|
||||||
const fileName = `${name}_${timestamp}.txt`;
|
|
||||||
return fileName;
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadNotification = useCallback(
|
|
||||||
(
|
|
||||||
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 renderWorkerInfo = useMemo(() => {
|
|
||||||
let workerIp = '-';
|
|
||||||
if (instanceData.worker_ip) {
|
|
||||||
workerIp = instanceData.port
|
|
||||||
? `${instanceData.worker_ip}:${instanceData.port}`
|
|
||||||
: instanceData.worker_ip;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>{instanceData.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(
|
|
||||||
instanceData.gpu_indexes?.sort?.((a, b) => a - b),
|
|
||||||
','
|
|
||||||
)}
|
|
||||||
]
|
|
||||||
</div>
|
|
||||||
<div className="flex-center">
|
|
||||||
<ThunderboltFilled className="m-r-5" />
|
|
||||||
{intl.formatMessage({ id: 'models.form.backend' })}:{' '}
|
|
||||||
{instanceData?.backend || modelData?.backend || ''}
|
|
||||||
{instanceData.backend_version || modelData?.backend_version
|
|
||||||
? `(${instanceData.backend_version || modelData?.backend_version})`
|
|
||||||
: ''}
|
|
||||||
</div>
|
|
||||||
<div className="flex-center">
|
|
||||||
<PieChartFilled className="m-r-5" />
|
|
||||||
{intl.formatMessage({ id: 'models.table.vram.allocated' })}:{' '}
|
|
||||||
{convertFileSize(
|
|
||||||
instanceData.computed_resource_claim?.vram
|
|
||||||
? calcTotalVram(instanceData.computed_resource_claim?.vram)
|
|
||||||
: 0,
|
|
||||||
1
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
instanceData.worker_name,
|
|
||||||
instanceData.worker_ip,
|
|
||||||
instanceData.port,
|
|
||||||
instanceData.gpu_indexes,
|
|
||||||
instanceData?.backend,
|
|
||||||
instanceData?.backend_version,
|
|
||||||
modelData?.backend,
|
|
||||||
modelData?.backend_version,
|
|
||||||
intl
|
|
||||||
]);
|
|
||||||
|
|
||||||
const renderDistributedServer = (severList: any[]) => {
|
|
||||||
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: `${instanceData.worker_name}`,
|
|
||||||
worker_ip: `${instanceData.worker_ip}`,
|
|
||||||
port: '',
|
|
||||||
vram: calcTotalVram(instanceData.computed_resource_claim?.vram || {}),
|
|
||||||
is_main: true,
|
|
||||||
gpu_index: instanceData.gpu_indexes?.sort(
|
|
||||||
(a: number, b: number) => a - b
|
|
||||||
)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SimpleTabel
|
|
||||||
rowKey="worker_name"
|
|
||||||
columns={distributeCols}
|
|
||||||
dataSource={[...mainWorker, ...list]}
|
|
||||||
></SimpleTabel>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderDistributionInfo = (distributed_servers: DistributedServers) => {
|
|
||||||
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={renderDistributedServer(severList)}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderOffloadInfo = useMemo(() => {
|
|
||||||
const total_layers = instanceData.computed_resource_claim?.total_layers;
|
|
||||||
const offload_layers = instanceData.computed_resource_claim?.offload_layers;
|
|
||||||
|
|
||||||
if (total_layers === offload_layers || !total_layers) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const offloadData = {
|
|
||||||
cpuoffload: `${
|
|
||||||
_.subtract(
|
|
||||||
instanceData.computed_resource_claim?.total_layers,
|
|
||||||
instanceData.computed_resource_claim?.offload_layers
|
|
||||||
) || 0
|
|
||||||
} ${intl.formatMessage({
|
|
||||||
id: 'models.table.layers'
|
|
||||||
})}`,
|
|
||||||
gpuoffload: `${instanceData.computed_resource_claim?.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>
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
instanceData.computed_resource_claim?.total_layers,
|
|
||||||
instanceData.computed_resource_claim?.offload_layers
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleOnSelect = (val: string) => {
|
|
||||||
if (val === 'benchmark') {
|
|
||||||
runBenchmarkOnInstance(instanceData);
|
|
||||||
} else if (val === 'download') {
|
|
||||||
downloadStream({
|
|
||||||
url: `${MODEL_INSTANCE_API}/${instanceData.id}/logs`,
|
|
||||||
filename: createFileName(instanceData.name),
|
|
||||||
downloadNotification
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
handleChildSelect(val, instanceData);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div style={{ borderRadius: 'var(--ant-table-header-border-radius)' }}>
|
||||||
{contextHolder}
|
<RowChildren>
|
||||||
<div style={{ borderRadius: 'var(--ant-table-header-border-radius)' }}>
|
<Row style={{ width: '100%' }} align="middle">
|
||||||
<RowChildren>
|
<Col
|
||||||
<Row style={{ width: '100%' }} align="middle">
|
span={6}
|
||||||
<Col
|
style={{
|
||||||
span={6}
|
paddingInline: 'var(--ant-table-cell-padding-inline)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NameCell
|
||||||
|
record={instanceData}
|
||||||
|
modelData={modelData}
|
||||||
|
defaultOpenId={defaultOpenId}
|
||||||
|
></NameCell>
|
||||||
|
</Col>
|
||||||
|
<Col span={7}>
|
||||||
|
<span
|
||||||
style={{
|
style={{
|
||||||
paddingInline: 'var(--ant-table-cell-padding-inline)'
|
paddingLeft: '58px',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '8px'
|
||||||
}}
|
}}
|
||||||
|
className="flex align-center"
|
||||||
>
|
>
|
||||||
<span className="flex-center instance-name">
|
<CPUOffloadingCell record={instanceData}></CPUOffloadingCell>
|
||||||
<AutoTooltip title={instanceData.name} ghost>
|
<DistributeInfoCell
|
||||||
<span className="m-r-5">{instanceData.name}</span>
|
record={instanceData}
|
||||||
</AutoTooltip>
|
workerList={workerList}
|
||||||
{!!instanceData.worker_id && (
|
></DistributeInfoCell>
|
||||||
<WorkerInfo
|
</span>
|
||||||
title={renderWorkerInfo}
|
</Col>
|
||||||
defaultOpen={defaultOpenId === instanceData.name}
|
<Col span={4}>
|
||||||
></WorkerInfo>
|
<span
|
||||||
)}
|
style={{ paddingLeft: '40px', gap: 4 }}
|
||||||
</span>
|
className="flex-center"
|
||||||
</Col>
|
>
|
||||||
<Col span={7}>
|
<InstanceStatusCell
|
||||||
<span
|
record={instanceData}
|
||||||
style={{
|
onSelect={handleChildSelect}
|
||||||
paddingLeft: '58px',
|
/>
|
||||||
flexWrap: 'wrap',
|
<DownloadingStatusCell
|
||||||
gap: '8px'
|
backend={modelData?.backend}
|
||||||
}}
|
distributed_servers={instanceData.distributed_servers}
|
||||||
className="flex align-center"
|
workerList={workerList}
|
||||||
>
|
record={instanceData}
|
||||||
{renderOffloadInfo}
|
></DownloadingStatusCell>
|
||||||
{renderDistributionInfo(
|
</span>
|
||||||
instanceData.distributed_servers || ({} as DistributedServers)
|
</Col>
|
||||||
)}
|
<Col span={4}>
|
||||||
</span>
|
<span style={{ paddingLeft: 43 }} className="flex">
|
||||||
</Col>
|
<AutoTooltip ghost>
|
||||||
<Col span={4}>
|
{dayjs(instanceData.created_at).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
<span
|
</AutoTooltip>
|
||||||
style={{ paddingLeft: '40px', gap: 4 }}
|
</span>
|
||||||
className="flex-center"
|
</Col>
|
||||||
>
|
<Col span={3}>
|
||||||
<InstanceStatusTag
|
<div style={{ paddingLeft: 36 }}>
|
||||||
instanceData={instanceData}
|
<ActionsCell
|
||||||
handleChildSelect={handleChildSelect}
|
record={instanceData}
|
||||||
/>
|
modelData={modelData}
|
||||||
<RenderWorkerDownloading
|
onSelect={handleChildSelect}
|
||||||
backend={modelData?.backend}
|
></ActionsCell>
|
||||||
distributed_servers={instanceData.distributed_servers}
|
</div>
|
||||||
workerList={workerList}
|
</Col>
|
||||||
instanceData={instanceData}
|
</Row>
|
||||||
></RenderWorkerDownloading>
|
</RowChildren>
|
||||||
</span>
|
</div>
|
||||||
</Col>
|
|
||||||
<Col span={4}>
|
|
||||||
<span style={{ paddingLeft: 43 }} className="flex">
|
|
||||||
<AutoTooltip ghost>
|
|
||||||
{dayjs(instanceData.created_at).format('YYYY-MM-DD HH:mm:ss')}
|
|
||||||
</AutoTooltip>
|
|
||||||
</span>
|
|
||||||
</Col>
|
|
||||||
<Col span={3}>
|
|
||||||
<div style={{ paddingLeft: 36 }}>
|
|
||||||
<DropdownButtons
|
|
||||||
items={actionItems}
|
|
||||||
onSelect={handleOnSelect}
|
|
||||||
></DropdownButtons>
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</RowChildren>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default InstanceItem;
|
export default InstanceItem;
|
||||||
|
|||||||
@@ -1,7 +1,111 @@
|
|||||||
import React from 'react';
|
import DeleteModal from '@/components/delete-modal';
|
||||||
|
import IconFont from '@/components/icon-font';
|
||||||
|
import { FilterBar } from '@/components/page-tools';
|
||||||
|
import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
|
||||||
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
|
import NoResult from '@/pages/_components/no-result';
|
||||||
|
import PageBox from '@/pages/_components/page-box';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
import { ConfigProvider, Table } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import {
|
||||||
|
deleteModelInstance,
|
||||||
|
MODEL_INSTANCE_API,
|
||||||
|
queryModelsInstances
|
||||||
|
} from '../apis';
|
||||||
|
import { ModelInstanceListItem as ListItem } from '../config/types';
|
||||||
|
import useInstanceColumns from './use-instance-columns';
|
||||||
|
|
||||||
const InstanceView: React.FC = () => {
|
const InstanceView: React.FC = () => {
|
||||||
return <div>InstanceView</div>;
|
const {
|
||||||
|
dataSource,
|
||||||
|
rowSelection,
|
||||||
|
queryParams,
|
||||||
|
modalRef,
|
||||||
|
handleTableChange,
|
||||||
|
handleDelete,
|
||||||
|
handleDeleteBatch,
|
||||||
|
fetchData,
|
||||||
|
handlePageChange,
|
||||||
|
handleSearch,
|
||||||
|
handleNameChange
|
||||||
|
} = useTableFetch<ListItem>({
|
||||||
|
key: PaginationKey.Providers,
|
||||||
|
fetchAPI: queryModelsInstances,
|
||||||
|
deleteAPI: deleteModelInstance,
|
||||||
|
watch: false,
|
||||||
|
API: MODEL_INSTANCE_API,
|
||||||
|
contentForDelete: 'menu.models.providers'
|
||||||
|
});
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const handleSelect = useMemoizedFn((val: any, row: ListItem) => {});
|
||||||
|
|
||||||
|
const renderEmpty = (type?: string) => {
|
||||||
|
if (type !== 'Table') return;
|
||||||
|
return (
|
||||||
|
<NoResult
|
||||||
|
loading={dataSource.loading}
|
||||||
|
loadend={dataSource.loadend}
|
||||||
|
dataSource={dataSource.dataList}
|
||||||
|
image={<IconFont type="icon-extension-outline" />}
|
||||||
|
filters={_.omit(queryParams, ['sort_by'])}
|
||||||
|
noFoundText={intl.formatMessage({
|
||||||
|
id: 'noresult.providers.nofound'
|
||||||
|
})}
|
||||||
|
title={intl.formatMessage({ id: 'noresult.providers.title' })}
|
||||||
|
subTitle={intl.formatMessage({
|
||||||
|
id: 'noresult.providers.subTitle'
|
||||||
|
})}
|
||||||
|
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
||||||
|
></NoResult>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = useInstanceColumns(handleSelect);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageBox>
|
||||||
|
<FilterBar
|
||||||
|
showSelect={false}
|
||||||
|
marginBottom={22}
|
||||||
|
marginTop={30}
|
||||||
|
widths={{ input: 300 }}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
handleInputChange={handleNameChange}
|
||||||
|
handleSearch={handleSearch}
|
||||||
|
handleDeleteByBatch={handleDeleteBatch}
|
||||||
|
></FilterBar>
|
||||||
|
<ConfigProvider renderEmpty={renderEmpty}>
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
tableLayout="fixed"
|
||||||
|
sortDirections={TABLE_SORT_DIRECTIONS}
|
||||||
|
showSorterTooltip={false}
|
||||||
|
dataSource={dataSource.dataList}
|
||||||
|
loading={{
|
||||||
|
spinning: dataSource.loading,
|
||||||
|
size: 'middle'
|
||||||
|
}}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
columns={columns}
|
||||||
|
scroll={{ x: 900 }}
|
||||||
|
pagination={{
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSize: queryParams.perPage,
|
||||||
|
current: queryParams.page,
|
||||||
|
total: dataSource.total,
|
||||||
|
hideOnSinglePage: queryParams.perPage === 10,
|
||||||
|
onChange: handlePageChange
|
||||||
|
}}
|
||||||
|
></Table>
|
||||||
|
</ConfigProvider>
|
||||||
|
</PageBox>
|
||||||
|
<DeleteModal ref={modalRef}></DeleteModal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InstanceView;
|
export default InstanceView;
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// columns.ts
|
||||||
|
import DropdownButtons from '@/components/drop-down-buttons';
|
||||||
|
import { tableSorter } from '@/config/settings';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { ModelInstanceListItem as ListItem } from '../config/types';
|
||||||
|
|
||||||
|
const useProviderColumns = (
|
||||||
|
handleSelect: (val: string, record: ListItem) => void,
|
||||||
|
onCellClick?: (record: ListItem, dataIndex: string) => void
|
||||||
|
): ColumnsType<ListItem> => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||||
|
dataIndex: 'name',
|
||||||
|
sorter: tableSorter(1),
|
||||||
|
minWidth: 160,
|
||||||
|
span: 5,
|
||||||
|
render: () => '--'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'providers.table.providerName' }),
|
||||||
|
dataIndex: ['config', 'type'],
|
||||||
|
sorter: tableSorter(2),
|
||||||
|
span: 4,
|
||||||
|
minWidth: 160,
|
||||||
|
render: () => '--'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'providers.table.models' }),
|
||||||
|
dataIndex: 'models',
|
||||||
|
span: 3,
|
||||||
|
minWidth: 200,
|
||||||
|
render: () => '--'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
||||||
|
dataIndex: 'created_at',
|
||||||
|
sorter: tableSorter(6),
|
||||||
|
span: 3,
|
||||||
|
render: () => '--'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'common.table.operation' }),
|
||||||
|
dataIndex: 'operations',
|
||||||
|
span: 3,
|
||||||
|
minWidth: 120,
|
||||||
|
render: (value: string, record: ListItem) => (
|
||||||
|
<DropdownButtons
|
||||||
|
items={[]}
|
||||||
|
onSelect={(val) => handleSelect(val, record)}
|
||||||
|
></DropdownButtons>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}, [handleSelect, onCellClick]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useProviderColumns;
|
||||||
@@ -11,6 +11,7 @@ import NoResult from '@/pages/_components/no-result';
|
|||||||
import PageBox from '@/pages/_components/page-box';
|
import PageBox from '@/pages/_components/page-box';
|
||||||
import { createModel } from '@/pages/llmodels/apis';
|
import { createModel } from '@/pages/llmodels/apis';
|
||||||
import DeployModal from '@/pages/llmodels/components/deployment/deploy-modal';
|
import DeployModal from '@/pages/llmodels/components/deployment/deploy-modal';
|
||||||
|
import DownloadModal from '@/pages/llmodels/components/download';
|
||||||
import { modelSourceMap } from '@/pages/llmodels/config';
|
import { modelSourceMap } from '@/pages/llmodels/config';
|
||||||
import {
|
import {
|
||||||
modalConfig,
|
modalConfig,
|
||||||
@@ -18,7 +19,6 @@ import {
|
|||||||
} from '@/pages/llmodels/config/button-actions';
|
} from '@/pages/llmodels/config/button-actions';
|
||||||
import { SourceType } from '@/pages/llmodels/config/types';
|
import { SourceType } from '@/pages/llmodels/config/types';
|
||||||
import { backendOptionsMap } from '@/pages/llmodels/constants/backend-parameters';
|
import { backendOptionsMap } from '@/pages/llmodels/constants/backend-parameters';
|
||||||
import DownloadModal from '@/pages/llmodels/download';
|
|
||||||
import useCheckBackend from '@/pages/llmodels/hooks/use-check-backend';
|
import useCheckBackend from '@/pages/llmodels/hooks/use-check-backend';
|
||||||
import { useGenerateWorkerOptions } from '@/pages/llmodels/hooks/use-form-initial-values';
|
import { useGenerateWorkerOptions } from '@/pages/llmodels/hooks/use-form-initial-values';
|
||||||
import useRecognizeAudio from '@/pages/llmodels/hooks/use-recognize-audio';
|
import useRecognizeAudio from '@/pages/llmodels/hooks/use-recognize-audio';
|
||||||
|
|||||||
Reference in New Issue
Block a user