refactor: resource table columns
This commit is contained in:
@@ -1,10 +1,6 @@
|
||||
import { modelsExpandKeysAtom } from '@/atoms/models';
|
||||
import AutoTooltip from '@/components/auto-tooltip';
|
||||
import DeleteModal from '@/components/delete-modal';
|
||||
import DropdownButtons from '@/components/drop-down-buttons';
|
||||
import { TooltipOverlayScroller } from '@/components/overlay-scroller';
|
||||
import { FilterBar } from '@/components/page-tools';
|
||||
import StatusTag from '@/components/status-tag';
|
||||
import { PageAction } from '@/config';
|
||||
import useAppUtils from '@/hooks/use-app-utils';
|
||||
import useBodyScroll from '@/hooks/use-body-scroll';
|
||||
@@ -15,26 +11,18 @@ import { backendOptionsMap, modelSourceMap } from '@/pages/llmodels/config';
|
||||
import { identifyModelTask } from '@/pages/llmodels/config/audio-catalog';
|
||||
import {
|
||||
modalConfig,
|
||||
modelFileActions,
|
||||
onLineSourceOptions
|
||||
} from '@/pages/llmodels/config/button-actions';
|
||||
import { SourceType } from '@/pages/llmodels/config/types';
|
||||
import DownloadModal from '@/pages/llmodels/download';
|
||||
import { useGenerateWorkerOptions } from '@/pages/llmodels/hooks/use-form-initial-values';
|
||||
import { convertFileSize } from '@/utils';
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
CopyOutlined,
|
||||
InfoCircleOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { ConfigProvider, Empty, Table, Tag, Typography, message } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { ConfigProvider, Empty, Table, message } from 'antd';
|
||||
import { useAtom } from 'jotai';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { checkCurrentbackend } from '../../llmodels/hooks';
|
||||
import {
|
||||
MODEL_FILES_API,
|
||||
@@ -43,242 +31,12 @@ import {
|
||||
queryModelFilesList,
|
||||
retryDownloadModelFile
|
||||
} from '../apis';
|
||||
import {
|
||||
ModelfileState,
|
||||
ModelfileStateMap,
|
||||
ModelfileStateMapValue,
|
||||
WorkerStatusMap
|
||||
} from '../config';
|
||||
import { WorkerStatusMap } from '../config';
|
||||
import { ModelFile as ListItem } from '../config/types';
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
import useFilesColumns from '../hooks/use-files-columns';
|
||||
|
||||
const filterPattern = /^(.*?)(?:-\d+-of-\d+)?(\.gguf)?$/;
|
||||
|
||||
const PathWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
height: 100%;
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.btn-wrapper {
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
align-items: center;
|
||||
}
|
||||
&:hover {
|
||||
.btn-wrapper {
|
||||
width: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ItemWrapper = styled.ul`
|
||||
max-width: 300px;
|
||||
margin: 0;
|
||||
padding-inline: 13px 0;
|
||||
word-break: break-word;
|
||||
li {
|
||||
line-height: 1.6;
|
||||
}
|
||||
`;
|
||||
|
||||
const FilesTag = styled(Tag)`
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-inline: 4px 0;
|
||||
height: 22px;
|
||||
border-radius: var(--border-radius-base);
|
||||
`;
|
||||
|
||||
const TextWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const TypographyPara = styled(Paragraph)`
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
margin-bottom: 0;
|
||||
font-size: 13px;
|
||||
`;
|
||||
|
||||
const TooltipTitle: React.FC<{ path: string }> = ({ path }) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<TypographyPara
|
||||
style={{ margin: 0 }}
|
||||
copyable={{
|
||||
icon: [
|
||||
<CopyOutlined key="copy-icon" />,
|
||||
<CheckCircleFilled key="copied-icon" />
|
||||
],
|
||||
text: path,
|
||||
tooltips: [
|
||||
intl.formatMessage({ id: 'common.button.copy' }),
|
||||
intl.formatMessage({ id: 'common.button.copied' })
|
||||
]
|
||||
}}
|
||||
>
|
||||
{path}
|
||||
</TypographyPara>
|
||||
);
|
||||
};
|
||||
|
||||
const getWorkerName = (
|
||||
id: number,
|
||||
workersList: Global.BaseOption<number>[]
|
||||
) => {
|
||||
const worker = workersList.find((item) => item.value === id);
|
||||
return worker?.label || '';
|
||||
};
|
||||
|
||||
const getModelInfo = (record: ListItem) => {
|
||||
const source = _.get(modelSourceMap, record.source, '');
|
||||
if (record.source === modelSourceMap.huggingface_value) {
|
||||
return {
|
||||
source: `${source}/${record.huggingface_repo_id}`,
|
||||
repo_id: record.huggingface_repo_id,
|
||||
title: `${record.huggingface_repo_id}/${record.huggingface_filename}`,
|
||||
filename: record.huggingface_filename || record.huggingface_repo_id
|
||||
};
|
||||
}
|
||||
if (record.source === modelSourceMap.modelscope_value) {
|
||||
return {
|
||||
source: `${source}/${record.model_scope_model_id}`,
|
||||
repo_id: record.model_scope_model_id,
|
||||
title: `${record.model_scope_model_id}/${record.model_scope_file_path}`,
|
||||
filename: record.model_scope_file_path || record.model_scope_model_id
|
||||
};
|
||||
}
|
||||
if (record.source === modelSourceMap.ollama_library_value) {
|
||||
return {
|
||||
source: `${source}/${record.ollama_library_model_name}`,
|
||||
repo_id: record.ollama_library_model_name,
|
||||
title: record.ollama_library_model_name,
|
||||
filename: record.ollama_library_model_name
|
||||
};
|
||||
}
|
||||
return {
|
||||
source: `${source}${record.local_path}`,
|
||||
repo_id: record.local_path,
|
||||
title: record.local_path,
|
||||
filename: _.split(record.local_path, /[\\/]/).pop()
|
||||
};
|
||||
};
|
||||
|
||||
const getResolvedPath = (pathList: string[]) => {
|
||||
return _.split(pathList?.[0], /[\\/]/).pop();
|
||||
};
|
||||
|
||||
const InstanceStatusTag = (props: { data: ListItem }) => {
|
||||
const { data } = props;
|
||||
if (!data.state) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<StatusTag
|
||||
download={
|
||||
data.state === ModelfileStateMap.Downloading
|
||||
? { percent: data.download_progress }
|
||||
: undefined
|
||||
}
|
||||
statusValue={{
|
||||
status:
|
||||
data.state === ModelfileStateMap.Downloading &&
|
||||
data.download_progress === 100
|
||||
? ModelfileState[ModelfileStateMap.Ready]
|
||||
: ModelfileState[data.state],
|
||||
text: ModelfileStateMapValue[data.state],
|
||||
message:
|
||||
data.state === ModelfileStateMap.Downloading &&
|
||||
data.download_progress === 100
|
||||
? ''
|
||||
: data.state_message
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const RenderParts = (props: { record: ListItem }) => {
|
||||
const { record } = props;
|
||||
const intl = useIntl();
|
||||
const parts = record.resolved_paths || [];
|
||||
if (parts.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderItem = () => {
|
||||
return (
|
||||
<ItemWrapper>
|
||||
{parts.map((item: string, index: number) => {
|
||||
return <li key={index}>{_.split(item, /[\\/]/).pop()}</li>;
|
||||
})}
|
||||
</ItemWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipOverlayScroller title={renderItem()}>
|
||||
<FilesTag color="purple" icon={<InfoCircleOutlined />}>
|
||||
<span style={{ opacity: 1 }}>
|
||||
{record.resolved_paths?.length}{' '}
|
||||
{intl.formatMessage({ id: 'models.form.files' })}
|
||||
</span>
|
||||
</FilesTag>
|
||||
</TooltipOverlayScroller>
|
||||
);
|
||||
};
|
||||
|
||||
const ResolvedPathColumn = (props: { record: ListItem }) => {
|
||||
const { record } = props;
|
||||
const intl = useIntl();
|
||||
if (
|
||||
!record.resolved_paths.length &&
|
||||
record.state === ModelfileStateMap.Downloading
|
||||
) {
|
||||
return (
|
||||
<span>
|
||||
{intl.formatMessage({
|
||||
id: 'resources.modelfiles.storagePath.holder'
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
record.resolved_paths?.length > 0 && (
|
||||
<PathWrapper>
|
||||
<TextWrapper>
|
||||
<AutoTooltip
|
||||
ghost
|
||||
showTitle
|
||||
title={
|
||||
<TooltipTitle path={record.resolved_paths?.[0]}></TooltipTitle>
|
||||
}
|
||||
>
|
||||
<span>{getResolvedPath(record.resolved_paths)}</span>
|
||||
</AutoTooltip>
|
||||
</TextWrapper>
|
||||
<RenderParts record={record}></RenderParts>
|
||||
</PathWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const ModelFiles = () => {
|
||||
const { getWorkerOptionList, workerOptions, clusterList, workersList } =
|
||||
useGenerateWorkerOptions();
|
||||
@@ -387,7 +145,7 @@ const ModelFiles = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const handleSelect = async (val: any, record: ListItem) => {
|
||||
const handleSelect = useMemoizedFn(async (val: any, record: ListItem) => {
|
||||
try {
|
||||
if (val === 'delete') {
|
||||
handleDelete(
|
||||
@@ -419,7 +177,7 @@ const ModelFiles = () => {
|
||||
} catch (error) {
|
||||
// console.log('error', error);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const renderEmpty = (type?: string) => {
|
||||
if (type !== 'Table') return;
|
||||
@@ -464,14 +222,6 @@ const ModelFiles = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const setActionList = (record: ListItem) => {
|
||||
return _.filter(modelFileActions, (item: { key: string }) => {
|
||||
if (item.key === 'deploy') {
|
||||
return record.state === ModelfileStateMap.Ready;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
const handleDeployModalCancel = () => {
|
||||
setOpenDeployModal({
|
||||
...openDeployModal,
|
||||
@@ -506,104 +256,19 @@ const ModelFiles = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const columns: any[] = [
|
||||
{
|
||||
title: intl.formatMessage({ id: 'models.form.source' }),
|
||||
dataIndex: 'source',
|
||||
ellipsis: {
|
||||
showTitle: false
|
||||
},
|
||||
render: (text: string, record: ListItem) => {
|
||||
const modelInfo = getModelInfo(record);
|
||||
const { repo_id, source } = modelInfo;
|
||||
return (
|
||||
<TextWrapper style={{ paddingRight: 8 }}>
|
||||
<AutoTooltip ghost title={source}>
|
||||
{source}
|
||||
</AutoTooltip>
|
||||
</TextWrapper>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Worker',
|
||||
dataIndex: 'worker_name',
|
||||
ellipsis: {
|
||||
showTitle: false
|
||||
},
|
||||
render: (text: string, record: ListItem) => {
|
||||
return (
|
||||
<AutoTooltip ghost>
|
||||
<span>{getWorkerName(record.worker_id, workersList)}</span>
|
||||
</AutoTooltip>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'common.table.status' }),
|
||||
dataIndex: 'state',
|
||||
width: 132,
|
||||
render: (text: string, record: ListItem) => {
|
||||
return <InstanceStatusTag data={record} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'resources.modelfiles.form.path' }),
|
||||
dataIndex: 'resolved_paths',
|
||||
width: '30%',
|
||||
ellipsis: {
|
||||
showTitle: false
|
||||
},
|
||||
render: (text: string, record: ListItem) => (
|
||||
<ResolvedPathColumn record={record} />
|
||||
)
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'resources.modelfiles.size' }),
|
||||
dataIndex: 'size',
|
||||
width: 110,
|
||||
align: 'right',
|
||||
ellipsis: {
|
||||
showTitle: false
|
||||
},
|
||||
render: (text: string, record: ListItem) => {
|
||||
return (
|
||||
<AutoTooltip ghost>
|
||||
<span>{convertFileSize(record.size, 1, true)}</span>
|
||||
</AutoTooltip>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
||||
dataIndex: 'created_at',
|
||||
sorter: false,
|
||||
width: 180,
|
||||
ellipsis: {
|
||||
showTitle: false
|
||||
},
|
||||
render: (text: number) => (
|
||||
<AutoTooltip ghost minWidth={20}>
|
||||
{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}
|
||||
</AutoTooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'common.table.operation' }),
|
||||
dataIndex: 'operation',
|
||||
width: 120,
|
||||
render: (text: string, record: ListItem) => (
|
||||
<DropdownButtons
|
||||
items={setActionList(record)}
|
||||
onSelect={(val) => handleSelect(val, record)}
|
||||
></DropdownButtons>
|
||||
)
|
||||
}
|
||||
];
|
||||
const columns = useFilesColumns({
|
||||
handleSelect,
|
||||
workersList
|
||||
});
|
||||
|
||||
const readyWorkers = useMemo(() => {
|
||||
return workersList.filter((item) => item.state === WorkerStatusMap.ready);
|
||||
}, [workersList]);
|
||||
return workerOptions.map((item) => {
|
||||
item.children = item.children?.filter(
|
||||
(child) => child.state === WorkerStatusMap.ready
|
||||
);
|
||||
return item;
|
||||
});
|
||||
}, [workerOptions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -622,8 +287,8 @@ const ModelFiles = () => {
|
||||
marginBottom={22}
|
||||
marginTop={30}
|
||||
actionType="dropdown"
|
||||
selectHolder="resources.filter.worker"
|
||||
inputHolder="resources.filter.path"
|
||||
selectHolder={intl.formatMessage({ id: 'resources.filter.worker' })}
|
||||
inputHolder={intl.formatMessage({ id: 'resources.filter.path' })}
|
||||
buttonText={intl.formatMessage({
|
||||
id: 'resources.modelfiles.download'
|
||||
})}
|
||||
@@ -666,8 +331,7 @@ const ModelFiles = () => {
|
||||
source={downloadModalStatus.source}
|
||||
width={downloadModalStatus.width}
|
||||
hasLinuxWorker={downloadModalStatus.hasLinuxWorker}
|
||||
workersList={readyWorkers}
|
||||
workerOptions={workerOptions}
|
||||
workerOptions={readyWorkers}
|
||||
></DownloadModal>
|
||||
<DeployModal
|
||||
deploymentType="modelFiles"
|
||||
|
||||
Reference in New Issue
Block a user