style: ui for no data in table list

This commit is contained in:
jialin
2025-10-16 11:11:39 +08:00
parent ca9ceda2f7
commit e2da7c5f96
29 changed files with 546 additions and 108 deletions
+12 -5
View File
@@ -112,20 +112,23 @@ export default function useTableFetch<T>(
}
};
const fetchData = async (params?: { query: any }, polling = false) => {
const fetchData = async (
params?: { query: Record<string, any>; loadmore?: boolean },
polling = false
) => {
if (!polling) {
setDataSource((pre) => {
pre.loading = true;
return { ...pre };
});
}
const { query } = params || {};
const { query, loadmore } = params || {};
try {
const params = {
..._.pickBy(query || queryParams, (val: any) => !!val)
};
const res = await fetchAPI(params);
if (
!res.items.length &&
params.page > res.pagination.totalPage &&
@@ -137,7 +140,9 @@ export default function useTableFetch<T>(
};
const newRes = await fetchAPI(newParams);
setDataSource({
dataList: newRes.items || [],
dataList: loadmore
? [...dataSource.dataList, ...(newRes.items || [])]
: newRes.items || [],
loading: false,
loadend: true,
total: newRes.pagination.total,
@@ -150,7 +155,9 @@ export default function useTableFetch<T>(
}
setDataSource({
dataList: res.items || [],
dataList: loadmore
? [...dataSource.dataList, ...(res.items || [])]
: res.items || [],
loading: false,
loadend: true,
total: res.pagination.total,
+2 -1
View File
@@ -193,5 +193,6 @@ export default {
'models.accessSettings.authed': 'Authenticated',
'models.accessSettings.allowedUsers': 'Allowed users',
'models.accessSettings.public.tips':
'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.'
'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.',
'models.table.button.deploy': 'Deploy Now'
};
+37
View File
@@ -0,0 +1,37 @@
export default {
'noresult.button.add': 'Add Now',
'noresult.mymodels.title': 'No Accessible Models',
'noresult.mymodels.subTitle': 'Please contact the administrator for access.',
'noresult.mymodels.nofound': 'No matching models found',
'noresult.deployments.title': 'No Deployed Models',
'noresult.deployments.subTitle':
'You have not deployed any models yet. Click the button below to get started.',
'noresult.gpus.title': 'No GPU Devices Detected',
'noresult.gpus.subTitle': 'Please check if the Worker status is READY.',
'noresult.gpus.nofound': 'No matching GPU devices found.',
'noresult.workers.title': 'No Workers',
'noresult.workers.subTitle': 'No workers found in any clusters.',
'noresult.workers.nofound': 'No matching workers found.',
'noresult.workers.button.add': 'Go to Add Worker',
'noresult.modelfiles.title': 'No Model Files',
'noresult.modelfiles.subTitle': 'No model files have been added yet.',
'noresult.modelfiles.nofound': 'No matching model files found.',
'noresult.backend.title': 'No Inference Backends',
'noresult.backend.subTitle': 'No inference backends have been added yet.',
'noresult.backend.nofound': 'No matching inference backends found.',
'noresult.cluster.title': 'No Clusters',
'noresult.cluster.subTitle': 'No clusters have been added yet.',
'noresult.cluster.nofound': 'No matching clusters found.',
'noresult.credentials.title': 'No Credentials',
'noresult.credentials.subTitle': 'No credentials have been added yet.',
'noresult.credentials.nofound': 'No matching credentials found.',
'noresult.users.title': 'No Users',
'noresult.users.subTitle': 'No users have been added yet.',
'noresult.users.nofound': 'No matching users found.',
'noresult.keys.title': 'No API Keys',
'noresult.keys.subTitle': 'No API keys have been added yet.',
'noresult.keys.nofound': 'No matching API keys found.',
'noresult.catalog.title': 'No Models',
'noresult.catalog.subTitle': 'No models have been configured yet.',
'noresult.catalog.nofound': 'No matching models found.'
};
+1 -1
View File
@@ -8,7 +8,7 @@ export default {
'users.table.status': 'Status',
'users.status.active': 'Active',
'users.status.inactive': 'Inactive',
'users.form.active': 'Active account',
'users.form.active': 'Active Account',
'users.form.active.description': 'Enable or disable this user account',
'users.form.fullname': 'Full Name',
'users.form.source': 'Source',
+4 -2
View File
@@ -190,7 +190,8 @@ export default {
'models.accessSettings.authed': 'Authenticated',
'models.accessSettings.allowedUsers': 'Allowed users',
'models.accessSettings.public.tips':
'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.'
'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.',
'models.table.button.deploy': 'Deploy Now'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -222,6 +223,7 @@ export default {
// 27. 'models.accessSettings.public': 'Public',
// 28. 'models.accessSettings.authed': 'Authenticated',
// 29. 'models.accessSettings.allowedUsers': 'Allowed users',
// 30. 'models.accessSettings.public.tips': 'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.'
// 30. 'models.accessSettings.public.tips': 'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.',
// 31. 'models.table.button.deploy': 'Deploy Now'
// ========== End of To-Do List ==========
+37
View File
@@ -0,0 +1,37 @@
export default {
'noresult.button.add': 'Add Now',
'noresult.mymodels.title': 'No Accessible Models',
'noresult.mymodels.subTitle': 'Please contact the administrator for access.',
'noresult.mymodels.nofound': 'No matching models found',
'noresult.deployments.title': 'No Deployed Models',
'noresult.deployments.subTitle':
'You have not deployed any models yet. Click the button below to get started.',
'noresult.gpus.title': 'No GPU Devices Detected',
'noresult.gpus.subTitle': 'Please check if the Worker status is READY.',
'noresult.gpus.nofound': 'No matching GPU devices found.',
'noresult.workers.title': 'No Workers',
'noresult.workers.subTitle': 'No workers found in any clusters.',
'noresult.workers.nofound': 'No matching workers found.',
'noresult.workers.button.add': 'Go to Add Worker',
'noresult.modelfiles.title': 'No Model Files',
'noresult.modelfiles.subTitle': 'No model files have been added yet.',
'noresult.modelfiles.nofound': 'No matching model files found.',
'noresult.backend.title': 'No Inference Backends',
'noresult.backend.subTitle': 'No inference backends have been added yet.',
'noresult.backend.nofound': 'No matching inference backends found.',
'noresult.cluster.title': 'No Clusters',
'noresult.cluster.subTitle': 'No clusters have been added yet.',
'noresult.cluster.nofound': 'No matching clusters found.',
'noresult.credentials.title': 'No Credentials',
'noresult.credentials.subTitle': 'No credentials have been added yet.',
'noresult.credentials.nofound': 'No matching credentials found.',
'noresult.users.title': 'No Users',
'noresult.users.subTitle': 'No users have been added yet.',
'noresult.users.nofound': 'No matching users found.',
'noresult.keys.title': 'No API Keys',
'noresult.keys.subTitle': 'No API keys have been added yet.',
'noresult.keys.nofound': 'No matching API keys found.',
'noresult.catalog.title': 'No Models',
'noresult.catalog.subTitle': 'No models have been configured yet.',
'noresult.catalog.nofound': 'No matching models found.'
};
+4 -2
View File
@@ -193,7 +193,8 @@ export default {
'models.accessSettings.authed': 'Authenticated',
'models.accessSettings.allowedUsers': 'Allowed users',
'models.accessSettings.public.tips':
'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.'
'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.',
'models.table.button.deploy': 'Deploy Now'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -217,5 +218,6 @@ export default {
// 19. 'models.accessSettings.public': 'Public',
// 20. 'models.accessSettings.authed': 'Authenticated',
// 21. 'models.accessSettings.allowedUsers': 'Allowed users',
// 22. 'models.accessSettings.public.tips': 'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.'
// 22. 'models.accessSettings.public.tips': 'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.',
// 23. 'models.table.button.deploy': 'Deploy Now'
// ========== End of To-Do List ==========
+37
View File
@@ -0,0 +1,37 @@
export default {
'noresult.button.add': 'Add Now',
'noresult.mymodels.title': 'No Accessible Models',
'noresult.mymodels.subTitle': 'Please contact the administrator for access.',
'noresult.mymodels.nofound': 'No matching models found',
'noresult.deployments.title': 'No Deployed Models',
'noresult.deployments.subTitle':
'You have not deployed any models yet. Click the button below to get started.',
'noresult.gpus.title': 'No GPU Devices Detected',
'noresult.gpus.subTitle': 'Please check if the Worker status is READY.',
'noresult.gpus.nofound': 'No matching GPU devices found.',
'noresult.workers.title': 'No Workers',
'noresult.workers.subTitle': 'No workers found in any clusters.',
'noresult.workers.nofound': 'No matching workers found.',
'noresult.workers.button.add': 'Go to Add Worker',
'noresult.modelfiles.title': 'No Model Files',
'noresult.modelfiles.subTitle': 'No model files have been added yet.',
'noresult.modelfiles.nofound': 'No matching model files found.',
'noresult.backend.title': 'No Inference Backends',
'noresult.backend.subTitle': 'No inference backends have been added yet.',
'noresult.backend.nofound': 'No matching inference backends found.',
'noresult.cluster.title': 'No Clusters',
'noresult.cluster.subTitle': 'No clusters have been added yet.',
'noresult.cluster.nofound': 'No matching clusters found.',
'noresult.credentials.title': 'No Credentials',
'noresult.credentials.subTitle': 'No credentials have been added yet.',
'noresult.credentials.nofound': 'No matching credentials found.',
'noresult.users.title': 'No Users',
'noresult.users.subTitle': 'No users have been added yet.',
'noresult.users.nofound': 'No matching users found.',
'noresult.keys.title': 'No API Keys',
'noresult.keys.subTitle': 'No API keys have been added yet.',
'noresult.keys.nofound': 'No matching API keys found.',
'noresult.catalog.title': 'No Models',
'noresult.catalog.subTitle': 'No models have been configured yet.',
'noresult.catalog.nofound': 'No matching models found.'
};
+2 -1
View File
@@ -183,5 +183,6 @@ export default {
'models.accessSettings.authed': '认证用户',
'models.accessSettings.allowedUsers': '指定用户',
'models.accessSettings.public.tips':
'公开后,任何人无需认证即可访问,可能存在数据泄露风险。'
'公开后,任何人无需认证即可访问,可能存在数据泄露风险。',
'models.table.button.deploy': '立即部署'
};
+37
View File
@@ -0,0 +1,37 @@
export default {
'noresult.button.add': '立即添加',
'noresult.mymodels.title': '暂无可访问的模型',
'noresult.mymodels.subTitle': '请联系管理员获取访问权限。',
'noresult.mymodels.nofound': '未找到匹配的模型',
'noresult.deployments.title': '尚未部署模型',
'noresult.deployments.subTitle':
'您还没有部署任何模型,点击下方按钮即可开始。',
'noresult.gpus.title': '当前未检测到 GPU 设备',
'noresult.gpus.subTitle': '请确认 Worker 状态是否正常。',
'noresult.gpus.nofound': '未找到匹配的 GPU 设备',
'noresult.workers.title': '暂无 Worker',
'noresult.workers.subTitle': '当前集群中没有任何 Worker。',
'noresult.workers.nofound': '未找到匹配的 Worker',
'noresult.workers.button.add': '前往添加 Worker',
'noresult.modelfiles.title': '暂无模型文件',
'noresult.modelfiles.subTitle': '尚未添加任何模型文件。',
'noresult.modelfiles.nofound': '未找到匹配的模型文件',
'noresult.backend.title': '暂无推理后端',
'noresult.backend.subTitle': '尚未添加任何推理后端。',
'noresult.backend.nofound': '未找到匹配的推理后端',
'noresult.cluster.title': '暂无集群',
'noresult.cluster.subTitle': '尚未添加任何集群。',
'noresult.cluster.nofound': '未找到匹配的集群',
'noresult.credentials.title': '暂无凭据',
'noresult.credentials.subTitle': '尚未添加任何凭据。',
'noresult.credentials.nofound': '未找到匹配的凭据',
'noresult.users.title': '暂无用户',
'noresult.users.subTitle': '尚未添加任何用户。',
'noresult.users.nofound': '未找到匹配的用户',
'noresult.keys.title': '暂无 API 密钥',
'noresult.keys.subTitle': '尚未添加任何 API 密钥。',
'noresult.keys.nofound': '未找到匹配的 API 密钥',
'noresult.catalog.title': '暂无模型',
'noresult.catalog.subTitle': '尚未配置任何模型。',
'noresult.catalog.nofound': '未找到匹配的模型'
};
+108
View File
@@ -0,0 +1,108 @@
import { Empty, EmptyProps, Typography } from 'antd';
import _ from 'lodash';
import React, { useMemo } from 'react';
import styled from 'styled-components';
const StyledEmpty = styled(Empty)`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-block: 60px 32px;
.ant-empty-image {
margin-bottom: 0;
height: auto;
line-height: 1;
font-size: 42px;
.anticon {
color: var(--ant-color-primary);
}
}
.ant-empty-footer {
display: flex;
}
`;
const ImageWrapper = styled.div`
margin-bottom: 16px;
display: flex;
justify-content: center;
align-items: center;
`;
const SimpleImageWrapper = styled.div`
margin-bottom: 8px;
display: flex;
justify-content: center;
align-items: center;
`;
const Description = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
justify-content: center;
align-items: center;
`;
const NoResult: React.FC<
EmptyProps & {
title?: React.ReactNode;
subTitle?: React.ReactNode;
noFoundText?: React.ReactNode;
filters?: Record<string, any>;
loading?: boolean;
loadend?: boolean;
dataSource?: any[];
}
> = (props) => {
const { filters, noFoundText, loadend, loading, dataSource } = props;
const hasFilters = useMemo(() => {
const filterValues = _.omit(filters, ['page', 'perPage']);
return Object.values(filterValues || {}).some((value) => {
if (Array.isArray(value)) {
return value.length > 0;
}
return !!value;
});
}, [filters]);
return (
<>
{!loading && loadend && !dataSource?.length ? (
<StyledEmpty
image={
hasFilters ? (
<SimpleImageWrapper>
{Empty.PRESENTED_IMAGE_SIMPLE}
</SimpleImageWrapper>
) : (
<ImageWrapper>{props.image}</ImageWrapper>
)
}
description={
<Description>
{!hasFilters && (
<Typography.Text style={{ fontSize: '18px', fontWeight: 500 }}>
{props.title}
</Typography.Text>
)}
<Typography.Text type="secondary">
{hasFilters ? noFoundText : props.subTitle}
</Typography.Text>
</Description>
}
>
{!hasFilters && props.children}
</StyledEmpty>
) : (
<span></span>
)}
</>
);
};
export default NoResult;
+21 -9
View File
@@ -1,4 +1,5 @@
import DeleteModal from '@/components/delete-modal';
import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools';
import { PageAction } from '@/config';
import type { PageActionType } from '@/config/types';
@@ -6,8 +7,9 @@ import useTableFetch from '@/hooks/use-table-fetch';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
import { ConfigProvider, Empty, Table } from 'antd';
import { Button, ConfigProvider, Table } from 'antd';
import { useState } from 'react';
import NoResult from '../_components/no-result';
import { deleteApisKey, queryApisKeysList } from './apis';
import AddAPIKeyModal from './components/add-apikey-modal';
import { ListItem } from './config/types';
@@ -97,14 +99,24 @@ const APIKeys: React.FC = () => {
const renderEmpty = (type?: string) => {
if (type !== 'Table') return;
if (
!dataSource.loading &&
dataSource.loadend &&
!dataSource.dataList.length
) {
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>;
}
return <div></div>;
return (
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataSource.dataList}
image={<IconFont type="icon-key" />}
filters={queryParams}
noFoundText={intl.formatMessage({
id: 'noresult.keys.nofound'
})}
title={intl.formatMessage({ id: 'noresult.keys.title' })}
subTitle={intl.formatMessage({ id: 'noresult.keys.subTitle' })}
>
<Button type="primary" onClick={handleAddKey}>
{intl.formatMessage({ id: 'noresult.button.add' })}
</Button>
</NoResult>
);
};
const columns = useKeysColumns({ handleSelect: onSelect, sortOrder });
+24 -2
View File
@@ -1,13 +1,16 @@
import DeleteModal from '@/components/delete-modal';
import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools';
import { PageActionType } from '@/config/types';
import useTableFetch from '@/hooks/use-table-fetch';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
import { Button } from 'antd';
import _ from 'lodash';
import { useState } from 'react';
import { ScrollerContext } from '../_components/infinite-scroller/use-scroller-context';
import NoResult from '../_components/no-result';
import {
createBackend,
createBackendFromYAML,
@@ -154,8 +157,11 @@ const BackendList = () => {
const loadMore = useMemoizedFn((nextPage: number) => {
fetchData({
...queryParams,
page: nextPage
query: {
...queryParams,
page: nextPage
},
loadmore: true
});
});
@@ -204,6 +210,22 @@ const BackendList = () => {
isFirst={!dataSource.loadend}
onSelect={handleOnSelect}
></BackendCardList>
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataSource.dataList}
image={<IconFont type="icon-models" />}
filters={queryParams}
noFoundText={intl.formatMessage({
id: 'noresult.backend.nofound'
})}
title={intl.formatMessage({ id: 'noresult.backend.title' })}
subTitle={intl.formatMessage({ id: 'noresult.backend.subTitle' })}
>
<Button type="primary" onClick={handleAddBackend}>
{intl.formatMessage({ id: 'noresult.button.add' })}
</Button>
</NoResult>
</ScrollerContext.Provider>
<AddModal
action={openModalStatus.action}
+25 -3
View File
@@ -1,5 +1,6 @@
import { expandKeysAtom } from '@/atoms/clusters';
import DeleteModal from '@/components/delete-modal';
import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools';
import SealTable from '@/components/seal-table';
import TableContext from '@/components/seal-table/table-context';
@@ -12,9 +13,10 @@ import AddWorker from '@/pages/cluster-management/components/add-worker';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { message } from 'antd';
import { Button, message } from 'antd';
import { useAtom } from 'jotai';
import { useEffect, useState } from 'react';
import NoResult from '../_components/no-result';
import {
CLUSTERS_API,
createWorkerPool,
@@ -39,7 +41,7 @@ import {
} from './config/types';
import useClusterColumns from './hooks/use-cluster-columns';
const Credentials: React.FC = () => {
const Clusters: React.FC = () => {
const {
dataSource,
rowSelection,
@@ -372,6 +374,26 @@ const Credentials: React.FC = () => {
columns={columns}
childParentKey="cluster_id"
expandable={true}
empty={
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataSource.dataList}
image={<IconFont type="icon-cluster-outline" />}
filters={queryParams}
noFoundText={intl.formatMessage({
id: 'noresult.cluster.nofound'
})}
title={intl.formatMessage({ id: 'noresult.cluster.title' })}
subTitle={intl.formatMessage({
id: 'noresult.cluster.subTitle'
})}
>
<Button type="primary" onClick={handleClickDropdown}>
{intl.formatMessage({ id: 'noresult.button.add' })}
</Button>
</NoResult>
}
pagination={{
showSizeChanger: true,
pageSize: queryParams.perPage,
@@ -451,4 +473,4 @@ const Credentials: React.FC = () => {
);
};
export default Credentials;
export default Clusters;
+25 -9
View File
@@ -8,9 +8,10 @@ import useTableFetch from '@/hooks/use-table-fetch';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl, useNavigate } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { ConfigProvider, Empty, Table, message } from 'antd';
import { Button, ConfigProvider, Table, message } from 'antd';
import { useAtom } from 'jotai';
import { useState } from 'react';
import NoResult from '../_components/no-result';
import {
createCredential,
deleteCredential,
@@ -148,14 +149,29 @@ const Credentials: React.FC = () => {
const renderEmpty = (type?: string) => {
if (type !== 'Table') return;
if (
!dataSource.loading &&
dataSource.loadend &&
!dataSource.dataList.length
) {
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>;
}
return <div></div>;
return (
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={[]}
image={<IconFont type="icon-credential-outline" />}
filters={queryParams}
noFoundText={intl.formatMessage({
id: 'noresult.credentials.nofound'
})}
title={intl.formatMessage({ id: 'noresult.credentials.title' })}
subTitle={intl.formatMessage({
id: 'noresult.credentials.subTitle'
})}
>
<Button
type="primary"
onClick={() => handleAddCredential(addActions[0])}
>
{intl.formatMessage({ id: 'noresult.button.add' })}
</Button>
</NoResult>
);
};
const columns = useCredentialColumns(sortOrder, handleSelect);
+2 -8
View File
@@ -5,6 +5,7 @@ import { request } from '@umijs/max';
import qs from 'query-string';
import {
AccessControlFormData,
BackendItem,
CatalogItem,
CatalogSpec,
EvaluateResult,
@@ -405,14 +406,7 @@ export async function evaluationsModelSpec(
export async function queryBackendList(params?: { cluster_id: number }) {
return request<{
items: {
backend_name: string;
backend_show_name: string;
from_config: boolean;
default_version: string;
default_backend_param: string[];
versions: string[];
}[];
items: BackendItem[];
}>(BACKEND_LIST_API, {
method: 'GET',
params
+14
View File
@@ -1,4 +1,5 @@
import { modelsExpandKeysAtom } from '@/atoms/models';
import IconFont from '@/components/icon-font';
import PageTools from '@/components/page-tools';
import BaseSelect from '@/components/seal-form/base/select';
import { PageAction } from '@/config';
@@ -14,6 +15,7 @@ import { useAtom } from 'jotai';
import _ from 'lodash';
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import NoResult from '../_components/no-result';
import { createModel, queryCatalogList } from './apis';
import CatalogList from './components/catalog-list';
import DelopyBuiltInModal from './components/deploy-builtin-modal';
@@ -297,6 +299,18 @@ const Catalog: React.FC = () => {
activeId={-1}
isFirst={isFirst}
></CatalogList>
<NoResult
loading={dataSource.loading}
loadend={!isFirst}
dataSource={dataSource.dataList}
image={<IconFont type="icon-layers" />}
filters={queryParams}
noFoundText={intl.formatMessage({
id: 'noresult.catalog.nofound'
})}
title={intl.formatMessage({ id: 'noresult.catalog.title' })}
subTitle={intl.formatMessage({ id: 'noresult.catalog.subTitle' })}
></NoResult>
</ScrollerContext.Provider>
<PageWrapper>
<Pagination
@@ -2,6 +2,7 @@ import CatalogSkelton from '@/components/templates/card-skelton';
import breakpoints from '@/config/breakpoints';
import InfiniteScroller from '@/pages/_components/infinite-scroller';
import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
import { useIntl } from '@umijs/max';
import { Col, FloatButton, Row, Spin } from 'antd';
import _ from 'lodash';
import ResizeObserver from 'rc-resize-observer';
@@ -55,6 +56,7 @@ const ListSkeleton: React.FC<{
};
const CatalogList: React.FC<CatalogListProps> = (props) => {
const intl = useIntl();
const { dataList, loading, activeId, isFirst, onDeploy } = props;
const {
total,
@@ -2,6 +2,7 @@ import { modelsExpandKeysAtom } from '@/atoms/models';
import DeleteModal from '@/components/delete-modal';
import DropDownActions from '@/components/drop-down-actions';
import DropdownButtons from '@/components/drop-down-buttons';
import IconFont from '@/components/icon-font';
import { PageSize } from '@/components/logs-viewer/config';
import PageTools from '@/components/page-tools';
import BaseSelect from '@/components/seal-form/base/select';
@@ -12,6 +13,7 @@ import useBodyScroll from '@/hooks/use-body-scroll';
import useExpandedRowKeys from '@/hooks/use-expanded-row-keys';
import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort';
import NoResult from '@/pages/_components/no-result';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import { handleBatchRequest } from '@/utils';
import { DownOutlined, SearchOutlined, SyncOutlined } from '@ant-design/icons';
@@ -704,6 +706,30 @@ const Models: React.FC<ModelsProps> = ({
loadChildren={getModelInstances}
loadChildrenAPI={generateChildrenRequestAPI}
renderChildren={renderChildren}
empty={
<NoResult
loading={loading}
loadend={loadend}
dataSource={dataSource}
image={<IconFont type="icon-rocket-launch1" />}
filters={queryParams}
noFoundText={intl.formatMessage({
id: 'noresult.mymodels.nofound'
})}
title={intl.formatMessage({ id: 'noresult.deployments.title' })}
subTitle={intl.formatMessage({
id: 'noresult.deployments.subTitle'
})}
>
<Button
type="primary"
iconPosition="end"
onClick={() => handleClickDropdown({ key: 'catalog' })}
>
{intl?.formatMessage?.({ id: 'models.table.button.deploy' })}
</Button>
</NoResult>
}
pagination={{
showSizeChanger: true,
pageSize: queryParams.perPage,
@@ -5,7 +5,7 @@ import vllmParameters from './vllm-parameters';
export const backendOptionsMap = {
llamaBox: 'llama-box',
vllm: 'vLLM',
voxBox: 'vox-box',
voxBox: 'VoxBox',
ascendMindie: 'MindIE',
custom: 'Custom'
};
@@ -13,12 +13,6 @@ export const modalConfig: Record<
width: 'calc(100vw - 220px)',
source: modelSourceMap.huggingface_value
},
ollama_library: {
show: true,
width: 600,
isGGUF: true,
source: modelSourceMap.ollama_library_value
},
modelscope: {
show: true,
width: 'calc(100vw - 220px)',
+11
View File
@@ -264,3 +264,14 @@ export interface AccessControlFormData {
access_policy: 'public' | 'authed' | 'allowed_users';
users: { id: number }[];
}
export interface BackendItem {
backend_name: string;
from_config: boolean;
default_version: string;
default_backend_param: string[];
versions: {
version: string;
is_deprecated: boolean;
}[];
}
@@ -11,12 +11,12 @@ export default function useQueryBackends() {
const list = res?.items?.map((item) => {
return {
value: item.backend_name,
label: item.backend_show_name,
label: item.backend_name,
default_backend_param: item.default_backend_param || [],
default_version: item.default_version,
versions: (item.versions || []).map((version) => ({
label: version,
value: version
versions: (item.versions || []).map((vItem) => ({
label: vItem.version,
value: vItem.version
}))
};
});
+11 -3
View File
@@ -44,6 +44,7 @@ const Models: React.FC = () => {
page: 1,
perPage: 10,
search: '',
cluster_id: 0,
categories: []
});
@@ -191,7 +192,11 @@ const Models: React.FC = () => {
};
const createModelsChunkRequest = useCallback(
async (params?: { search: string; categories: any[] }) => {
async (params?: {
search: string;
categories: any[];
cluster_id: number;
}) => {
const search = params?.search || queryParams.search;
const categories = params?.categories || queryParams.categories;
chunkRequedtRef.current?.current?.cancel?.();
@@ -266,7 +271,8 @@ const Models: React.FC = () => {
});
createModelsChunkRequest({
search: e.target.value,
categories: queryParams.categories
categories: queryParams.categories,
cluster_id: queryParams.cluster_id || 0
});
}, 350);
@@ -279,17 +285,19 @@ const Models: React.FC = () => {
});
createModelsChunkRequest({
search: queryParams.search,
cluster_id: queryParams.cluster_id || 0,
categories: value
});
};
const handleClusterChange = async (value: any) => {
const handleClusterChange = async (value: number) => {
handleQueryChange({
page: 1,
cluster_id: value
});
createModelsChunkRequest({
search: queryParams.search,
categories: queryParams.categories,
cluster_id: value
});
};
+19 -2
View File
@@ -1,3 +1,4 @@
import IconFont from '@/components/icon-font';
import PageTools from '@/components/page-tools';
import BaseSelect from '@/components/seal-form/base/select';
import CardList from '@/components/templates/card-list';
@@ -10,6 +11,7 @@ import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
import { Button, Input, Space } from 'antd';
import React from 'react';
import { ScrollerContext } from '../_components/infinite-scroller/use-scroller-context';
import NoResult from '../_components/no-result';
import { MY_MODELS_API, queryMyModels } from './apis';
import ModelItem from './components/model-item';
import { categoryOptions, modelCategoriesMap } from './config';
@@ -67,8 +69,11 @@ const UserModels: React.FC = () => {
const loadMore = useMemoizedFn((nextPage: number) => {
fetchData({
...queryParams,
page: nextPage
query: {
...queryParams,
page: nextPage
},
loadmore: true
});
});
@@ -137,6 +142,18 @@ const UserModels: React.FC = () => {
Skeleton={CardSkeleton}
renderItem={renderCard}
></CardList>
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataSource.dataList}
image={<IconFont type="icon-models" />}
filters={queryParams}
noFoundText={intl.formatMessage({
id: 'noresult.mymodels.nofound'
})}
title={intl.formatMessage({ id: 'noresult.mymodels.title' })}
subTitle={intl.formatMessage({ id: 'noresult.mymodels.subTitle' })}
></NoResult>
</ScrollerContext.Provider>
</PageContainer>
);
+15 -9
View File
@@ -1,9 +1,11 @@
import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools';
import useTableFetch from '@/hooks/use-table-fetch';
import NoResult from '@/pages/_components/no-result';
import { queryClusterList } from '@/pages/cluster-management/apis';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import { ConfigProvider, Empty, Table } from 'antd';
import { ConfigProvider, Table } from 'antd';
import React, { useEffect, useState } from 'react';
import { GPU_DEVICES_API, queryGpuDevicesList } from '../apis';
import { GPUDeviceItem } from '../config/types';
@@ -44,14 +46,18 @@ const GPUList: React.FC = () => {
const renderEmpty = (type?: string) => {
if (type !== 'Table') return;
if (
!dataSource.loading &&
dataSource.loadend &&
!dataSource.dataList.length
) {
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>;
}
return <div></div>;
return (
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataSource.dataList}
image={<IconFont type="icon-gpu1" />}
filters={queryParams}
noFoundText={intl.formatMessage({ id: 'noresult.gpus.nofound' })}
title={intl.formatMessage({ id: 'noresult.gpus.title' })}
subTitle={intl.formatMessage({ id: 'noresult.gpus.subTitle' })}
></NoResult>
);
};
const columns = useGPUColumns({
+19 -13
View File
@@ -1,10 +1,12 @@
import { modelsExpandKeysAtom } from '@/atoms/models';
import DeleteModal from '@/components/delete-modal';
import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools';
import { PageAction } from '@/config';
import useAppUtils from '@/hooks/use-app-utils';
import useBodyScroll from '@/hooks/use-body-scroll';
import useTableFetch from '@/hooks/use-table-fetch';
import NoResult from '@/pages/_components/no-result';
import { createModel } from '@/pages/llmodels/apis';
import DeployModal from '@/pages/llmodels/components/deploy-modal';
import { modelSourceMap } from '@/pages/llmodels/config';
@@ -21,7 +23,7 @@ import useRecognizeAudio from '@/pages/llmodels/hooks/use-recognize-audio';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl, useNavigate } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { ConfigProvider, Empty, Table, message } from 'antd';
import { ConfigProvider, Table, message } from 'antd';
import { useAtom } from 'jotai';
import _ from 'lodash';
import { useEffect, useMemo, useState } from 'react';
@@ -190,18 +192,6 @@ const ModelFiles = () => {
}
});
const renderEmpty = (type?: string) => {
if (type !== 'Table') return;
if (
!dataSource.loading &&
dataSource.loadend &&
!dataSource.dataList.length
) {
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>;
}
return <div></div>;
};
const handleClickDropdown = (item: any) => {
const config = modalConfig[item.key];
const hasLinuxWorker = workersList.some(
@@ -212,6 +202,22 @@ const ModelFiles = () => {
}
};
const renderEmpty = (type?: string) => {
if (type !== 'Table') return;
return (
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataSource.dataList}
image={<IconFont type="icon-files" />}
filters={queryParams}
noFoundText={intl.formatMessage({ id: 'noresult.modelfiles.nofound' })}
title={intl.formatMessage({ id: 'noresult.modelfiles.title' })}
subTitle={intl.formatMessage({ id: 'noresult.modelfiles.subTitle' })}
></NoResult>
);
};
const handleDownloadCancel = () => {
setDownlaodMoalStatus({
...downloadModalStatus,
+25 -10
View File
@@ -1,12 +1,14 @@
import DeleteModal from '@/components/delete-modal';
import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools';
import useTableFetch from '@/hooks/use-table-fetch';
import { queryClusterList } from '@/pages/cluster-management/apis';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import { useIntl, useNavigate } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { ConfigProvider, Empty, Table, message } from 'antd';
import { Button, ConfigProvider, Table, message } from 'antd';
import React, { useEffect, useState } from 'react';
import NoResult from '../../_components/no-result';
import TerminalTabs from '../../_components/terminal-tabs';
import {
WORKERS_API,
@@ -43,6 +45,7 @@ const Workers: React.FC = () => {
API: WORKERS_API
});
const navigate = useNavigate();
const intl = useIntl();
const [updateLabelsData, setUpdateLabelsData] = useState<{
open: boolean;
@@ -175,16 +178,28 @@ const Workers: React.FC = () => {
}
});
const handleAddWorker = () => {
navigate('/cluster-management/clusters/list');
};
const renderEmpty = (type?: string) => {
if (type !== 'Table') return;
if (
!dataSource.loading &&
dataSource.loadend &&
!dataSource.dataList.length
) {
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>;
}
return <div></div>;
return (
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataSource.dataList}
image={<IconFont type="icon-resources" />}
filters={queryParams}
noFoundText={intl.formatMessage({ id: 'noresult.workers.nofound' })}
title={intl.formatMessage({ id: 'noresult.workers.title' })}
subTitle={intl.formatMessage({ id: 'noresult.workers.subTitle' })}
>
<Button type="primary" onClick={handleAddWorker}>
{intl.formatMessage({ id: 'noresult.workers.button.add' })}
</Button>
</NoResult>
);
};
const handleClusterChange = (value: number) => {
+21 -17
View File
@@ -1,4 +1,5 @@
import DeleteModal from '@/components/delete-modal';
import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools';
import { PageAction } from '@/config';
import type { PageActionType } from '@/config/types';
@@ -6,21 +7,14 @@ import useTableFetch from '@/hooks/use-table-fetch';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { ConfigProvider, Empty, message, Switch, Table } from 'antd';
import { Button, ConfigProvider, message, Table } from 'antd';
import { useState } from 'react';
import styled from 'styled-components';
import NoResult from '../_components/no-result';
import { createUser, deleteUser, queryUsersList, updateUser } from './apis';
import AddModal from './components/add-modal';
import { FormData, ListItem } from './config/types';
import useUsersColumns from './hooks/use-users-columns';
const StyledSwitch = styled(Switch)`
.ant-switch-handle::before {
inset-inline-end: 0 !important;
inset-inline-start: 0 !important;
}
`;
const Users: React.FC = () => {
const {
dataSource,
@@ -136,14 +130,24 @@ const Users: React.FC = () => {
const renderEmpty = (type?: string) => {
if (type !== 'Table') return;
if (
!dataSource.loading &&
dataSource.loadend &&
!dataSource.dataList.length
) {
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>;
}
return <div></div>;
return (
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataSource.dataList}
image={<IconFont type="icon-users" />}
filters={queryParams}
noFoundText={intl.formatMessage({
id: 'noresult.users.nofound'
})}
title={intl.formatMessage({ id: 'noresult.users.title' })}
subTitle={intl.formatMessage({ id: 'noresult.users.subTitle' })}
>
<Button type="primary" onClick={handleAddUser}>
{intl.formatMessage({ id: 'noresult.button.add' })}
</Button>
</NoResult>
);
};
const columns = useUsersColumns({