diff --git a/src/hooks/use-table-fetch.ts b/src/hooks/use-table-fetch.ts index 6497bc54..195015fc 100644 --- a/src/hooks/use-table-fetch.ts +++ b/src/hooks/use-table-fetch.ts @@ -112,20 +112,23 @@ export default function useTableFetch( } }; - const fetchData = async (params?: { query: any }, polling = false) => { + const fetchData = async ( + params?: { query: Record; 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( }; 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( } setDataSource({ - dataList: res.items || [], + dataList: loadmore + ? [...dataSource.dataList, ...(res.items || [])] + : res.items || [], loading: false, loadend: true, total: res.pagination.total, diff --git a/src/locales/en-US/models.ts b/src/locales/en-US/models.ts index 5cba55cb..b2cbf5f9 100644 --- a/src/locales/en-US/models.ts +++ b/src/locales/en-US/models.ts @@ -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' }; diff --git a/src/locales/en-US/no-result.ts b/src/locales/en-US/no-result.ts new file mode 100644 index 00000000..208dfc27 --- /dev/null +++ b/src/locales/en-US/no-result.ts @@ -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.' +}; diff --git a/src/locales/en-US/users.ts b/src/locales/en-US/users.ts index b750cc02..ec2a6595 100644 --- a/src/locales/en-US/users.ts +++ b/src/locales/en-US/users.ts @@ -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', diff --git a/src/locales/ja-JP/models.ts b/src/locales/ja-JP/models.ts index 7d855d65..b31f2f77 100644 --- a/src/locales/ja-JP/models.ts +++ b/src/locales/ja-JP/models.ts @@ -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 ========== diff --git a/src/locales/ja-JP/no-result.ts b/src/locales/ja-JP/no-result.ts new file mode 100644 index 00000000..208dfc27 --- /dev/null +++ b/src/locales/ja-JP/no-result.ts @@ -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.' +}; diff --git a/src/locales/ru-RU/models.ts b/src/locales/ru-RU/models.ts index ec229253..4d715d57 100644 --- a/src/locales/ru-RU/models.ts +++ b/src/locales/ru-RU/models.ts @@ -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 ========== diff --git a/src/locales/ru-RU/no-result.ts b/src/locales/ru-RU/no-result.ts new file mode 100644 index 00000000..208dfc27 --- /dev/null +++ b/src/locales/ru-RU/no-result.ts @@ -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.' +}; diff --git a/src/locales/zh-CN/models.ts b/src/locales/zh-CN/models.ts index 74675413..39e1b6f3 100644 --- a/src/locales/zh-CN/models.ts +++ b/src/locales/zh-CN/models.ts @@ -183,5 +183,6 @@ export default { 'models.accessSettings.authed': '认证用户', 'models.accessSettings.allowedUsers': '指定用户', 'models.accessSettings.public.tips': - '公开后,任何人无需认证即可访问,可能存在数据泄露风险。' + '公开后,任何人无需认证即可访问,可能存在数据泄露风险。', + 'models.table.button.deploy': '立即部署' }; diff --git a/src/locales/zh-CN/no-result.ts b/src/locales/zh-CN/no-result.ts new file mode 100644 index 00000000..1e235f3f --- /dev/null +++ b/src/locales/zh-CN/no-result.ts @@ -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': '未找到匹配的模型' +}; diff --git a/src/pages/_components/no-result.tsx b/src/pages/_components/no-result.tsx new file mode 100644 index 00000000..67b37ffe --- /dev/null +++ b/src/pages/_components/no-result.tsx @@ -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; + 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 ? ( + + {Empty.PRESENTED_IMAGE_SIMPLE} + + ) : ( + {props.image} + ) + } + description={ + + {!hasFilters && ( + + {props.title} + + )} + + {hasFilters ? noFoundText : props.subTitle} + + + } + > + {!hasFilters && props.children} + + ) : ( + + )} + + ); +}; + +export default NoResult; diff --git a/src/pages/api-keys/index.tsx b/src/pages/api-keys/index.tsx index ecd7eb80..ccccbec1 100644 --- a/src/pages/api-keys/index.tsx +++ b/src/pages/api-keys/index.tsx @@ -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 ; - } - return
; + return ( + } + filters={queryParams} + noFoundText={intl.formatMessage({ + id: 'noresult.keys.nofound' + })} + title={intl.formatMessage({ id: 'noresult.keys.title' })} + subTitle={intl.formatMessage({ id: 'noresult.keys.subTitle' })} + > + + + ); }; const columns = useKeysColumns({ handleSelect: onSelect, sortOrder }); diff --git a/src/pages/backends/index.tsx b/src/pages/backends/index.tsx index b8c5c38d..5a1d5ae9 100644 --- a/src/pages/backends/index.tsx +++ b/src/pages/backends/index.tsx @@ -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} > + } + filters={queryParams} + noFoundText={intl.formatMessage({ + id: 'noresult.backend.nofound' + })} + title={intl.formatMessage({ id: 'noresult.backend.title' })} + subTitle={intl.formatMessage({ id: 'noresult.backend.subTitle' })} + > + + { +const Clusters: React.FC = () => { const { dataSource, rowSelection, @@ -372,6 +374,26 @@ const Credentials: React.FC = () => { columns={columns} childParentKey="cluster_id" expandable={true} + empty={ + } + filters={queryParams} + noFoundText={intl.formatMessage({ + id: 'noresult.cluster.nofound' + })} + title={intl.formatMessage({ id: 'noresult.cluster.title' })} + subTitle={intl.formatMessage({ + id: 'noresult.cluster.subTitle' + })} + > + + + } pagination={{ showSizeChanger: true, pageSize: queryParams.perPage, @@ -451,4 +473,4 @@ const Credentials: React.FC = () => { ); }; -export default Credentials; +export default Clusters; diff --git a/src/pages/cluster-management/credentials.tsx b/src/pages/cluster-management/credentials.tsx index 33d69612..e31c69d5 100644 --- a/src/pages/cluster-management/credentials.tsx +++ b/src/pages/cluster-management/credentials.tsx @@ -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 ; - } - return
; + return ( + } + filters={queryParams} + noFoundText={intl.formatMessage({ + id: 'noresult.credentials.nofound' + })} + title={intl.formatMessage({ id: 'noresult.credentials.title' })} + subTitle={intl.formatMessage({ + id: 'noresult.credentials.subTitle' + })} + > + + + ); }; const columns = useCredentialColumns(sortOrder, handleSelect); diff --git a/src/pages/llmodels/apis/index.ts b/src/pages/llmodels/apis/index.ts index 31906b91..b3fdeabb 100644 --- a/src/pages/llmodels/apis/index.ts +++ b/src/pages/llmodels/apis/index.ts @@ -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 diff --git a/src/pages/llmodels/catalog.tsx b/src/pages/llmodels/catalog.tsx index 2bd387d0..eda06b3a 100644 --- a/src/pages/llmodels/catalog.tsx +++ b/src/pages/llmodels/catalog.tsx @@ -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} > + } + filters={queryParams} + noFoundText={intl.formatMessage({ + id: 'noresult.catalog.nofound' + })} + title={intl.formatMessage({ id: 'noresult.catalog.title' })} + subTitle={intl.formatMessage({ id: 'noresult.catalog.subTitle' })} + > = (props) => { + const intl = useIntl(); const { dataList, loading, activeId, isFirst, onDeploy } = props; const { total, diff --git a/src/pages/llmodels/components/table-list.tsx b/src/pages/llmodels/components/table-list.tsx index d8110662..43c2a50b 100644 --- a/src/pages/llmodels/components/table-list.tsx +++ b/src/pages/llmodels/components/table-list.tsx @@ -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 = ({ loadChildren={getModelInstances} loadChildrenAPI={generateChildrenRequestAPI} renderChildren={renderChildren} + empty={ + } + filters={queryParams} + noFoundText={intl.formatMessage({ + id: 'noresult.mymodels.nofound' + })} + title={intl.formatMessage({ id: 'noresult.deployments.title' })} + subTitle={intl.formatMessage({ + id: 'noresult.deployments.subTitle' + })} + > + + + } pagination={{ showSizeChanger: true, pageSize: queryParams.perPage, diff --git a/src/pages/llmodels/config/backend-parameters/index.ts b/src/pages/llmodels/config/backend-parameters/index.ts index 9885de8a..e99420c2 100644 --- a/src/pages/llmodels/config/backend-parameters/index.ts +++ b/src/pages/llmodels/config/backend-parameters/index.ts @@ -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' }; diff --git a/src/pages/llmodels/config/button-actions.ts b/src/pages/llmodels/config/button-actions.ts index 26077fc8..89d8a8b9 100644 --- a/src/pages/llmodels/config/button-actions.ts +++ b/src/pages/llmodels/config/button-actions.ts @@ -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)', diff --git a/src/pages/llmodels/config/types.ts b/src/pages/llmodels/config/types.ts index 671d7153..903ec758 100644 --- a/src/pages/llmodels/config/types.ts +++ b/src/pages/llmodels/config/types.ts @@ -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; + }[]; +} diff --git a/src/pages/llmodels/hooks/use-query-backends.ts b/src/pages/llmodels/hooks/use-query-backends.ts index b580775e..a6675f03 100644 --- a/src/pages/llmodels/hooks/use-query-backends.ts +++ b/src/pages/llmodels/hooks/use-query-backends.ts @@ -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 })) }; }); diff --git a/src/pages/llmodels/index.tsx b/src/pages/llmodels/index.tsx index bab7fcc3..32c2e535 100644 --- a/src/pages/llmodels/index.tsx +++ b/src/pages/llmodels/index.tsx @@ -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 }); }; diff --git a/src/pages/llmodels/user-models.tsx b/src/pages/llmodels/user-models.tsx index 11b1075b..b82b9e46 100644 --- a/src/pages/llmodels/user-models.tsx +++ b/src/pages/llmodels/user-models.tsx @@ -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} > + } + filters={queryParams} + noFoundText={intl.formatMessage({ + id: 'noresult.mymodels.nofound' + })} + title={intl.formatMessage({ id: 'noresult.mymodels.title' })} + subTitle={intl.formatMessage({ id: 'noresult.mymodels.subTitle' })} + > ); diff --git a/src/pages/resources/components/gpus.tsx b/src/pages/resources/components/gpus.tsx index 183e0241..0ffbabf9 100644 --- a/src/pages/resources/components/gpus.tsx +++ b/src/pages/resources/components/gpus.tsx @@ -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 ; - } - return
; + return ( + } + filters={queryParams} + noFoundText={intl.formatMessage({ id: 'noresult.gpus.nofound' })} + title={intl.formatMessage({ id: 'noresult.gpus.title' })} + subTitle={intl.formatMessage({ id: 'noresult.gpus.subTitle' })} + > + ); }; const columns = useGPUColumns({ diff --git a/src/pages/resources/components/model-files.tsx b/src/pages/resources/components/model-files.tsx index 1b8261b6..701c8cc5 100644 --- a/src/pages/resources/components/model-files.tsx +++ b/src/pages/resources/components/model-files.tsx @@ -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 ; - } - return
; - }; - 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 ( + } + filters={queryParams} + noFoundText={intl.formatMessage({ id: 'noresult.modelfiles.nofound' })} + title={intl.formatMessage({ id: 'noresult.modelfiles.title' })} + subTitle={intl.formatMessage({ id: 'noresult.modelfiles.subTitle' })} + > + ); + }; + const handleDownloadCancel = () => { setDownlaodMoalStatus({ ...downloadModalStatus, diff --git a/src/pages/resources/components/workers.tsx b/src/pages/resources/components/workers.tsx index b23dd0b4..fd7d0072 100644 --- a/src/pages/resources/components/workers.tsx +++ b/src/pages/resources/components/workers.tsx @@ -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 ; - } - return
; + return ( + } + filters={queryParams} + noFoundText={intl.formatMessage({ id: 'noresult.workers.nofound' })} + title={intl.formatMessage({ id: 'noresult.workers.title' })} + subTitle={intl.formatMessage({ id: 'noresult.workers.subTitle' })} + > + + + ); }; const handleClusterChange = (value: number) => { diff --git a/src/pages/users/index.tsx b/src/pages/users/index.tsx index 15ed07fd..a9c65066 100644 --- a/src/pages/users/index.tsx +++ b/src/pages/users/index.tsx @@ -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 ; - } - return
; + return ( + } + filters={queryParams} + noFoundText={intl.formatMessage({ + id: 'noresult.users.nofound' + })} + title={intl.formatMessage({ id: 'noresult.users.title' })} + subTitle={intl.formatMessage({ id: 'noresult.users.subTitle' })} + > + + + ); }; const columns = useUsersColumns({