fix: gpu selector by cluster

This commit is contained in:
jialin
2025-09-16 11:26:17 +08:00
parent 207c90e26c
commit 553ec724c2
40 changed files with 1106 additions and 1233 deletions
+3 -3
View File
@@ -161,9 +161,9 @@ export default [
name: 'clusters', name: 'clusters',
path: '/cluster-management/clusters', path: '/cluster-management/clusters',
key: 'clusters', key: 'clusters',
icon: 'icon-k8s-outline', icon: 'icon-cluster2-outline',
selectedIcon: 'icon-k8s-filled', selectedIcon: 'icon-cluster2-filled',
defaultIcon: 'icon-k8s-outline', defaultIcon: 'icon-cluster2-outline',
component: './cluster-management/clusters' component: './cluster-management/clusters'
}, },
{ {
+7 -1
View File
@@ -6,6 +6,8 @@ import {
EditOutlined, EditOutlined,
ExperimentOutlined, ExperimentOutlined,
FileTextOutlined, FileTextOutlined,
KubernetesOutlined,
ProfileOutlined,
RetweetOutlined, RetweetOutlined,
ThunderboltOutlined ThunderboltOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
@@ -20,6 +22,8 @@ const icons = {
DownloadOutlined: React.createElement(DownloadOutlined), DownloadOutlined: React.createElement(DownloadOutlined),
FileTextOutlined: React.createElement(FileTextOutlined), FileTextOutlined: React.createElement(FileTextOutlined),
ApiOutlined: React.createElement(ApiOutlined), ApiOutlined: React.createElement(ApiOutlined),
KubernetesOutlined: React.createElement(KubernetesOutlined),
ProfileOutlined: React.createElement(ProfileOutlined),
Stop: React.createElement(IconFont, { type: 'icon-stop1' }), Stop: React.createElement(IconFont, { type: 'icon-stop1' }),
Play: React.createElement(IconFont, { type: 'icon-outline-play' }), Play: React.createElement(IconFont, { type: 'icon-outline-play' }),
Catalog: React.createElement(IconFont, { type: 'icon-catalog' }), Catalog: React.createElement(IconFont, { type: 'icon-catalog' }),
@@ -29,7 +33,9 @@ const icons = {
LocalPath: React.createElement(IconFont, { type: 'icon-hard-disk' }), LocalPath: React.createElement(IconFont, { type: 'icon-hard-disk' }),
Launch: React.createElement(IconFont, { type: 'icon-rocket-launch' }), Launch: React.createElement(IconFont, { type: 'icon-rocket-launch' }),
Deployment: React.createElement(IconFont, { type: 'icon-rocket-launch1' }), Deployment: React.createElement(IconFont, { type: 'icon-rocket-launch1' }),
Docker: React.createElement(IconFont, { type: 'icon-docker' }) Docker: React.createElement(IconFont, { type: 'icon-docker' }),
DigitalOcean: React.createElement(IconFont, { type: 'icon-digitalocean' }),
DetailInfo: React.createElement(IconFont, { type: 'icon-detail-info' })
}; };
export default icons; export default icons;
+1 -1
View File
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
// import './iconfont/iconfont.js'; // import './iconfont/iconfont.js';
const IconFont = createFromIconfontCN({ const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_4613488_arejw0wk0kd.js' scriptUrl: '//at.alicdn.com/t/c/font_4613488_wy0l8ooa03c.js'
}); });
export default IconFont; export default IconFont;
+6 -4
View File
@@ -56,10 +56,12 @@ const ScrollerModal = (props: ModalProps & { maxContentHeight?: number }) => {
body: { body: {
padding: '0' padding: '0'
}, },
footer: { footer: props.footer
padding: '12px 24px 24px', ? {
margin: '0' padding: '12px 24px 24px',
} margin: '0'
}
: {}
}} }}
> >
<Wrapper <Wrapper
+4 -1
View File
@@ -46,7 +46,10 @@ const SealSelect: React.FC<SelectProps & SealFormItemProps> = (props) => {
}, [options, intl]); }, [options, intl]);
useEffect(() => { useEffect(() => {
if (isNotEmptyValue(props.value) || (allowNull && props.value === null)) { if (
isNotEmptyValue(props.value) ||
(allowNull && (props.value === null || props.value === undefined))
) {
setIsFocus(true); setIsFocus(true);
} }
}, [props.value, allowNull]); }, [props.value, allowNull]);
+4 -4
View File
@@ -24,18 +24,18 @@ declare namespace Global {
avatar_url?: string; avatar_url?: string;
} }
interface BaseListItem<T> { type BaseListItem<T, U extends Record<string, any>> = {
key: string; key: string;
locale?: boolean; locale?: boolean;
value: T; value: T;
} } & U;
interface BaseOption<T> { type BaseOption<T, U extends Record<string, any>> = {
label: string; label: string;
locale?: boolean; locale?: boolean;
value: T; value: T;
meta?: Record<string, any>; meta?: Record<string, any>;
} } & U;
interface HintOptions { interface HintOptions {
label: string; label: string;
+4 -4
View File
@@ -8,13 +8,13 @@ import {
NodePoolListItem NodePoolListItem
} from '../config/types'; } from '../config/types';
export const CREDENTIALS_API = '/credentials'; export const CREDENTIALS_API = '/cloud-credentials';
export const CLUSTERS_API = '/clusters'; export const CLUSTERS_API = '/clusters';
export const WORKER_POOLS_API = '/worker-pools'; export const WORKER_POOLS_API = '/worker-pools';
export const CLUSTER_TOKEN = 'registration_token'; export const CLUSTER_TOKEN = 'registration-token';
// ===================== Credentials ===================== // ===================== Credentials =====================
@@ -89,8 +89,8 @@ export async function queryClusterDetail(id: number) {
}); });
} }
export async function queryClusterToken(id: number) { export async function queryClusterToken(params: { id: number }) {
return request(`${CLUSTERS_API}/${id}/${CLUSTER_TOKEN}`, { return request(`${CLUSTERS_API}/${params.id}/${CLUSTER_TOKEN}`, {
method: 'GET' method: 'GET'
}); });
} }
+93 -111
View File
@@ -1,5 +1,4 @@
import DeleteModal from '@/components/delete-modal'; import DeleteModal from '@/components/delete-modal';
import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools'; import { FilterBar } from '@/components/page-tools';
import CardList from '@/components/templates/card-list'; import CardList from '@/components/templates/card-list';
import CardSkeleton from '@/components/templates/card-skelton'; import CardSkeleton from '@/components/templates/card-skelton';
@@ -7,12 +6,6 @@ import { PageAction } from '@/config';
import type { PageActionType } from '@/config/types'; import type { PageActionType } from '@/config/types';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
import AddWorker from '@/pages/resources/components/add-worker'; import AddWorker from '@/pages/resources/components/add-worker';
import {
DeleteOutlined,
EditOutlined,
KubernetesOutlined,
ProfileOutlined
} from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Table, message } from 'antd'; import { Table, message } from 'antd';
@@ -21,76 +14,19 @@ import {
createCluster, createCluster,
deleteCluster, deleteCluster,
queryClusterList, queryClusterList,
queryClusterToken,
updateCluster updateCluster
} from './apis'; } from './apis';
import AddCluster from './components/add-cluster'; import AddCluster from './components/add-cluster';
import ClusterDetailModal from './components/cluster-detail-modal'; import ClusterDetailModal from './components/cluster-detail-modal';
import ClusterItem from './components/cluster-item'; import ClusterItem from './components/cluster-item';
import { ClusterDataList } from './config'; import { ProviderLabelMap, ProviderValueMap, addActions } from './config';
import { import {
ClusterFormData as FormData, ClusterFormData as FormData,
ClusterListItem as ListItem ClusterListItem as ListItem
} from './config/types'; } from './config/types';
const { Column } = Table; const { Column } = Table;
const addActions = [
{
label: 'Custom',
locale: false,
value: 'custom',
key: 'custom',
icon: <IconFont type="icon-docker" className="size-16" />
},
{
label: 'Kubernetes',
locale: false,
value: 'kubernetes',
key: 'kubernetes',
icon: <KubernetesOutlined className="size-16" />
},
{
label: 'Digital Ocean',
locale: false,
value: 'digitalocean',
key: 'digitalocean',
icon: <IconFont type="icon-digitalocean" />
}
];
const ActionList = [
{
key: 'edit',
label: 'common.button.edit',
icon: <EditOutlined></EditOutlined>
},
{
key: 'add',
label: 'Add Worker',
locale: false,
icon: <EditOutlined></EditOutlined>
},
{
key: 'terminal',
label: 'common.button.detail',
icon: <ProfileOutlined />
},
{
key: 'addPool',
label: 'Add Node Pool',
locale: false,
icon: <IconFont type="icon-catalog1" />
},
{
key: 'delete',
props: {
danger: true
},
label: 'common.button.delete',
icon: <DeleteOutlined></DeleteOutlined>
}
];
const Credentials: React.FC = () => { const Credentials: React.FC = () => {
const { const {
dataSource, dataSource,
@@ -121,15 +57,33 @@ const Credentials: React.FC = () => {
}); });
const [openAddWorker, setOpenAddWorker] = useState<{ const [openAddWorker, setOpenAddWorker] = useState<{
open: boolean; open: boolean;
token: string; registrationInfo: {
token: string;
image: string;
server_url: string;
};
}>({ }>({
open: false, open: false,
token: '' registrationInfo: {
token: '',
image: '',
server_url: ''
}
}); });
const [openAddModal, setOpenAddModal] = useState(false); const [openAddModal, setOpenAddModal] = useState<{
const [provider, setProvider] = useState<string>('custom'); open: boolean;
const [action, setAction] = useState<PageActionType>(PageAction.CREATE); action: PageActionType;
const [title, setTitle] = useState<string>(''); currentData?: ListItem;
title: string;
provider: string;
}>({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: '',
provider: ''
});
const [addPoolStatus, setAddPoolStatus] = useState<{ const [addPoolStatus, setAddPoolStatus] = useState<{
open: boolean; open: boolean;
action: PageActionType; action: PageActionType;
@@ -139,25 +93,19 @@ const Credentials: React.FC = () => {
open: false, open: false,
action: PageAction.CREATE, action: PageAction.CREATE,
title: '', title: '',
provider: 'digitalocean' provider: ProviderValueMap.DigitalOcean
}); });
const [currentData, setCurrentData] = useState<ListItem | undefined>(
undefined
);
const setActions = (row: ListItem) => {
if (row.provider !== 'custom') {
return ActionList.filter((item) => item.key !== 'add');
}
return ActionList;
};
const handleAddCluster = (value: string) => { const handleAddCluster = (value: string) => {
setOpenAddModal(true); const label = ProviderLabelMap[value];
setAction(PageAction.CREATE);
setProvider(value); setOpenAddModal({
const label = addActions.find((item) => item.value === value)?.label; open: true,
setTitle(`Add ${label} Cluster`); action: PageAction.CREATE,
currentData: undefined,
title: `Add ${label} Cluster`,
provider: value
});
}; };
const handleAddPool = (value: string) => { const handleAddPool = (value: string) => {
@@ -178,32 +126,65 @@ const Credentials: React.FC = () => {
...data ...data
}; };
try { try {
if (action === PageAction.EDIT) { if (openAddModal.action === PageAction.EDIT) {
await updateCluster({ await updateCluster({
data: params, data: params,
id: currentData!.id id: openAddModal.currentData!.id
}); });
} else { } else {
await createCluster({ data: params }); await createCluster({ data: params });
} }
fetchData(); fetchData();
setOpenAddModal(false); setOpenAddModal({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: '',
provider: ''
});
message.success(intl.formatMessage({ id: 'common.message.success' })); message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) { } catch (error) {
setOpenAddModal(false); setOpenAddModal({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: '',
provider: ''
});
} }
}; };
const handleModalCancel = () => { const handleModalCancel = () => {
console.log('handleModalCancel'); console.log('handleModalCancel');
setOpenAddModal(false); setOpenAddModal({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: '',
provider: ''
});
}; };
const handleEditCluster = (row: ListItem) => { const handleEditCluster = (row: ListItem) => {
setCurrentData(row); setOpenAddModal({
setOpenAddModal(true); open: true,
setAction(PageAction.EDIT); action: PageAction.EDIT,
setTitle(`Edit ${row.name} Cluster`); currentData: row,
title: `Edit ${row.name} Cluster`,
provider: row.provider
});
};
const handleAddWorker = async (row: ListItem) => {
try {
const data = await queryClusterToken({ id: row.id });
setOpenAddWorker({
open: true,
registrationInfo: data
});
} catch (error: any) {
message.error(error.message || 'Failed to fetch cluster token');
}
}; };
const handleSelect = (val: any, row: ListItem) => { const handleSelect = (val: any, row: ListItem) => {
@@ -212,11 +193,7 @@ const Credentials: React.FC = () => {
} else if (val === 'delete') { } else if (val === 'delete') {
handleDelete({ ...row, name: row.name }); handleDelete({ ...row, name: row.name });
} else if (val === 'add_worker') { } else if (val === 'add_worker') {
setOpenAddWorker({ handleAddWorker(row);
open: true,
token: '${token}'
});
setCurrentData(row);
} else if (val === 'addPool') { } else if (val === 'addPool') {
handleAddPool(row.provider); handleAddPool(row.provider);
} else if (val === 'details') { } else if (val === 'details') {
@@ -255,14 +232,14 @@ const Credentials: React.FC = () => {
marginTop={30} marginTop={30}
handleInputChange={handleNameChange} handleInputChange={handleNameChange}
handleSearch={handleSearch} handleSearch={handleSearch}
width={{ input: 200 }} width={{ input: 300 }}
buttonText="Add Cluster" buttonText="Add Cluster"
actionType="dropdown" actionType="dropdown"
actionItems={addActions} actionItems={addActions}
handleClickPrimary={handleClickDropdown} handleClickPrimary={handleClickDropdown}
></FilterBar> ></FilterBar>
<CardList <CardList
dataList={ClusterDataList} dataList={dataSource.dataList}
loading={dataSource.loading} loading={dataSource.loading}
activeId={-1} activeId={-1}
isFirst={!dataSource.loadend} isFirst={!dataSource.loadend}
@@ -273,18 +250,23 @@ const Credentials: React.FC = () => {
></CardList> ></CardList>
</PageContainer> </PageContainer>
<AddCluster <AddCluster
provider={provider} provider={openAddModal.provider}
open={openAddModal} open={openAddModal.open}
action={action} action={openAddModal.action}
title={title} title={openAddModal.title}
data={currentData} currentData={openAddModal.currentData}
onCancel={handleModalCancel} onCancel={handleModalCancel}
onOk={handleModalOk} onOk={handleModalOk}
></AddCluster> ></AddCluster>
<AddWorker <AddWorker
open={openAddWorker.open} open={openAddWorker.open}
onCancel={() => setOpenAddWorker({ open: false, token: '' })} onCancel={() =>
token={openAddWorker.token} setOpenAddWorker({
open: false,
registrationInfo: { token: '', image: '', server_url: '' }
})
}
registrationInfo={openAddWorker.registrationInfo}
></AddWorker> ></AddWorker>
<ClusterDetailModal <ClusterDetailModal
open={openClusterDetail.open} open={openClusterDetail.open}
@@ -19,6 +19,7 @@ type AddModalProps = {
title: string; title: string;
action: PageActionType; action: PageActionType;
open: boolean; open: boolean;
currentData?: ListItem; // Used when action is EDIT
provider: string; // 'kubernetes' | 'custom' | 'digitalocean'; provider: string; // 'kubernetes' | 'custom' | 'digitalocean';
onOk: (values: FormData) => void; onOk: (values: FormData) => void;
onCancel: () => void; onCancel: () => void;
@@ -28,6 +29,7 @@ const AddCluster: React.FC<AddModalProps> = ({
action, action,
open, open,
provider, provider,
currentData,
onOk, onOk,
onCancel onCancel
}) => { }) => {
@@ -36,12 +38,20 @@ const AddCluster: React.FC<AddModalProps> = ({
const [submissionStatus, setSubmissionStatus] = React.useState<{ const [submissionStatus, setSubmissionStatus] = React.useState<{
success: boolean; success: boolean;
data: ListItem; data: ListItem;
}>({ success: true, data: {} as ListItem }); }>({ success: false, data: {} as ListItem });
const handleSubmit = () => { const handleSubmit = () => {
form.submit(); form.submit();
}; };
const handleOk = async (data: FormData) => {
console.log('handleOk===', data);
onOk({
...data,
provider
});
};
const handleCancel = () => { const handleCancel = () => {
form.resetFields(); form.resetFields();
onCancel(); onCancel();
@@ -109,9 +119,14 @@ const AddCluster: React.FC<AddModalProps> = ({
{submissionStatus.success ? ( {submissionStatus.success ? (
renderAddWorkerContent() renderAddWorkerContent()
) : ( ) : (
<Form form={form} onFinish={onOk} preserve={false}> <Form
form={form}
onFinish={handleOk}
preserve={false}
initialValues={currentData}
>
<Form.Item<FormData> <Form.Item<FormData>
name="display_name" name="name"
rules={[ rules={[
{ {
required: true, required: true,
@@ -129,7 +144,7 @@ const AddCluster: React.FC<AddModalProps> = ({
required required
></SealInput.Input> ></SealInput.Input>
</Form.Item> </Form.Item>
{provider === 'digitalocean' && ( {provider === ProviderValueMap.DigitalOcean && (
<CloudProvider provider={provider}></CloudProvider> <CloudProvider provider={provider}></CloudProvider>
)} )}
<Form.Item<FormData> name="description" rules={[{ required: false }]}> <Form.Item<FormData> name="description" rules={[{ required: false }]}>
@@ -1,19 +1,23 @@
import ModalFooter from '@/components/modal-footer'; import ModalFooter from '@/components/modal-footer';
import ScrollerModal from '@/components/scroller-modal'; import ScrollerModal from '@/components/scroller-modal';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import { PageAction, PasswordReg } from '@/config'; import { PageAction } from '@/config';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button, Form } from 'antd'; import { Button, Form } from 'antd';
import React from 'react'; import React from 'react';
import { FormData, ListItem } from '../config/types'; import { ProviderValueMap } from '../config';
import {
CredentialFormData as FormData,
CredentialListItem as ListItem
} from '../config/types';
type AddModalProps = { type AddModalProps = {
title: string; title: string;
action: PageActionType; action: PageActionType;
open: boolean; open: boolean;
onOk: (values: FormData) => void; onOk: (values: FormData) => void;
data?: ListItem; currentData?: ListItem;
onCancel: () => void; onCancel: () => void;
provider: string; // 'kubernetes' | 'digitalocean'; provider: string; // 'kubernetes' | 'digitalocean';
}; };
@@ -22,7 +26,7 @@ const AddModal: React.FC<AddModalProps> = ({
action, action,
open, open,
onOk, onOk,
data, currentData,
provider, provider,
onCancel onCancel
}) => { }) => {
@@ -33,6 +37,10 @@ const AddModal: React.FC<AddModalProps> = ({
form.submit(); form.submit();
}; };
const handleOk = async (data: FormData) => {
onOk(data);
};
return ( return (
<ScrollerModal <ScrollerModal
title={title} title={title}
@@ -53,7 +61,12 @@ const AddModal: React.FC<AddModalProps> = ({
></ModalFooter> ></ModalFooter>
} }
> >
<Form form={form} onFinish={onOk} preserve={false}> <Form
form={form}
onFinish={handleOk}
preserve={false}
initialValues={currentData}
>
<Form.Item<FormData> <Form.Item<FormData>
name="name" name="name"
rules={[ rules={[
@@ -73,14 +86,13 @@ const AddModal: React.FC<AddModalProps> = ({
required required
></SealInput.Input> ></SealInput.Input>
</Form.Item> </Form.Item>
{provider === 'digital_ocean' && ( {provider === ProviderValueMap.DigitalOcean && (
<> <>
<Form.Item<FormData> <Form.Item<FormData>
name="access_key" name="key"
rules={[ rules={[
{ {
required: action === PageAction.CREATE, required: action === PageAction.CREATE,
pattern: PasswordReg,
message: intl.formatMessage({ message: intl.formatMessage({
id: 'users.form.rule.password' id: 'users.form.rule.password'
}) })
@@ -93,11 +105,10 @@ const AddModal: React.FC<AddModalProps> = ({
></SealInput.Password> ></SealInput.Password>
</Form.Item> </Form.Item>
<Form.Item<FormData> <Form.Item<FormData>
name="secret_key" name="secret"
rules={[ rules={[
{ {
required: action === PageAction.CREATE, required: action === PageAction.CREATE,
pattern: PasswordReg,
message: intl.formatMessage({ message: intl.formatMessage({
id: 'users.form.rule.password' id: 'users.form.rule.password'
}) })
@@ -105,17 +116,12 @@ const AddModal: React.FC<AddModalProps> = ({
]} ]}
> >
<SealInput.Password <SealInput.Password
label="Secret Key" label="Access Secret"
required={action === PageAction.CREATE} required={action === PageAction.CREATE}
></SealInput.Password> ></SealInput.Password>
</Form.Item> </Form.Item>
</> </>
)} )}
{provider === 'kubernetes' && (
<Form.Item<FormData> name="kubeconfig" rules={[{ required: false }]}>
<SealInput.TextArea label="Kubeconfig"></SealInput.TextArea>
</Form.Item>
)}
<Form.Item<FormData> name="description" rules={[{ required: false }]}> <Form.Item<FormData> name="description" rules={[{ required: false }]}>
<SealInput.TextArea <SealInput.TextArea
label={intl.formatMessage({ id: 'common.table.description' })} label={intl.formatMessage({ id: 'common.table.description' })}
@@ -30,9 +30,9 @@ const CloudProvider: React.FC<CloudProviderProps> = () => {
label="Credential" label="Credential"
required required
options={['credential1', 'credential2', 'credential3'].map( options={['credential1', 'credential2', 'credential3'].map(
(item) => ({ (item, i) => ({
label: item, label: item,
value: item value: i
}) })
)} )}
></SealSelect> ></SealSelect>
@@ -5,78 +5,22 @@ import StatusTag from '@/components/status-tag';
import ThemeTag from '@/components/tags-wrapper/theme-tag'; import ThemeTag from '@/components/tags-wrapper/theme-tag';
import Card from '@/components/templates/card'; import Card from '@/components/templates/card';
import { PageAction } from '@/config'; import { PageAction } from '@/config';
import {
DeleteOutlined,
EditOutlined,
KubernetesOutlined
} from '@ant-design/icons';
import { Card as ACard, Col, Collapse, Row } from 'antd'; import { Card as ACard, Col, Collapse, Row } from 'antd';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { queryClusterToken } from '../apis';
import { import {
ClusterStatus, ClusterStatus,
ClusterStatusLabelMap, ClusterStatusLabelMap,
ProviderLabelMap, ProviderLabelMap,
ProviderValueMap ProviderValueMap,
poolActionList
} from '../config'; } from '../config';
import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types'; import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types';
import AddPool from './add-pool'; import AddPool from './add-pool';
import RegisterCluster from './register-cluster'; import RegisterCluster from './register-cluster';
import WorkerPools from './worker-pools'; import WorkerPools from './worker-pools';
const actionItems = [
{
key: 'edit',
label: 'common.button.edit',
icon: <EditOutlined />
},
{
key: 'details',
label: 'common.button.view',
icon: <IconFont type="icon-detail-info" className="font-size-16" />
},
{
key: 'add_worker',
label: 'Add Worker',
provider: ProviderValueMap.Custom,
locale: false,
icon: <IconFont type="icon-docker" />
},
{
key: 'register_cluster',
label: 'Register Cluster',
provider: ProviderValueMap.Kubernetes,
locale: false,
icon: <KubernetesOutlined />
},
{
key: 'addPool',
label: 'Add Node Pool',
provider: ProviderValueMap.DigitalOcean,
locale: false,
icon: <IconFont type="icon-catalog1" />
},
{
key: 'delete',
label: 'common.button.delete',
icon: <DeleteOutlined />,
props: {
danger: true
}
}
];
const CollapseTitle = styled.div`
display: flex;
align-items: center;
gap: 8px;
font-size: var(--font-size-middle);
font-weight: 500;
color: var(--ant-color-text);
height: 32px;
cursor: pointer;
`;
const Content = styled.div` const Content = styled.div`
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -216,12 +160,39 @@ const CardItem: React.FC<CardProps> = (props) => {
open: false, open: false,
action: PageAction.CREATE, action: PageAction.CREATE,
title: '', title: '',
provider: 'digitalocean' provider: ProviderValueMap.DigitalOcean
}); });
const [registerClusterStatus, setRegisterClusterStatus] = React.useState({ const [registerClusterStatus, setRegisterClusterStatus] = React.useState<{
open: false open: boolean;
registrationInfo: {
token: string;
image: string;
server_url: string;
cluster_id: number;
};
}>({
open: false,
registrationInfo: {
token: '',
image: '',
server_url: '',
cluster_id: 0
}
}); });
const handleRegisterCluster = async () => {
try {
const info = await queryClusterToken({ id: data.id });
setRegisterClusterStatus({
open: true,
registrationInfo: {
...info,
cluster_id: data.id
}
});
} catch (error) {}
};
// cluster action handler // cluster action handler
const handleOnSelect = (key: string) => { const handleOnSelect = (key: string) => {
if (key === 'addPool') { if (key === 'addPool') {
@@ -235,9 +206,7 @@ const CardItem: React.FC<CardProps> = (props) => {
} }
if (key === 'register_cluster') { if (key === 'register_cluster') {
setRegisterClusterStatus({ handleRegisterCluster();
open: true
});
return; return;
} }
onSelect?.(key, data); onSelect?.(key, data);
@@ -256,7 +225,7 @@ const CardItem: React.FC<CardProps> = (props) => {
}; };
const actions = useMemo(() => { const actions = useMemo(() => {
return actionItems.filter((item) => { return poolActionList.filter((item) => {
if (item.provider) { if (item.provider) {
return item.provider === data.provider; return item.provider === data.provider;
} }
@@ -269,7 +238,7 @@ const CardItem: React.FC<CardProps> = (props) => {
height={'auto'} height={'auto'}
clickable={false} clickable={false}
ghost ghost
footer={ footerHolder={
<CollapseWrapper <CollapseWrapper
onChange={() => setShow(!show)} onChange={() => setShow(!show)}
expandIconPosition="end" expandIconPosition="end"
@@ -340,11 +309,13 @@ const CardItem: React.FC<CardProps> = (props) => {
<div className="title"> <div className="title">
<span className="flex-center gap-8"> <span className="flex-center gap-8">
<span className="text">{data.name}</span> <span className="text">{data.name}</span>
<ThemeTag>{ProviderLabelMap[data.provider]}</ThemeTag> <ThemeTag color="purple">
{ProviderLabelMap[data.provider]}
</ThemeTag>
<StatusTag <StatusTag
statusValue={{ statusValue={{
status: ClusterStatus[data.status], status: ClusterStatus[data.state],
text: ClusterStatusLabelMap[data.status] text: ClusterStatusLabelMap[data.state]
}} }}
/> />
</span> </span>
@@ -359,15 +330,17 @@ const CardItem: React.FC<CardProps> = (props) => {
<CardBox> <CardBox>
<CardWrapper bordered={false}> <CardWrapper bordered={false}>
<div className="label">Workers</div> <div className="label">Workers</div>
<div className="value">1/1</div> <div className="value">
{data.ready_workers} / {data.workers}
</div>
</CardWrapper> </CardWrapper>
<CardWrapper bordered={false}> <CardWrapper bordered={false}>
<div className="label">GPUs</div> <div className="label">GPUs</div>
<div className="value">12</div> <div className="value">{data.gpus}</div>
</CardWrapper> </CardWrapper>
<CardWrapper bordered={false}> <CardWrapper bordered={false}>
<div className="label">Deployments</div> <div className="label">Deployments</div>
<div className="value">2</div> <div className="value">{data.models}</div>
</CardWrapper> </CardWrapper>
</CardBox> </CardBox>
</Content> </Content>
@@ -397,10 +370,16 @@ const CardItem: React.FC<CardProps> = (props) => {
<RegisterCluster <RegisterCluster
title="Register Cluster" title="Register Cluster"
open={registerClusterStatus.open} open={registerClusterStatus.open}
data={data} registrationInfo={registerClusterStatus.registrationInfo}
onCancel={() => { onCancel={() => {
setRegisterClusterStatus({ setRegisterClusterStatus({
open: false open: false,
registrationInfo: {
token: '',
image: '',
server_url: '',
cluster_id: 0
}
}); });
}} }}
></RegisterCluster> ></RegisterCluster>
@@ -1,18 +1,22 @@
import ScrollerModal from '@/components/scroller-modal/index'; import ScrollerModal from '@/components/scroller-modal/index';
import React from 'react'; import React from 'react';
import { ClusterListItem as ListItem } from '../config/types';
import RegisterClusterInner from './resiter-cluster-inner'; import RegisterClusterInner from './resiter-cluster-inner';
type AddModalProps = { type AddModalProps = {
title: string; title: string;
open: boolean; open: boolean;
data: ListItem; registrationInfo: {
token: string;
image: string;
server_url: string;
cluster_id: number;
};
onCancel: () => void; onCancel: () => void;
}; };
const AddCluster: React.FC<AddModalProps> = ({ const AddCluster: React.FC<AddModalProps> = ({
title, title,
open, open,
data, registrationInfo,
onCancel onCancel
}) => { }) => {
const handleCancel = () => { const handleCancel = () => {
@@ -31,7 +35,7 @@ const AddCluster: React.FC<AddModalProps> = ({
width={600} width={600}
footer={false} footer={false}
> >
<RegisterClusterInner data={data} /> <RegisterClusterInner registrationInfo={registrationInfo} />
</ScrollerModal> </ScrollerModal>
); );
}; };
@@ -1,41 +1,39 @@
import HighlightCode from '@/components/highlight-code'; import HighlightCode from '@/components/highlight-code';
import React, { useEffect } from 'react'; import React, { useMemo } from 'react';
import { queryClusterToken } from '../apis'; import styled from 'styled-components';
import { generateRegisterCommand } from '../config'; import { generateRegisterCommand } from '../config';
import { ClusterListItem as ListItem } from '../config/types';
const Title = styled.h3`
font-weight: 600;
color: var(--ant-color-text);
margin-bottom: 12px;
margin-top: 12px;
font-size: var(--font-size-normal);
.ant-tag {
color: var(--ant-color-text-secondary);
font-weight: 400;
}
`;
type AddModalProps = { type AddModalProps = {
data: ListItem; registrationInfo: {
token: string;
image: string;
server_url: string;
cluster_id: number;
};
}; };
const AddCluster: React.FC<AddModalProps> = ({ data }) => { const AddCluster: React.FC<AddModalProps> = ({ registrationInfo }) => {
const [code, setCode] = React.useState<string>(''); const code = useMemo(() => {
const getToken = async () => { return generateRegisterCommand({
const res = await queryClusterToken(data?.id); server: registrationInfo?.server_url || window.location.origin,
return res.data?.token || ''; clusterId: registrationInfo?.cluster_id,
}; registrationToken: registrationInfo?.token
});
}, [registrationInfo]);
const getCode = async () => { const applyCommand = useMemo(() => {
try { return `kubectl apply -f manifest.yaml`;
const token = await getToken();
const command = generateRegisterCommand({
server: window.location.origin,
clusterId: data?.id || 0,
registrationToken: token
});
setCode(command);
} catch (error) {
setCode(
generateRegisterCommand({
server: window.location.origin,
clusterId: data?.id || 0,
registrationToken: '{token}'
})
);
}
};
useEffect(() => {
getCode();
}, []); }, []);
return ( return (
+79 -41
View File
@@ -1,57 +1,29 @@
import icons from '@/components/icon-font/icons';
import { StatusMaps } from '@/config'; import { StatusMaps } from '@/config';
import { StatusType } from '@/config/types'; import { StatusType } from '@/config/types';
export const ClusterDataList = [
{
id: 3,
name: 'Custom-cluster',
provider: 'custom',
clusterType: 'Custom',
workers: 4,
gpus: 8,
status: 'ready',
deployments: 3
},
{
id: 1,
name: 'kubernetes-cluster',
provider: 'kubernetes',
clusterType: 'Kubernetes',
workers: 2,
gpus: 4,
status: 'ready',
deployments: 1
},
{
id: 2,
name: 'Digital-Ocean-cluster',
provider: 'digitalocean',
workers: 3,
gpus: 6,
status: 'error',
deployments: 2
}
];
export const ClusterStatusValueMap = { export const ClusterStatusValueMap = {
Ready: 'ready', Provisioning: 0,
Error: 'error' Ready: 3,
ProvisionedNotReady: 1
}; };
export const ClusterStatusLabelMap = { export const ClusterStatusLabelMap = {
[ClusterStatusValueMap.Provisioning]: 'Provisioning',
[ClusterStatusValueMap.Ready]: 'Ready', [ClusterStatusValueMap.Ready]: 'Ready',
[ClusterStatusValueMap.Error]: 'Error' [ClusterStatusValueMap.ProvisionedNotReady]: 'Provisioned Not Ready'
}; };
export const ClusterStatus: Record<string, StatusType> = { export const ClusterStatus: Record<string, StatusType> = {
[ClusterStatusValueMap.Provisioning]: StatusMaps.transitioning,
[ClusterStatusValueMap.Ready]: StatusMaps.success, [ClusterStatusValueMap.Ready]: StatusMaps.success,
[ClusterStatusValueMap.Error]: StatusMaps.error [ClusterStatusValueMap.ProvisionedNotReady]: StatusMaps.error
}; };
export const ProviderValueMap = { export const ProviderValueMap = {
Kubernetes: 'kubernetes', Kubernetes: 'Kubernetes',
DigitalOcean: 'digitalocean', DigitalOcean: 'DigitalOcean',
Custom: 'custom' Custom: 'Custom'
}; };
export const ProviderLabelMap = { export const ProviderLabelMap = {
@@ -65,6 +37,72 @@ export const generateRegisterCommand = (params: {
clusterId: number; clusterId: number;
registrationToken: string; registrationToken: string;
}) => { }) => {
return `curl -k -L '${params.server}/v2/clusters/${params.clusterId}/manifests' \\ return `curl -k -L '${params.server}/v1/clusters/${params.clusterId}/manifests' \\
--header 'Authorization: Bearer ${params.registrationToken}'`; --header 'Authorization: Bearer ${params.registrationToken}' | kubectl apply -f -`;
}; };
export const addActions = [
{
label: 'Custom',
locale: false,
value: ProviderValueMap.Custom,
key: ProviderValueMap.Custom,
icon: icons.Docker
},
{
label: 'Kubernetes',
locale: false,
value: ProviderValueMap.Kubernetes,
key: ProviderValueMap.Kubernetes,
icon: icons.KubernetesOutlined
},
{
label: 'Digital Ocean',
locale: false,
value: ProviderValueMap.DigitalOcean,
key: ProviderValueMap.DigitalOcean,
icon: icons.DigitalOcean
}
];
export const poolActionList = [
{
key: 'edit',
label: 'common.button.edit',
icon: icons.EditOutlined
},
{
key: 'details',
label: 'common.button.view',
icon: icons.DetailInfo
},
{
key: 'add_worker',
label: 'Add Worker',
provider: ProviderValueMap.Custom,
locale: false,
icon: icons.Docker
},
{
key: 'register_cluster',
label: 'Register Cluster',
provider: ProviderValueMap.Kubernetes,
locale: false,
icon: icons.KubernetesOutlined
},
{
key: 'addPool',
label: 'Add Node Pool',
provider: ProviderValueMap.DigitalOcean,
locale: false,
icon: icons.Catalog
},
{
key: 'delete',
label: 'common.button.delete',
icon: icons.DeleteOutlined,
props: {
danger: true
}
}
];
+9 -5
View File
@@ -1,12 +1,14 @@
export interface CredentialFormData { export interface CredentialFormData {
name: string; name: string;
provider: string; provider: string;
access_key: string; key: string;
secret_key: string; secret: string;
description?: string; description?: string;
id?: number; id?: number;
} }
export type ClusterStatusType = 0 | 1 | 3;
export interface CredentialListItem { export interface CredentialListItem {
id: number; id: number;
name: string; name: string;
@@ -19,7 +21,7 @@ export interface CredentialListItem {
} }
export interface ClusterFormData { export interface ClusterFormData {
display_name: string; name: string;
description: string; description: string;
provider: string; provider: string;
credential_id: number; credential_id: number;
@@ -55,9 +57,11 @@ export interface ClusterListItem {
zone: string; zone: string;
region: string; region: string;
gpus: number; gpus: number;
deployments: number; models: number;
workers: number;
ready_workers: number;
id: number; id: number;
status: string; state: ClusterStatusType;
state_message: string; state_message: string;
worker_pools: NodePoolListItem[]; worker_pools: NodePoolListItem[];
} }
+28 -85
View File
@@ -1,30 +1,19 @@
import AutoTooltip from '@/components/auto-tooltip'; import AutoTooltip from '@/components/auto-tooltip';
import DeleteModal from '@/components/delete-modal'; import DeleteModal from '@/components/delete-modal';
import DropDownActions from '@/components/drop-down-actions';
import DropdownButtons from '@/components/drop-down-buttons'; import DropdownButtons from '@/components/drop-down-buttons';
import IconFont from '@/components/icon-font'; import IconFont from '@/components/icon-font';
import PageTools from '@/components/page-tools'; import { FilterBar } from '@/components/page-tools';
import { PageAction } from '@/config'; import { PageAction } from '@/config';
import type { PageActionType } from '@/config/types'; import type { PageActionType } from '@/config/types';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
import { import {
DeleteOutlined, DeleteOutlined,
DownOutlined,
EditOutlined, EditOutlined,
KubernetesOutlined, KubernetesOutlined
SyncOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { import { ConfigProvider, Empty, Table, message } from 'antd';
Button,
ConfigProvider,
Empty,
Input,
Space,
Table,
message
} from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useState } from 'react'; import { useState } from 'react';
import { import {
@@ -34,6 +23,7 @@ import {
updateCredential updateCredential
} from './apis'; } from './apis';
import AddModal from './components/add-credential'; import AddModal from './components/add-credential';
import { ProviderValueMap } from './config';
import { import {
CredentialFormData as FormData, CredentialFormData as FormData,
CredentialListItem as ListItem CredentialListItem as ListItem
@@ -99,26 +89,22 @@ const Credentials: React.FC = () => {
open: boolean; open: boolean;
action: PageActionType; action: PageActionType;
title: string; title: string;
currentData: ListItem | undefined;
}>({ }>({
provider: '', provider: '',
open: false, open: false,
action: PageAction.CREATE, action: PageAction.CREATE,
title: '' title: '',
currentData: undefined
}); });
const [currentData, setCurrentData] = useState<ListItem | undefined>(
undefined
);
const handleAddCredential = (value: string) => { const handleAddCredential = () => {
const title =
value === 'digital_ocean'
? 'Add Digital Ocean Credential'
: 'Add Kubernetes Credential';
setOpenModalStatus({ setOpenModalStatus({
provider: value, provider: ProviderValueMap.DigitalOcean,
open: true, open: true,
action: PageAction.CREATE, action: PageAction.CREATE,
title: title title: 'Add Cloud Credential',
currentData: undefined
}); });
}; };
@@ -132,7 +118,7 @@ const Credentials: React.FC = () => {
data: { data: {
...params ...params
}, },
id: currentData!.id id: openModalStatus.currentData!.id
}); });
} else { } else {
await createCredential({ data: params }); await createCredential({ data: params });
@@ -151,12 +137,12 @@ const Credentials: React.FC = () => {
}; };
const handleEditUser = (row: ListItem) => { const handleEditUser = (row: ListItem) => {
setCurrentData(row);
setOpenModalStatus({ setOpenModalStatus({
provider: row.provider, provider: row.provider,
open: true, open: true,
action: PageAction.EDIT, action: PageAction.EDIT,
title: `Edit ${row.provider} Credential` title: `Edit ${row.name} Credential`,
currentData: row
}); });
}; };
@@ -180,11 +166,6 @@ const Credentials: React.FC = () => {
return <div></div>; return <div></div>;
}; };
const handleClickDropdown = (e: any) => {
const value = e.key;
handleAddCredential(value);
};
return ( return (
<> <>
<PageContainer <PageContainer
@@ -200,58 +181,20 @@ const Credentials: React.FC = () => {
}} }}
extra={[]} extra={[]}
> >
<PageTools <FilterBar
showSelect={false}
showPrimaryButton={true}
marginBottom={22} marginBottom={22}
left={ marginTop={30}
<Space> buttonText={'Add Cloud Credential'}
<Input handleDeleteByBatch={handleDeleteBatch}
placeholder={intl.formatMessage({ id: 'common.filter.name' })} handleSearch={handleSearch}
style={{ width: 300 }} handleInputChange={handleNameChange}
allowClear handleClickPrimary={handleAddCredential}
onChange={handleNameChange} rowSelection={rowSelection}
></Input> width={{ input: 300 }}
<Button ></FilterBar>
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
onClick={handleSearch}
icon={<SyncOutlined></SyncOutlined>}
></Button>
</Space>
}
right={
<Space size={20}>
<DropDownActions
menu={{
items: addActions,
onClick: handleClickDropdown
}}
trigger={['hover']}
placement="bottomRight"
>
<Button
icon={<DownOutlined></DownOutlined>}
type="primary"
iconPosition="end"
>
Add Credential
</Button>
</DropDownActions>
<Button
icon={<DeleteOutlined />}
danger
onClick={handleDeleteBatch}
disabled={!rowSelection.selectedRowKeys.length}
>
<span>
{intl?.formatMessage?.({ id: 'common.button.delete' })}
{rowSelection.selectedRowKeys.length > 0 && (
<span>({rowSelection.selectedRowKeys?.length})</span>
)}
</span>
</Button>
</Space>
}
></PageTools>
<ConfigProvider renderEmpty={renderEmpty}> <ConfigProvider renderEmpty={renderEmpty}>
<Table <Table
dataSource={dataSource.dataList} dataSource={dataSource.dataList}
@@ -355,7 +298,7 @@ const Credentials: React.FC = () => {
open={openModalStatus.open} open={openModalStatus.open}
action={openModalStatus.action} action={openModalStatus.action}
title={openModalStatus.title} title={openModalStatus.title}
data={currentData} currentData={openModalStatus.currentData}
onCancel={handleModalCancel} onCancel={handleModalCancel}
onOk={handleModalOk} onOk={handleModalOk}
></AddModal> ></AddModal>
+5 -3
View File
@@ -52,9 +52,11 @@ export async function queryModelsList(
); );
} }
export async function queryGPUList(params?: Global.SearchParams) { export async function queryGPUList<T extends Record<string, any>>(
params?: Global.SearchParams & T
) {
return request<Global.PageResponse<GPUListItem>>(`/gpu-devices`, { return request<Global.PageResponse<GPUListItem>>(`/gpu-devices`, {
methos: 'GET', method: 'GET',
params params
}); });
} }
@@ -172,7 +174,7 @@ export async function queryModelScopeModels(
config?: any config?: any
) { ) {
const tagsCriterion = params.tags?.map((tag: string) => { const tagsCriterion = params.tags?.map((tag: string) => {
return { category: 'libraries', predicate: 'contains', values: [tag] }; return { category: 'tags', predicate: 'contains', values: [tag] };
}); });
const tasksCriterion = params.tasks?.map((task: string) => { const tasksCriterion = params.tasks?.map((task: string) => {
return { category: 'tasks', predicate: 'contains', values: [task] }; return { category: 'tasks', predicate: 'contains', values: [task] };
@@ -30,7 +30,6 @@ import vllmConfig from '../config/vllm-config';
import dataformStyles from '../style/data-form.less'; import dataformStyles from '../style/data-form.less';
import GPUCard from './gpu-card'; import GPUCard from './gpu-card';
import Performance from './performance'; import Performance from './performance';
import Scaling from './scaling';
interface AdvanceConfigProps { interface AdvanceConfigProps {
isGGUF: boolean; isGGUF: boolean;
@@ -460,19 +459,19 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
forceRender: true, forceRender: true,
children: <Performance></Performance> children: <Performance></Performance>
}, },
{ // {
key: '3', // key: '3',
label: ( // label: (
<span // <span
style={{ fontWeight: 'var(--font-weight-medium)' }} // style={{ fontWeight: 'var(--font-weight-medium)' }}
className="font-size-14" // className="font-size-14"
> // >
Scaling // Scaling
</span> // </span>
), // ),
forceRender: true, // forceRender: true,
children: <Scaling></Scaling> // children: <Scaling></Scaling>
}, // },
{ {
key: '1', key: '1',
label: ( label: (
+106 -90
View File
@@ -6,17 +6,13 @@ import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { forwardRef, useImperativeHandle } from 'react'; import React, { forwardRef, useImperativeHandle } from 'react';
import { import { backendOptionsMap, excludeFields, sourceOptions } from '../config';
backendOptionsMap,
excludeFields,
modelSourceMap,
sourceOptions
} from '../config';
import { FormInnerContext } from '../config/form-context'; import { FormInnerContext } from '../config/form-context';
import { FormData, SourceType } from '../config/types'; import { FormData, SourceType } from '../config/types';
import CatalogFrom from '../forms/catalog'; import CatalogFrom from '../forms/catalog';
import HuggingFaceForm from '../forms/hugging-face'; import HuggingFaceForm from '../forms/hugging-face';
import LocalPathForm from '../forms/local-path'; import LocalPathForm from '../forms/local-path';
import { useGenerateGPUOptions } from '../hooks/use-form-initial-values';
import AdvanceConfig from './advance-config'; import AdvanceConfig from './advance-config';
interface DataFormProps { interface DataFormProps {
@@ -29,8 +25,7 @@ interface DataFormProps {
sourceDisable?: boolean; sourceDisable?: boolean;
backendOptions?: Global.BaseOption<string>[]; backendOptions?: Global.BaseOption<string>[];
sourceList?: Global.BaseOption<string>[]; sourceList?: Global.BaseOption<string>[];
gpuOptions: any[]; clusterList: Global.BaseOption<number>[];
modelFileOptions?: any[];
fields?: string[]; fields?: string[];
onValuesChange?: (changedValues: any, allValues: any) => void; onValuesChange?: (changedValues: any, allValues: any) => void;
onSourceChange?: (value: string) => void; onSourceChange?: (value: string) => void;
@@ -38,11 +33,6 @@ interface DataFormProps {
onBackendChange?: (value: string) => void; onBackendChange?: (value: string) => void;
} }
const SEARCH_SOURCE = [
modelSourceMap.huggingface_value,
modelSourceMap.modelscope_value
];
const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => { const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
const { const {
action, action,
@@ -51,13 +41,13 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
sourceDisable = true, sourceDisable = true,
backendOptions, backendOptions,
sourceList, sourceList,
gpuOptions = [], clusterList = [],
modelFileOptions = [],
fields = ['source'], fields = ['source'],
onSourceChange, onSourceChange,
onValuesChange, onValuesChange,
onOk onOk
} = props; } = props;
const { getGPUOptionList, gpuOptions } = useGenerateGPUOptions();
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
const [form] = Form.useForm(); const [form] = Form.useForm();
const intl = useIntl(); const intl = useIntl();
@@ -143,6 +133,10 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
onSourceChange?.(val); onSourceChange?.(val);
}; };
const handleClusterChange = (value: number) => {
getGPUOptionList({ clusterId: value });
};
const handleOnValuesChange = async (changedValues: any, allValues: any) => { const handleOnValuesChange = async (changedValues: any, allValues: any) => {
const fieldName = Object.keys(changedValues)[0]; const fieldName = Object.keys(changedValues)[0];
@@ -172,6 +166,9 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
}, },
getFieldsValue: () => { getFieldsValue: () => {
return form.getFieldsValue(); return form.getFieldsValue();
},
getGPUOptionList(params: { clusterId: number }) {
getGPUOptionList(params);
} }
}; };
}, },
@@ -179,78 +176,96 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
); );
return ( return (
<Form <FormInnerContext.Provider
name="deployModel" value={{
form={form} onBackendChange: handleBackendChange,
onFinish={handleOk} onValuesChange: onValuesChange,
preserve={false} gpuOptions: gpuOptions
style={{ padding: '16px 24px' }}
clearOnDestroy={true}
onValuesChange={handleOnValuesChange}
scrollToFirstError={true}
initialValues={{
replicas: 1,
source: props.source,
placement_strategy: 'spread',
cpu_offloading: true,
scheduleType: 'auto',
categories: null,
restart_on_error: true,
distributed_inference_across_workers: true,
...initialValues
}} }}
> >
<Form.Item<FormData> <Form
name="name" name="deployModel"
rules={[ form={form}
{ onFinish={handleOk}
required: true, preserve={false}
message: getRuleMessage('input', 'common.table.name') style={{ padding: '16px 24px' }}
} clearOnDestroy={true}
]} onValuesChange={handleOnValuesChange}
scrollToFirstError={true}
initialValues={{
replicas: 1,
source: props.source,
placement_strategy: 'spread',
cpu_offloading: true,
scheduleType: 'auto',
categories: null,
restart_on_error: true,
distributed_inference_across_workers: true,
...initialValues
}}
> >
<SealInput.Input
label={intl.formatMessage({
id: 'common.table.name'
})}
required
></SealInput.Input>
</Form.Item>
{fields.includes('source') && (
<Form.Item<FormData> <Form.Item<FormData>
name="source" name="name"
rules={[ rules={[
{ {
required: true, required: true,
message: getRuleMessage('select', 'models.form.source') message: getRuleMessage('input', 'common.table.name')
}
]}
>
<SealInput.Input
label={intl.formatMessage({
id: 'common.table.name'
})}
required
></SealInput.Input>
</Form.Item>
{fields.includes('source') && (
<Form.Item<FormData>
name="source"
rules={[
{
required: true,
message: getRuleMessage('select', 'models.form.source')
}
]}
>
{
<SealSelect
onChange={handleOnSourceChange}
disabled={sourceDisable}
label={intl.formatMessage({
id: 'models.form.source'
})}
options={sourceList ?? sourceOptions}
required
></SealSelect>
}
</Form.Item>
)}
<HuggingFaceForm></HuggingFaceForm>
<LocalPathForm></LocalPathForm>
<Form.Item<FormData>
name="cluster_id"
rules={[
{
required: true,
message: getRuleMessage('select', 'Cluster', false)
} }
]} ]}
> >
{ {
<SealSelect <SealSelect
onChange={handleOnSourceChange} onChange={handleClusterChange}
disabled={sourceDisable} label="Cluster"
label={intl.formatMessage({ options={clusterList}
id: 'models.form.source'
})}
options={sourceList ?? sourceOptions}
required required
></SealSelect> ></SealSelect>
} }
</Form.Item> </Form.Item>
)} {/* <Form.Item name="backend" rules={[{ required: true }]}>
<FormInnerContext.Provider
value={{
onBackendChange: handleBackendChange,
onValuesChange: onValuesChange,
gpuOptions: gpuOptions
}}
>
<HuggingFaceForm></HuggingFaceForm>
<LocalPathForm></LocalPathForm>
</FormInnerContext.Provider>
{/* <Form.Item name="backend" rules={[{ required: true }]}>
<SealSelect <SealSelect
required required
onChange={handleBackendChange} onChange={handleBackendChange}
@@ -291,25 +306,26 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
} }
></SealSelect> ></SealSelect>
</Form.Item> */} </Form.Item> */}
<CatalogFrom></CatalogFrom> <CatalogFrom></CatalogFrom>
<Form.Item<FormData> name="description"> <Form.Item<FormData> name="description">
<SealInput.TextArea <SealInput.TextArea
scaleSize={true} scaleSize={true}
label={intl.formatMessage({ label={intl.formatMessage({
id: 'common.table.description' id: 'common.table.description'
})} })}
></SealInput.TextArea> ></SealInput.TextArea>
</Form.Item> </Form.Item>
<AdvanceConfig <AdvanceConfig
form={form} form={form}
gpuOptions={gpuOptions} gpuOptions={gpuOptions}
isGGUF={isGGUF} isGGUF={isGGUF}
action={action} action={action}
source={props.source} source={props.source}
backendOptions={backendOptions} backendOptions={backendOptions}
handleBackendChange={handleBackendChange} handleBackendChange={handleBackendChange}
></AdvanceConfig> ></AdvanceConfig>
</Form> </Form>
</FormInnerContext.Provider>
); );
}); });
@@ -2,6 +2,7 @@ import ModalFooter from '@/components/modal-footer';
import GSDrawer from '@/components/scroller-modal/gs-drawer'; import GSDrawer from '@/components/scroller-modal/gs-drawer';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import { createAxiosToken } from '@/hooks/use-chunk-request'; import { createAxiosToken } from '@/hooks/use-chunk-request';
import { ProviderValueMap } from '@/pages/cluster-management/config';
import { CloseOutlined } from '@ant-design/icons'; import { CloseOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button } from 'antd'; import { Button } from 'antd';
@@ -17,11 +18,8 @@ import {
} from '../config'; } from '../config';
import { FormContext } from '../config/form-context'; import { FormContext } from '../config/form-context';
import { CatalogSpec, FormData, ListItem, SourceType } from '../config/types'; import { CatalogSpec, FormData, ListItem, SourceType } from '../config/types';
import { import { useCheckCompatibility } from '../hooks';
checkOnlyAscendNPU, import useFormInitialValues from '../hooks/use-form-initial-values';
useCheckCompatibility,
useGenerateFormEditInitialValues
} from '../hooks';
import ColumnWrapper from './column-wrapper'; import ColumnWrapper from './column-wrapper';
import CompatibilityAlert from './compatible-alert'; import CompatibilityAlert from './compatible-alert';
import DataForm from './data-form'; import DataForm from './data-form';
@@ -104,10 +102,9 @@ const AddModal: React.FC<AddModalProps> = (props) => {
handleOnValuesChange, handleOnValuesChange,
warningStatus warningStatus
} = useCheckCompatibility(); } = useCheckCompatibility();
const { getClusterList, clusterList } = useFormInitialValues();
const intl = useIntl(); const intl = useIntl();
const { getGPUList } = useGenerateFormEditInitialValues();
const form = useRef<any>({}); const form = useRef<any>({});
const [gpuOptions, setGpuOptions] = useState<any[]>([]);
const [isGGUF, setIsGGUF] = useState<boolean>(false); const [isGGUF, setIsGGUF] = useState<boolean>(false);
const [sourceList, setSourceList] = useState<any[]>([]); const [sourceList, setSourceList] = useState<any[]>([]);
const [backendList, setBackendList] = useState<any[]>([]); const [backendList, setBackendList] = useState<any[]>([]);
@@ -161,15 +158,6 @@ const AddModal: React.FC<AddModalProps> = (props) => {
return EmbeddingRerankFirstQuant.includes(_.toUpper(data.quantOption)); return EmbeddingRerankFirstQuant.includes(_.toUpper(data.quantOption));
} }
if (
data.backend === backendOptionsMap.llamaBox &&
checkOnlyAscendNPU(gpuOptions)
) {
return hasF16Ref.current
? AscendNPUQuant_F16.includes(_.toUpper(data.quantOption))
: AscendNPUQuant_Q8.includes(_.toUpper(data.quantOption));
}
return defaultQuant.includes(_.toUpper(data.quantOption)); return defaultQuant.includes(_.toUpper(data.quantOption));
}; };
@@ -360,6 +348,15 @@ const AddModal: React.FC<AddModalProps> = (props) => {
handleCheckFormData(); handleCheckFormData();
}; };
const initClusterId = () => {
const cluster_id =
clusterList?.find((item) => item.provider === ProviderValueMap.Custom)
?.value || clusterList?.[0]?.value;
console.log('cluster_id:', cluster_id);
return cluster_id;
};
const fetchSpecData = async () => { const fetchSpecData = async () => {
try { try {
axiosToken.current?.cancel?.(); axiosToken.current?.cancel?.();
@@ -407,7 +404,10 @@ const AddModal: React.FC<AddModalProps> = (props) => {
size: defaultSpec.size, size: defaultSpec.size,
backend: defaultSpec.backend backend: defaultSpec.backend
}); });
initFormDataBySource(defaultSpec); initFormDataBySource({
...defaultSpec,
cluster_id: initClusterId()
});
const name = _.toLower(current.name).replace(/\s/g, '-') || ''; const name = _.toLower(current.name).replace(/\s/g, '-') || '';
form.current.setFieldValue('name', name); form.current.setFieldValue('name', name);
@@ -420,6 +420,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
const allValues = generateSubmitData({ const allValues = generateSubmitData({
...defaultSpec, ...defaultSpec,
categories: _.get(current, 'categories.0', null), categories: _.get(current, 'categories.0', null),
cluster_id: initClusterId(),
name name
}); });
handleCheckCompatibility(allValues); handleCheckCompatibility(allValues);
@@ -484,7 +485,9 @@ const AddModal: React.FC<AddModalProps> = (props) => {
useEffect(() => { useEffect(() => {
if (open) { if (open) {
fetchSpecData(); setTimeout(() => {
fetchSpecData();
}, 100);
} }
return () => { return () => {
axiosToken.current?.cancel?.(); axiosToken.current?.cancel?.();
@@ -498,9 +501,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
}, [open, current]); }, [open, current]);
useEffect(() => { useEffect(() => {
getGPUList().then((data) => { getClusterList();
setGpuOptions(data);
});
}, []); }, []);
return ( return (
@@ -608,7 +609,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
sourceDisable={false} sourceDisable={false}
backendOptions={backendList} backendOptions={backendList}
sourceList={sourceList} sourceList={sourceList}
gpuOptions={gpuOptions} clusterList={clusterList}
onBackendChange={handleBackendChange} onBackendChange={handleBackendChange}
onSourceChange={handleSourceChange} onSourceChange={handleSourceChange}
onValuesChange={onValuesChange} onValuesChange={onValuesChange}
+33 -89
View File
@@ -1,8 +1,8 @@
import { getRequestId } from '@/atoms/models';
import ModalFooter from '@/components/modal-footer'; import ModalFooter from '@/components/modal-footer';
import GSDrawer from '@/components/scroller-modal/gs-drawer'; import GSDrawer from '@/components/scroller-modal/gs-drawer';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import useDeferredRequest from '@/hooks/use-deferred-request'; import useDeferredRequest from '@/hooks/use-deferred-request';
import { ProviderValueMap } from '@/pages/cluster-management/config';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button } from 'antd'; import { Button } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
@@ -68,10 +68,12 @@ type AddModalProps = {
source: SourceType; source: SourceType;
isGGUF?: boolean; isGGUF?: boolean;
width?: string | number; width?: string | number;
gpuOptions: any[];
modelFileOptions: any[];
initialValues?: any; initialValues?: any;
deploymentType?: 'modelList' | 'modelFiles'; deploymentType?: 'modelList' | 'modelFiles';
clusterList: Global.BaseOption<
number,
{ provider: string; state: string | number }
>[];
onOk: (values: FormData) => void; onOk: (values: FormData) => void;
onCancel: () => void; onCancel: () => void;
}; };
@@ -95,7 +97,8 @@ const AddModal: FC<AddModalProps> = (props) => {
action, action,
width = 600, width = 600,
deploymentType = 'modelList', deploymentType = 'modelList',
initialValues initialValues,
clusterList
} = props || {}; } = props || {};
const SEARCH_SOURCE = [ const SEARCH_SOURCE = [
modelSourceMap.huggingface_value, modelSourceMap.huggingface_value,
@@ -113,7 +116,7 @@ const AddModal: FC<AddModalProps> = (props) => {
warningStatus, warningStatus,
submitAnyway submitAnyway
} = useCheckCompatibility(); } = useCheckCompatibility();
const { onSelectModel } = useSelectModel({ gpuOptions: props.gpuOptions }); const { onSelectModel } = useSelectModel({ gpuOptions: [] });
const form = useRef<any>({}); const form = useRef<any>({});
const intl = useIntl(); const intl = useIntl();
const [selectedModel, setSelectedModel] = useState<any>({}); const [selectedModel, setSelectedModel] = useState<any>({});
@@ -161,15 +164,6 @@ const AddModal: FC<AddModalProps> = (props) => {
evaluateStateRef.current = state; evaluateStateRef.current = state;
}; };
const updateEvaluateState = (state: EvaluateProccessType) => {
const currentRequestModelId = evaluateStateRef.current.requestModelId;
setEvaluteState({
...evaluateStateRef.current,
state
});
return currentRequestModelId;
};
const handleOnValuesChange = (data: { const handleOnValuesChange = (data: {
changedValues: any; changedValues: any;
allValues: any; allValues: any;
@@ -199,71 +193,6 @@ const AddModal: FC<AddModalProps> = (props) => {
return categories || null; return categories || null;
}; };
const { run: onSelectFile } = useDeferredRequest(
async (item: any, modelInfo: any, manual?: boolean) => {
unlockWarningStatus();
const evaluateRes = await handleOnValuesChangeBefore?.({
changedValues: {},
allValues: form.current?.form?.getFieldsValue?.(),
source: props.source
});
console.log('onSelectFile:', item, modelInfo, evaluateRes);
// for cancel evaluate request case
if (!evaluateRes) {
return;
}
const defaultSpec = getDefaultSpec({
evaluateResult: evaluateRes
});
/**
* do not reset backend_parameters when select a model file
*/
const formValues = form.current?.getFieldsValue?.(pickFieldsFromSpec);
form.current?.setFieldsValue?.({
..._.omit(modelInfo, ['name']),
file_name: item.fakeName,
backend_parameters:
formValues.backend_parameters?.length > 0
? formValues.backend_parameters
: defaultSpec.backend_parameters || [],
backend_version:
formValues.backend_version || defaultSpec.backend_version,
env: formValues.env || defaultSpec.env,
categories: getCategory(item)
});
},
100
);
const handleSelectModelFile = async (
item: any,
options: { requestModelId: number; manual?: boolean }
) => {
const { requestModelId, manual } = options || {};
if (requestModelId !== getRequestId()) {
return;
}
console.log('handleSelectModelFile:', item, selectedModel);
const modelInfo = onSelectModel(selectedModel, props.source);
form.current?.setFieldsValue?.({
..._.omit(modelInfo, ['name']),
file_name: item.fakeName,
categories: getCategory(item)
});
// evaluate the form data when select a model file
if (item.fakeName) {
onSelectFile(item, modelInfo, manual);
}
};
const handleCancelFiles = () => { const handleCancelFiles = () => {
cancelEvaluate(); cancelEvaluate();
modelFileRef.current?.cancelRequest(); modelFileRef.current?.cancelRequest();
@@ -441,22 +370,37 @@ const AddModal: FC<AddModalProps> = (props) => {
onCancel?.(); onCancel?.();
}, [onCancel]); }, [onCancel]);
const initClusterId = () => {
const cluster_id =
clusterList?.find((item) => item.provider === ProviderValueMap.Custom)
?.value || clusterList?.[0]?.value;
return cluster_id;
};
const handleOnOpen = () => { const handleOnOpen = () => {
if (props.deploymentType === 'modelFiles') { if (props.deploymentType === 'modelFiles') {
form.current?.form?.setFieldsValue({ form.current?.form?.setFieldsValue({
...props.initialValues ...props.initialValues,
cluster_id: initClusterId()
}); });
handleOnValuesChange?.({ handleOnValuesChange?.({
changedValues: {}, changedValues: {},
allValues: props.initialValues, allValues: {
...props.initialValues,
cluster_id: initClusterId()
},
source: source source: source
}); });
} else { } else {
let backend = checkOnlyAscendNPU(props.gpuOptions) let backend = checkOnlyAscendNPU([])
? backendOptionsMap.ascendMindie ? backendOptionsMap.ascendMindie
: backendOptionsMap.vllm; : backendOptionsMap.vllm;
form.current?.setFieldValue?.('backend', backend); form.current?.setFieldsValue?.({
backend,
cluster_id: initClusterId()
});
} }
}; };
@@ -483,6 +427,9 @@ const AddModal: FC<AddModalProps> = (props) => {
useEffect(() => { useEffect(() => {
if (open) { if (open) {
handleOnOpen(); handleOnOpen();
form.current?.getGPUOptionList?.({
clusterId: initClusterId()
});
} else { } else {
cancelEvaluate(); cancelEvaluate();
clearCahceFormValues(); clearCahceFormValues();
@@ -495,7 +442,7 @@ const AddModal: FC<AddModalProps> = (props) => {
message: [] message: []
}); });
}; };
}, [open, props.gpuOptions.length]); }, [open, clusterList]);
return ( return (
<GSDrawer <GSDrawer
@@ -534,7 +481,7 @@ const AddModal: FC<AddModalProps> = (props) => {
handleOnSelectModelAfterEvaluate handleOnSelectModelAfterEvaluate
} }
displayEvaluateStatus={displayEvaluateStatus} displayEvaluateStatus={displayEvaluateStatus}
gpuOptions={props.gpuOptions} gpuOptions={[]}
></SearchModel> ></SearchModel>
</ColumnWrapper> </ColumnWrapper>
<Separator></Separator> <Separator></Separator>
@@ -559,8 +506,6 @@ const AddModal: FC<AddModalProps> = (props) => {
value={{ value={{
isGGUF: isGGUF, isGGUF: isGGUF,
pageAction: action, pageAction: action,
modelFileOptions: props.modelFileOptions,
gpuOptions: props.gpuOptions,
onValuesChange: onValuesChange onValuesChange: onValuesChange
}} }}
> >
@@ -609,12 +554,11 @@ const AddModal: FC<AddModalProps> = (props) => {
initialValues={initialValues} initialValues={initialValues}
source={source} source={source}
action={action} action={action}
clusterList={clusterList}
selectedModel={selectedModel} selectedModel={selectedModel}
onOk={handleOnOk} onOk={handleOnOk}
ref={form} ref={form}
isGGUF={isGGUF} isGGUF={isGGUF}
gpuOptions={props.gpuOptions}
modelFileOptions={props.modelFileOptions}
onBackendChange={handleBackendChange} onBackendChange={handleBackendChange}
onValuesChange={onValuesChange} onValuesChange={onValuesChange}
></DataForm> ></DataForm>
+5 -12
View File
@@ -1,4 +1,3 @@
import CheckboxField from '@/components/seal-form/checkbox-field';
import SealCascader from '@/components/seal-form/seal-cascader'; import SealCascader from '@/components/seal-form/seal-cascader';
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
import TooltipList from '@/components/tooltip-list'; import TooltipList from '@/components/tooltip-list';
@@ -7,8 +6,7 @@ import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import React from 'react'; import React from 'react';
import { backendOptionsMap } from '../config'; import { backendOptionsMap } from '../config';
import { useFormContext } from '../config/form-context'; import { useFormContext, useFormInnerContext } from '../config/form-context';
import { FormData } from '../config/types';
import GPUCard from './gpu-card'; import GPUCard from './gpu-card';
const scheduleTypeTips = [ const scheduleTypeTips = [
@@ -30,13 +28,8 @@ const scheduleTypeTips = [
const Performance: React.FC = () => { const Performance: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { const { gpuOptions } = useFormInnerContext();
onValuesChange, const { onValuesChange, onQuantizationChange } = useFormContext();
onQuantizationChange,
gpuOptions,
source,
quantizationOptions
} = useFormContext();
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
@@ -167,7 +160,7 @@ const Performance: React.FC = () => {
</Form.Item> </Form.Item>
</> </>
)} )}
<div style={{ paddingBottom: 22, paddingLeft: 10 }}> {/* <div style={{ paddingBottom: 22, paddingLeft: 10 }}>
<Form.Item<FormData> <Form.Item<FormData>
name="optimize_long_prompt" name="optimize_long_prompt"
valuePropName="checked" valuePropName="checked"
@@ -192,7 +185,7 @@ const Performance: React.FC = () => {
})} })}
></CheckboxField> ></CheckboxField>
</Form.Item> </Form.Item>
</div> </div> */}
</> </>
); );
}; };
+1 -2
View File
@@ -45,8 +45,7 @@ const CheckboxField: React.FC<{
const Scaling: React.FC = () => { const Scaling: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { onValuesChange, onQuantizationChange, source, quantizationOptions } = const { onValuesChange, onQuantizationChange } = useFormContext();
useFormContext();
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
@@ -199,7 +199,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
try { try {
const params = { const params = {
Name: `${searchInputRef.current}`, Name: `${searchInputRef.current}`,
tags: [], tags: ['gptq'],
tasks: filterTaskRef.current tasks: filterTaskRef.current
? ([ModelscopeTaskMap[filterTaskRef.current]] as string[]) ? ([ModelscopeTaskMap[filterTaskRef.current]] as string[])
: [], : [],
+19 -130
View File
@@ -14,11 +14,6 @@ import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort'; import useTableSort from '@/hooks/use-table-sort';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import { handleBatchRequest } from '@/utils'; import { handleBatchRequest } from '@/utils';
import {
IS_FIRST_LOGIN,
readState,
writeState
} from '@/utils/localstore/index';
import { import {
DownOutlined, DownOutlined,
QuestionCircleOutlined, QuestionCircleOutlined,
@@ -26,16 +21,7 @@ import {
} from '@ant-design/icons'; } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import { useIntl, useNavigate } from '@umijs/max'; import { useIntl, useNavigate } from '@umijs/max';
import { import { Button, Input, Select, Space, Tooltip, message } from 'antd';
Button,
Empty,
Input,
Select,
Space,
Tooltip,
Typography,
message
} from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import _ from 'lodash'; import _ from 'lodash';
@@ -76,7 +62,7 @@ import {
ModelInstanceListItem, ModelInstanceListItem,
SourceType SourceType
} from '../config/types'; } from '../config/types';
import { useGenerateFormEditInitialValues } from '../hooks'; import useFormInitialValues from '../hooks/use-form-initial-values';
import APIAccessInfoModal from './api-access-info'; import APIAccessInfoModal from './api-access-info';
import DeployModal from './deploy-modal'; import DeployModal from './deploy-modal';
import Instances from './instances'; import Instances from './instances';
@@ -88,6 +74,7 @@ interface ModelsProps {
handleNameChange: (e: any) => void; handleNameChange: (e: any) => void;
handleShowSizeChange?: (page: number, size: number) => void; handleShowSizeChange?: (page: number, size: number) => void;
handlePageChange: (page: number, pageSize: number | undefined) => void; handlePageChange: (page: number, pageSize: number | undefined) => void;
handleClusterChange: (value: number) => void;
handleDeleteSuccess: () => void; handleDeleteSuccess: () => void;
handleCategoryChange: (val: any) => void; handleCategoryChange: (val: any) => void;
onViewLogs: () => void; onViewLogs: () => void;
@@ -103,29 +90,12 @@ interface ModelsProps {
}; };
deleteIds?: number[]; deleteIds?: number[];
workerList: WorkerListItem[]; workerList: WorkerListItem[];
modelFileOptions: any[];
catalogList?: any[];
dataSource: ListItem[]; dataSource: ListItem[];
loading: boolean; loading: boolean;
loadend: boolean; loadend: boolean;
total: number; total: number;
} }
const clusterList = [
{
label: 'Custom',
value: 'custom'
},
{
label: 'Kubernetes',
value: 'kubernetes'
},
{
label: 'Digital Ocean',
value: 'digital_ocean'
}
];
const statusList = [ const statusList = [
{ {
label: 'Running', label: 'Running',
@@ -164,34 +134,27 @@ const Models: React.FC<ModelsProps> = ({
onCancelViewLogs, onCancelViewLogs,
handleCategoryChange, handleCategoryChange,
handleOnToggleExpandAll, handleOnToggleExpandAll,
handleClusterChange,
onStop, onStop,
onStart, onStart,
modelFileOptions,
deleteIds, deleteIds,
dataSource, dataSource,
workerList, workerList,
catalogList,
queryParams, queryParams,
loading, loading,
loadend, loadend,
total total
}) => { }) => {
const { getGPUList, generateFormValues, gpuDeviceList } = const { getGPUOptionList, generateFormValues, clusterList, getClusterList } =
useGenerateFormEditInitialValues(); useFormInitialValues();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [updateFormInitials, setUpdateFormInitials] = useState<{ const [updateFormInitials, setUpdateFormInitials] = useState<{
gpuOptions: any[];
modelFileOptions?: any[];
data: any; data: any;
isGGUF: boolean; isGGUF: boolean;
}>({ }>({
gpuOptions: [],
modelFileOptions: [],
data: {}, data: {},
isGGUF: false isGGUF: false
}); });
const [isFirstLogin, setIsFirstLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [expandAtom, setExpandAtom] = useAtom(modelsExpandKeysAtom); const [expandAtom, setExpandAtom] = useAtom(modelsExpandKeysAtom);
const intl = useIntl(); const intl = useIntl();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -218,17 +181,13 @@ const Models: React.FC<ModelsProps> = ({
width: number | string; width: number | string;
hasLinuxWorker?: boolean; hasLinuxWorker?: boolean;
source: SourceType; source: SourceType;
gpuOptions: any[];
isGGUF?: boolean; isGGUF?: boolean;
modelFileOptions?: any[];
}>({ }>({
show: false, show: false,
hasLinuxWorker: false, hasLinuxWorker: false,
width: 600, width: 600,
isGGUF: false, isGGUF: false,
source: modelSourceMap.huggingface_value as SourceType, source: modelSourceMap.huggingface_value as SourceType
gpuOptions: [],
modelFileOptions: []
}); });
const currentData = useRef<ListItem>({} as ListItem); const currentData = useRef<ListItem>({} as ListItem);
const [currentInstance, setCurrentInstance] = useState<{ const [currentInstance, setCurrentInstance] = useState<{
@@ -243,17 +202,6 @@ const Models: React.FC<ModelsProps> = ({
}); });
const modalRef = useRef<any>(null); const modalRef = useRef<any>(null);
useEffect(() => {
if (!catalogList?.length) {
return;
}
const getFirstLoginState = async () => {
const is_first_login = await readState(IS_FIRST_LOGIN);
setIsFirstLogin(is_first_login);
};
getFirstLoginState();
}, [catalogList?.length]);
useEffect(() => { useEffect(() => {
if (deleteIds?.length) { if (deleteIds?.length) {
rowSelection.removeSelectedKey(deleteIds); rowSelection.removeSelectedKey(deleteIds);
@@ -262,7 +210,9 @@ const Models: React.FC<ModelsProps> = ({
useEffect(() => { useEffect(() => {
const getData = async () => { const getData = async () => {
await getGPUList(); const res = await getClusterList();
const clusterId = res[0]?.value;
await getGPUOptionList({ clusterId });
}; };
getData(); getData();
return () => { return () => {
@@ -470,10 +420,8 @@ const Models: React.FC<ModelsProps> = ({
}, []); }, []);
const handleEdit = async (row: ListItem) => { const handleEdit = async (row: ListItem) => {
const initialValues = generateFormValues(row, gpuDeviceList.current); const initialValues = generateFormValues(row, []);
setUpdateFormInitials({ setUpdateFormInitials({
gpuOptions: gpuDeviceList.current,
modelFileOptions: modelFileOptions,
data: initialValues, data: initialValues,
isGGUF: row.backend === backendOptionsMap.llamaBox isGGUF: row.backend === backendOptionsMap.llamaBox
}); });
@@ -584,9 +532,7 @@ const Models: React.FC<ModelsProps> = ({
if (config) { if (config) {
setOpenDeployModal({ setOpenDeployModal({
...config, ...config,
hasLinuxWorker: hasLinuxWorker, hasLinuxWorker: hasLinuxWorker
gpuOptions: gpuDeviceList.current,
modelFileOptions: modelFileOptions
}); });
} }
}; };
@@ -652,7 +598,10 @@ const Models: React.FC<ModelsProps> = ({
span: 3, span: 3,
render: (text: string, record: ListItem) => ( render: (text: string, record: ListItem) => (
<span className="flex flex-column" style={{ width: '100%' }}> <span className="flex flex-column" style={{ width: '100%' }}>
{['Custom', 'Kubernetes', 'Digital Ocean'][record.id] || 'Custom'} {
clusterList.find((item) => item.value === record.cluster_id)
?.label
}
</span> </span>
) )
}, },
@@ -724,31 +673,6 @@ const Models: React.FC<ModelsProps> = ({
]; ];
}, [sortOrder, intl, handleSelect]); }, [sortOrder, intl, handleSelect]);
const handleOnClick = async () => {
if (isLoading) {
return;
}
const data = catalogList?.[0] || {};
try {
setIsLoading(true);
const modelData = await createModel({
data: data
});
writeState(IS_FIRST_LOGIN, false);
setIsFirstLogin(false);
setTimeout(() => {
updateExpandedRowKeys([modelData.id]);
}, 300);
message.success(intl.formatMessage({ id: 'common.message.success' }));
handleSearch?.();
} catch (error) {
// ingore
} finally {
setIsLoading(false);
}
};
const handleToggleExpandAll = useCallback( const handleToggleExpandAll = useCallback(
(expanded: boolean) => { (expanded: boolean) => {
const keys = dataSource.map((item) => item.id); const keys = dataSource.map((item) => item.id);
@@ -760,33 +684,6 @@ const Models: React.FC<ModelsProps> = ({
[dataSource] [dataSource]
); );
const renderEmpty = useMemo(() => {
if (dataSource.length || !isFirstLogin || !catalogList?.length) {
return null;
}
return (
<div
className="flex-column justify-center flex-center"
style={{ height: 300 }}
>
<Empty description=""></Empty>
<Typography.Title level={4} style={{ marginBottom: 30 }}>
{intl.formatMessage({ id: 'models.table.list.empty' })}
</Typography.Title>
<div>
<Button type="primary" onClick={handleOnClick} loading={isLoading}>
<span
className="flex-center"
dangerouslySetInnerHTML={{
__html: intl.formatMessage({ id: 'models.table.list.getStart' })
}}
></span>
</Button>
</div>
</div>
);
}, [dataSource.length, isFirstLogin, isLoading, intl]);
return ( return (
<> <>
<PageContainer <PageContainer
@@ -831,17 +728,9 @@ const Models: React.FC<ModelsProps> = ({
style={{ width: 160 }} style={{ width: 160 }}
size="large" size="large"
maxTagCount={1} maxTagCount={1}
onChange={handleClusterChange}
options={clusterList} options={clusterList}
></Select> ></Select>
<Select
allowClear
showSearch={false}
placeholder="Running Replicas"
style={{ width: 140 }}
size="large"
maxTagCount={1}
options={statusList}
></Select>
<Button <Button
type="text" type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }} style={{ color: 'var(--ant-color-text-tertiary)' }}
@@ -918,6 +807,7 @@ const Models: React.FC<ModelsProps> = ({
action={PageAction.EDIT} action={PageAction.EDIT}
title={intl.formatMessage({ id: 'models.title.edit' })} title={intl.formatMessage({ id: 'models.title.edit' })}
updateFormInitials={updateFormInitials} updateFormInitials={updateFormInitials}
clusterList={clusterList}
onCancel={handleModalCancel} onCancel={handleModalCancel}
onOk={handleModalOk} onOk={handleModalOk}
></UpdateModel> ></UpdateModel>
@@ -929,8 +819,7 @@ const Models: React.FC<ModelsProps> = ({
width={openDeployModal.width} width={openDeployModal.width}
isGGUF={openDeployModal.isGGUF} isGGUF={openDeployModal.isGGUF}
hasLinuxWorker={openDeployModal.hasLinuxWorker} hasLinuxWorker={openDeployModal.hasLinuxWorker}
gpuOptions={openDeployModal.gpuOptions} clusterList={clusterList}
modelFileOptions={openDeployModal.modelFileOptions || []}
onCancel={handleDeployModalCancel} onCancel={handleDeployModalCancel}
onOk={handleCreateModel} onOk={handleCreateModel}
></DeployModal> ></DeployModal>
+89 -113
View File
@@ -1,7 +1,6 @@
import ModalFooter from '@/components/modal-footer'; import ModalFooter from '@/components/modal-footer';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
import TooltipList from '@/components/tooltip-list';
import { PageAction } from '@/config'; import { PageAction } from '@/config';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
@@ -10,9 +9,7 @@ import { Button, Form, Modal } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { useEffect, useMemo, useRef } from 'react'; import React, { useEffect, useMemo, useRef } from 'react';
import { import {
backendLabelMap,
backendOptionsMap, backendOptionsMap,
backendTipsList,
updateExcludeFields as excludeFields, updateExcludeFields as excludeFields,
getSourceRepoConfigValue, getSourceRepoConfigValue,
modelSourceMap, modelSourceMap,
@@ -24,6 +21,7 @@ import { FormData, ListItem } from '../config/types';
import HuggingFaceForm from '../forms/hugging-face'; import HuggingFaceForm from '../forms/hugging-face';
import LocalPathForm from '../forms/local-path'; import LocalPathForm from '../forms/local-path';
import { useCheckCompatibility } from '../hooks'; import { useCheckCompatibility } from '../hooks';
import { useGenerateGPUOptions } from '../hooks/use-form-initial-values';
import AdvanceConfig from './advance-config'; import AdvanceConfig from './advance-config';
import ColumnWrapper from './column-wrapper'; import ColumnWrapper from './column-wrapper';
import CompatibilityAlert from './compatible-alert'; import CompatibilityAlert from './compatible-alert';
@@ -34,9 +32,12 @@ type AddModalProps = {
open: boolean; open: boolean;
updateFormInitials: { updateFormInitials: {
data?: ListItem; data?: ListItem;
gpuOptions: any[];
isGGUF: boolean; isGGUF: boolean;
}; };
clusterList: Global.BaseOption<
number,
{ provider: string; state: string | number }
>[];
onOk: (values: FormData) => void; onOk: (values: FormData) => void;
onCancel: () => void; onCancel: () => void;
}; };
@@ -48,7 +49,8 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
open, open,
onOk, onOk,
onCancel, onCancel,
updateFormInitials: { gpuOptions, isGGUF, data: formData } clusterList,
updateFormInitials: { isGGUF, data: formData }
} = props || {}; } = props || {};
const intl = useIntl(); const intl = useIntl();
const { const {
@@ -58,12 +60,17 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
checkTokenRef, checkTokenRef,
warningStatus warningStatus
} = useCheckCompatibility(); } = useCheckCompatibility();
const { getGPUOptionList, gpuOptions } = useGenerateGPUOptions();
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
const [form] = Form.useForm(); const [form] = Form.useForm();
const submitAnyway = useRef<boolean>(false); const submitAnyway = useRef<boolean>(false);
const originFormData = useRef<any>(null); const originFormData = useRef<any>(null);
const handleClusterChange = (value: number) => {
getGPUOptionList({ clusterId: value });
};
const setOriginalFormData = () => { const setOriginalFormData = () => {
if (!originFormData.current) { if (!originFormData.current) {
originFormData.current = _.cloneDeep(formData); originFormData.current = _.cloneDeep(formData);
@@ -336,48 +343,53 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
onValuesChange: handleManulOnValuesChange onValuesChange: handleManulOnValuesChange
}} }}
> >
<Form <FormInnerContext.Provider
name="updateModalForm" value={{
form={form} onBackendChange: handleBackendChange,
onFinish={handleOk} gpuOptions: gpuOptions
onValuesChange={onValuesChange}
scrollToFirstError={true}
preserve={false}
clearOnDestroy={true}
initialValues={{
...formData
}}
style={{
padding: 'var(--ant-modal-content-padding)',
paddingBlock: 0
}} }}
> >
<Form.Item<FormData> <Form
name="name" name="updateModalForm"
rules={[ form={form}
{ onFinish={handleOk}
required: true, onValuesChange={onValuesChange}
message: getRuleMessage('input', 'common.table.name') scrollToFirstError={true}
} preserve={false}
]} clearOnDestroy={true}
initialValues={{
...formData
}}
style={{
padding: 'var(--ant-modal-content-padding)',
paddingBlock: 0
}}
> >
<SealInput.Input <Form.Item<FormData>
label={intl.formatMessage({ name="name"
id: 'common.table.name' rules={[
})} {
required required: true,
></SealInput.Input> message: getRuleMessage('input', 'common.table.name')
</Form.Item> }
<Form.Item<FormData> ]}
name="source" >
rules={[ <SealInput.Input
{ label={intl.formatMessage({
required: true, id: 'common.table.name'
message: getRuleMessage('select', 'models.form.source') })}
} required
]} ></SealInput.Input>
> </Form.Item>
{action === PageAction.EDIT && ( <Form.Item<FormData>
name="source"
rules={[
{
required: true,
message: getRuleMessage('select', 'models.form.source')
}
]}
>
<SealSelect <SealSelect
disabled={true} disabled={true}
label={intl.formatMessage({ label={intl.formatMessage({
@@ -386,82 +398,46 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
options={sourceOptions} options={sourceOptions}
required required
></SealSelect> ></SealSelect>
)} </Form.Item>
</Form.Item>
<FormInnerContext.Provider
value={{
onBackendChange: handleBackendChange,
gpuOptions: gpuOptions
}}
>
<HuggingFaceForm></HuggingFaceForm> <HuggingFaceForm></HuggingFaceForm>
<LocalPathForm></LocalPathForm> <LocalPathForm></LocalPathForm>
</FormInnerContext.Provider> <Form.Item<FormData>
<Form.Item name="backend" rules={[{ required: true }]}> name="cluster_id"
<SealSelect rules={[
required
onChange={handleAsyncBackendChange}
label={intl.formatMessage({ id: 'models.form.backend' })}
description={<TooltipList list={backendTipsList}></TooltipList>}
options={[
{ {
label: backendLabelMap[backendOptionsMap.llamaBox], required: true,
value: backendOptionsMap.llamaBox, message: getRuleMessage('select', 'Cluster', false)
disabled:
formData?.source === modelSourceMap.local_path_value
? false
: !isGGUF
},
{
label: backendLabelMap[backendOptionsMap.vllm],
value: backendOptionsMap.vllm,
disabled:
formData?.source === modelSourceMap.local_path_value ||
isVllmOrAscend
? false
: isGGUF
},
{
label: backendLabelMap[backendOptionsMap.ascendMindie],
value: backendOptionsMap.ascendMindie,
disabled:
formData?.source === modelSourceMap.local_path_value ||
isVllmOrAscend
? false
: isGGUF
},
{
label: backendLabelMap[backendOptionsMap.voxBox],
value: backendOptionsMap.voxBox,
disabled:
formData?.source !== modelSourceMap.local_path_value ||
!isVllmOrAscend
} }
]} ]}
disabled={ >
action === PageAction.EDIT && {
formData?.source !== modelSourceMap.local_path_value && <SealSelect
!isVllmOrAscend onChange={handleClusterChange}
label="Cluster"
options={clusterList}
required
></SealSelect>
} }
></SealSelect> </Form.Item>
</Form.Item> <Form.Item<FormData> name="description">
<Form.Item<FormData> name="description"> <SealInput.TextArea
<SealInput.TextArea scaleSize={true}
scaleSize={true} label={intl.formatMessage({
label={intl.formatMessage({ id: 'common.table.description'
id: 'common.table.description' })}
})} ></SealInput.TextArea>
></SealInput.TextArea> </Form.Item>
</Form.Item>
<AdvanceConfig <AdvanceConfig
form={form} form={form}
gpuOptions={gpuOptions} gpuOptions={gpuOptions}
action={PageAction.EDIT} action={PageAction.EDIT}
source={formData?.source || ''} source={formData?.source || ''}
isGGUF={formData?.backend === backendOptionsMap.llamaBox} isGGUF={formData?.backend === backendOptionsMap.llamaBox}
></AdvanceConfig> ></AdvanceConfig>
</Form> </Form>
</FormInnerContext.Provider>
</FormContext.Provider> </FormContext.Provider>
</ColumnWrapper> </ColumnWrapper>
</Modal> </Modal>
@@ -8,7 +8,6 @@ interface FormContextProps {
pageAction: PageActionType; pageAction: PageActionType;
sizeOptions?: Global.BaseOption<number>[]; sizeOptions?: Global.BaseOption<number>[];
quantizationOptions?: Global.BaseOption<string>[]; quantizationOptions?: Global.BaseOption<string>[];
modelFileOptions?: any[];
gpuOptions?: any[]; gpuOptions?: any[];
onSizeChange?: (val: number) => void; onSizeChange?: (val: number) => void;
onQuantizationChange?: (val: string) => void; onQuantizationChange?: (val: string) => void;
+2
View File
@@ -20,6 +20,7 @@ export interface ListItem {
name: string; name: string;
description: string; description: string;
id: number; id: number;
cluster_id: number;
local_path?: string; local_path?: string;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
@@ -69,6 +70,7 @@ export interface FormData {
description: string; description: string;
optimize_long_prompt: boolean; optimize_long_prompt: boolean;
enable_speculative_decoding: boolean; enable_speculative_decoding: boolean;
cluster_id: number;
} }
interface ComputedResourceClaim { interface ComputedResourceClaim {
+4 -1
View File
@@ -20,6 +20,7 @@ type AddModalProps = {
width?: string | number; width?: string | number;
hasLinuxWorker?: boolean; hasLinuxWorker?: boolean;
workersList: Global.BaseOption<number>[]; workersList: Global.BaseOption<number>[];
workerOptions: any[];
onOk: (values: FormData) => void; onOk: (values: FormData) => void;
onCancel: () => void; onCancel: () => void;
}; };
@@ -33,7 +34,8 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
onCancel, onCancel,
hasLinuxWorker, hasLinuxWorker,
source, source,
width = 600 width = 600,
workerOptions
} = props || {}; } = props || {};
const SEARCH_SOURCE = [ const SEARCH_SOURCE = [
modelSourceMap.huggingface_value, modelSourceMap.huggingface_value,
@@ -227,6 +229,7 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
onOk={handleOk} onOk={handleOk}
source={source} source={source}
workersList={workersList} workersList={workersList}
workerOptions={workerOptions}
></TargetForm> ></TargetForm>
</> </>
</ColumnWrapper> </ColumnWrapper>
+20 -8
View File
@@ -13,11 +13,12 @@ interface TargetFormProps {
ref?: any; ref?: any;
workersList: Global.BaseOption<number>[]; workersList: Global.BaseOption<number>[];
source: string; source: string;
workerOptions: any[];
onOk: (values: any) => void; onOk: (values: any) => void;
} }
const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => { const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
const { onOk, source, workersList } = props; const { onOk, source, workersList, workerOptions } = props;
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
const intl = useIntl(); const intl = useIntl();
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -115,13 +116,24 @@ const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
} }
]} ]}
> >
{ <SealSelect
<SealSelect label="Worker"
label="Worker" options={workersList}
options={workersList} required
required ></SealSelect>
></SealSelect> {/* <SealCascader
} required
showSearch
expandTrigger="hover"
multiple={false}
popupClassName="cascader-popup-wrapper gpu-selector"
maxTagCount={1}
label="Worker"
options={workerOptions}
showCheckedStrategy="SHOW_CHILD"
value={form.getFieldValue(['gpu_selector', 'gpu_ids'])}
getPopupContainer={(triggerNode) => triggerNode.parentNode}
></SealCascader> */}
</Form.Item> </Form.Item>
{source !== modelSourceMap.local_path_value && ( {source !== modelSourceMap.local_path_value && (
<Form.Item<FormData> <Form.Item<FormData>
+4 -156
View File
@@ -1,30 +1,19 @@
import { createAxiosToken } from '@/hooks/use-chunk-request'; import { createAxiosToken } from '@/hooks/use-chunk-request';
import { queryModelFilesList, queryWorkersList } from '@/pages/resources/apis'; import { queryModelFilesList } from '@/pages/resources/apis';
import {
WorkerStatusMap,
WorkerStatusMapValue
} from '@/pages/resources/config';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import { convertFileSize } from '@/utils'; import { convertFileSize } from '@/utils';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { useDebounceFn } from 'ahooks';
import _ from 'lodash'; import _ from 'lodash';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { evaluationsModelSpec, queryGPUList } from '../apis'; import { evaluationsModelSpec } from '../apis';
import { import {
backendOptionsMap, backendOptionsMap,
getSourceRepoConfigValue, getSourceRepoConfigValue,
modelSourceMap, modelSourceMap,
modelTaskMap, modelTaskMap
setSourceRepoConfigValue
} from '../config'; } from '../config';
import { handleRecognizeAudioModel } from '../config/audio-catalog'; import { handleRecognizeAudioModel } from '../config/audio-catalog';
import { import { EvaluateResult, FormData } from '../config/types';
EvaluateResult,
FormData,
GPUListItem,
ListItem
} from '../config/types';
export type MessageStatus = { export type MessageStatus = {
show: boolean; show: boolean;
@@ -41,123 +30,6 @@ export type WarningStausOptions = {
override?: boolean; override?: boolean;
}; };
export const useGenerateFormEditInitialValues = () => {
const gpuDeviceList = useRef<any[]>([]);
const workerList = useRef<any[]>([]);
const generateCascaderOptions = (
list: GPUListItem[],
workerList: WorkerListItem[]
) => {
// pick the worker fields from gpuList
const workerFields = new Set(['worker_name', 'worker_id', 'worker_ip']);
// generate a map for workerList by name to data
const workerDataMap = new Map<string, WorkerListItem>();
for (const worker of workerList) {
workerDataMap.set(worker.name, worker);
}
const workersMap = new Map<string, GPUListItem[]>();
for (const gpu of list) {
if (!workersMap.has(gpu.worker_name)) {
workersMap.set(gpu.worker_name, []);
}
workersMap.get(gpu.worker_name)!.push(gpu);
}
const gpuSelectorList = Array.from(workersMap.entries()).map(
([workerName, items]) => {
const firstItem = items[0];
const currentState = workerDataMap.get(workerName)?.state || '';
const disDisabled = WorkerStatusMap.ready !== currentState;
return {
label: disDisabled
? `${workerName} [${WorkerStatusMapValue[currentState]}]`
: workerName,
value: workerName,
parent: true,
disabled: disDisabled,
children: items
.map((item) => ({
label: item.name,
value: item.id,
index: item.index,
...Object.fromEntries(
Object.entries(item).filter(([key]) => !workerFields.has(key))
)
}))
.sort((a, b) => a.index - b.index),
...Object.fromEntries(
Object.entries(firstItem).filter(([key]) => workerFields.has(key))
)
};
}
);
return gpuSelectorList;
};
const getGPUList = async () => {
const [gpuData, workerData] = await Promise.all([
queryGPUList({ page: 1, perPage: 100 }),
queryWorkersList({ page: 1, perPage: 100 })
]);
const gpuList = generateCascaderOptions(gpuData.items, workerData.items);
gpuDeviceList.current = gpuList;
workerList.current = workerData.items;
return gpuList;
};
const generateGPUSelector = (data: any, gpuOptions: any[]) => {
const gpu_ids = _.get(data, 'gpu_selector.gpu_ids', []);
if (gpu_ids.length === 0) {
return [];
}
const valueMap = new Map<string, string>();
gpuOptions?.forEach((item) => {
item.children?.forEach((child: any) => {
valueMap.set(child.value, item.value);
});
});
const gpuids: string[][] = gpu_ids
.map((id: string) => {
const parent = valueMap.get(id);
return parent ? [parent, id] : null;
})
.filter(Boolean) as string[][];
return data.backend === backendOptionsMap.voxBox ? gpuids[0] : gpuids;
};
const generateFormValues = (data: ListItem, gpuOptions: any[]) => {
const result = setSourceRepoConfigValue(data?.source || '', data);
const formData = {
...result.values,
categories: data?.categories?.length ? data.categories[0] : null,
scheduleType: data?.gpu_selector ? 'manual' : 'auto',
gpu_selector: data?.gpu_selector?.gpu_ids?.length
? {
gpu_ids: generateGPUSelector(data, gpuOptions)
}
: null
};
return formData;
};
return {
getGPUList,
generateFormValues,
gpuDeviceList,
workerList
};
};
export const useGenerateModelFileOptions = () => { export const useGenerateModelFileOptions = () => {
const getModelFileList = async () => { const getModelFileList = async () => {
try { try {
@@ -514,25 +386,6 @@ export const useCheckCompatibility = () => {
return null; return null;
}; };
const checkRequiredValue = (allValues: any) => {
const { scheduleType } = allValues;
const gpuIds = allValues.gpu_selector?.gpu_ids || [];
const noLocalValue =
allValues.source === modelSourceMap.local_path_value &&
!allValues.local_path;
const noOllamaValue =
allValues.source === modelSourceMap.ollama_library_value &&
!allValues.ollama_library_model_name;
if (scheduleType === 'manual') {
return !gpuIds.length || noLocalValue || noOllamaValue;
}
return noLocalValue || noOllamaValue;
};
const clearCahceFormValues = () => { const clearCahceFormValues = () => {
cacheFormValuesRef.current = {}; cacheFormValuesRef.current = {};
}; };
@@ -585,11 +438,6 @@ export const useCheckCompatibility = () => {
return res; return res;
}; };
const { run: debounceHandleValuesChange } = useDebounceFn(
handleOnValuesChange,
{ wait: 500 }
);
const cancelEvaluate = () => { const cancelEvaluate = () => {
// update the requestId to cancel the current evaluation // update the requestId to cancel the current evaluation
updateRequestId(); updateRequestId();
@@ -0,0 +1,255 @@
import { queryClusterList } from '@/pages/cluster-management/apis';
import { ClusterListItem } from '@/pages/cluster-management/config/types';
import { queryWorkersList } from '@/pages/resources/apis';
import {
WorkerStatusMap,
WorkerStatusMapValue
} from '@/pages/resources/config';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import _ from 'lodash';
import { useState } from 'react';
import { queryGPUList } from '../apis';
import { backendOptionsMap, setSourceRepoConfigValue } from '../config';
import { GPUListItem, ListItem } from '../config/types';
interface CascaderOption {
label: string;
value: string | number;
parent?: boolean;
disabled?: boolean;
index?: number;
children?: CascaderOption[];
}
export const useGenerateGPUOptions = () => {
const [gpuOptions, setGpuOptions] = useState<CascaderOption[]>([]);
const generateCascaderGPUOptions = (
gpuList: GPUListItem[],
workerList: WorkerListItem[]
) => {
// pick the worker fields from gpuList
const workerFields = new Set(['worker_name', 'worker_id', 'worker_ip']);
// generate a map for workerList by name to data
const workerDataMap = new Map<string, WorkerListItem>();
for (const worker of workerList) {
workerDataMap.set(worker.name, worker);
}
const workersMap = new Map<string, GPUListItem[]>();
for (const gpu of gpuList) {
if (!workersMap.has(gpu.worker_name)) {
workersMap.set(gpu.worker_name, []);
}
workersMap.get(gpu.worker_name)!.push(gpu);
}
const gpuSelectorList = Array.from(workersMap.entries()).map(
([workerName, items]) => {
const firstItem = items[0];
const currentState = workerDataMap.get(workerName)?.state || '';
const disDisabled = WorkerStatusMap.ready !== currentState;
return {
label: disDisabled
? `${workerName} [${WorkerStatusMapValue[currentState]}]`
: workerName,
value: workerName,
parent: true,
disabled: disDisabled,
children: items
.map((item) => ({
label: item.name,
value: item.id,
index: item.index,
...Object.fromEntries(
Object.entries(item).filter(([key]) => !workerFields.has(key))
)
}))
.sort((a, b) => a.index - b.index),
...Object.fromEntries(
Object.entries(firstItem).filter(([key]) => workerFields.has(key))
)
};
}
);
return gpuSelectorList;
};
const getGPUOptionList = async (params?: { clusterId: number }) => {
const { clusterId } = params || {};
const [gpuData, workerData] = await Promise.all([
queryGPUList({
page: 1,
perPage: 100,
cluster_id: clusterId
}),
queryWorkersList({
page: 1,
perPage: 100,
cluster_id: clusterId
})
]);
const gpuList = generateCascaderGPUOptions(gpuData.items, workerData.items);
setGpuOptions(gpuList);
return gpuList;
};
return {
getGPUOptionList,
gpuOptions
};
};
export const useGenerateWorkerOptions = () => {
const [workerOptions, setWorkerOptions] = useState<CascaderOption[]>([]);
const [clusterList, setClusterList] = useState<
Global.BaseOption<number, { provider: string; state: string | number }>[]
>([]);
const [workersList, setWorkersList] = useState<
Global.BaseOption<
number,
{ state: string; labels: Record<string, string> }
>[]
>([]);
const generateCascaderWorkerOptions = (
workerList: WorkerListItem[],
clusterList: ClusterListItem[]
) => {
const options = clusterList.map((cluster) => ({
label: cluster.name,
value: cluster.id,
parent: true,
children: workerList
.filter((worker) => worker.cluster_id === cluster.id)
.map((worker) => ({
disabled: WorkerStatusMap.ready !== worker.state,
label: worker.name,
value: worker.id
}))
}));
setWorkerOptions(options);
return options;
};
const getDataList = async (): Promise<
[WorkerListItem[], ClusterListItem[]]
> => {
const [workerRes, clusterRes] = await Promise.all([
queryWorkersList({
page: 1,
perPage: 100
}),
queryClusterList({
page: 1,
perPage: 100
})
]);
const workerList = workerRes.items || ([] as WorkerListItem[]);
const clusterList = clusterRes.items || ([] as ClusterListItem[]);
return [workerList, clusterList];
};
const getWorkerOptionList = async () => {
const data = await getDataList();
const [workerList, clusterList] = data;
generateCascaderWorkerOptions(workerList, clusterList);
setWorkersList(
workerList.map((item) => ({
state: item.state,
label: item.name,
value: item.id
}))
);
setClusterList(
clusterList.map((item) => ({
label: item.name,
value: item.id,
provider: item.provider,
state: item.state
}))
);
};
return {
getWorkerOptionList,
workerOptions,
clusterList,
workersList
};
};
export default function useFormInitialValues() {
const { getGPUOptionList } = useGenerateGPUOptions();
const [clusterList, setClusterList] = useState<
Global.BaseOption<number, { provider: string; state: string | number }>[]
>([]);
const getClusterList = async (): Promise<Global.BaseOption<number>[]> => {
try {
const response = await queryClusterList({
page: 1,
perPage: 100
});
const list = response.items.map((item) => ({
label: item.name,
value: item.id,
provider: item.provider,
state: item.state
}));
setClusterList(list);
return list;
} catch (error) {
console.error('Failed to fetch cluster list:', error);
setClusterList([]);
return [];
}
};
const generateGPUSelector = (data: any, gpuOptions: any[]) => {
const gpu_ids = _.get(data, 'gpu_selector.gpu_ids', []);
if (gpu_ids.length === 0) {
return [];
}
const valueMap = new Map<string, string>();
gpuOptions?.forEach((item) => {
item.children?.forEach((child: any) => {
valueMap.set(child.value, item.value);
});
});
const gpuids: string[][] = gpu_ids
.map((id: string) => {
const parent = valueMap.get(id);
return parent ? [parent, id] : null;
})
.filter(Boolean) as string[][];
return data.backend === backendOptionsMap.voxBox ? gpuids[0] : gpuids;
};
const generateFormValues = (data: ListItem, gpuOptions: any[]) => {
const result = setSourceRepoConfigValue(data?.source || '', data);
const formData = {
...result.values,
categories: data?.categories?.length ? data.categories[0] : null,
scheduleType: data?.gpu_selector ? 'manual' : 'auto',
gpu_selector: data?.gpu_selector?.gpu_ids?.length
? {
gpu_ids: generateGPUSelector(data, gpuOptions)
}
: null
};
return formData;
};
return {
getGPUOptionList,
generateFormValues,
getClusterList,
clusterList
};
}
+35 -97
View File
@@ -3,26 +3,19 @@ import useSetChunkRequest from '@/hooks/use-chunk-request';
import useUpdateChunkedList from '@/hooks/use-update-chunk-list'; import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
import { queryWorkersList } from '@/pages/resources/apis'; import { queryWorkersList } from '@/pages/resources/apis';
import { ListItem as WokerListItem } from '@/pages/resources/config/types'; import { ListItem as WokerListItem } from '@/pages/resources/config/types';
import { IS_FIRST_LOGIN, readState } from '@/utils/localstore';
import _ from 'lodash'; import _ from 'lodash';
import qs from 'query-string'; import qs from 'query-string';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { import {
MODELS_API, MODELS_API,
MODEL_INSTANCE_API, MODEL_INSTANCE_API,
queryCatalogItemSpec,
queryCatalogList,
queryModelsInstances, queryModelsInstances,
queryModelsList queryModelsList
} from './apis'; } from './apis';
import TableList from './components/table-list'; import TableList from './components/table-list';
import { backendOptionsMap } from './config';
import { ListItem } from './config/types'; import { ListItem } from './config/types';
import { useGenerateModelFileOptions } from './hooks';
const Models: React.FC = () => { const Models: React.FC = () => {
const { getModelFileList, generateModelFileOptions } =
useGenerateModelFileOptions();
const { setChunkRequest, createAxiosToken } = useSetChunkRequest(); const { setChunkRequest, createAxiosToken } = useSetChunkRequest();
const { setChunkRequest: setModelInstanceChunkRequest } = const { setChunkRequest: setModelInstanceChunkRequest } =
useSetChunkRequest(); useSetChunkRequest();
@@ -41,9 +34,7 @@ const Models: React.FC = () => {
total: 0 total: 0
}); });
const [catalogList, setCatalogList] = useState<any[]>([]);
const [workerList, setWorkerList] = useState<WokerListItem[]>([]); const [workerList, setWorkerList] = useState<WokerListItem[]>([]);
const [modelFileOptions, setModelFileOptions] = useState<any[]>([]);
const chunkRequedtRef = useRef<any>(); const chunkRequedtRef = useRef<any>();
const chunkInstanceRequedtRef = useRef<any>(); const chunkInstanceRequedtRef = useRef<any>();
const isPageHidden = useRef(false); const isPageHidden = useRef(false);
@@ -168,20 +159,20 @@ const Models: React.FC = () => {
[queryParams] [queryParams]
); );
const handleQueryChange = (params: any) => {
setQueryParams({
...queryParams,
...params
});
fetchData({ query: { ...queryParams, ...params } });
};
const handlePageChange = useCallback( const handlePageChange = useCallback(
(page: number, pageSize: number | undefined) => { (page: number, pageSize: number | undefined) => {
setQueryParams({ handleQueryChange({
...queryParams,
page: page, page: page,
perPage: pageSize || 10 perPage: pageSize || 10
}); });
fetchData({
query: {
...queryParams,
page: page,
perPage: pageSize || 10
}
});
}, },
[queryParams] [queryParams]
); );
@@ -269,18 +260,10 @@ const Models: React.FC = () => {
); );
const debounceUpdateFilter = _.debounce((e: any) => { const debounceUpdateFilter = _.debounce((e: any) => {
setQueryParams({ handleQueryChange({
...queryParams,
page: 1, page: 1,
search: e.target.value search: e.target.value
}); });
fetchData({
query: {
...queryParams,
page: 1,
search: e.target.value
}
});
createModelsChunkRequest({ createModelsChunkRequest({
search: e.target.value, search: e.target.value,
categories: queryParams.categories categories: queryParams.categories
@@ -289,27 +272,27 @@ const Models: React.FC = () => {
const handleNameChange = useCallback(debounceUpdateFilter, [queryParams]); const handleNameChange = useCallback(debounceUpdateFilter, [queryParams]);
const handleCategoryChange = useCallback( const handleCategoryChange = async (value: any) => {
async (value: any) => { handleQueryChange({
setQueryParams({ page: 1,
...queryParams, categories: value
page: 1, });
categories: value createModelsChunkRequest({
}); search: queryParams.search,
fetchData({ categories: value
query: { });
...queryParams, };
page: 1,
categories: value const handleClusterChange = async (value: any) => {
} handleQueryChange({
}); page: 1,
createModelsChunkRequest({ cluster_id: value
search: queryParams.search, });
categories: value createModelsChunkRequest({
}); search: queryParams.search,
}, cluster_id: value
[queryParams] });
); };
useEffect(() => { useEffect(() => {
let timer: any = null; let timer: any = null;
@@ -345,55 +328,12 @@ const Models: React.FC = () => {
} }
}; };
// get catalog list
const getCataLogList = async () => {
const isFirstLogin = readState(IS_FIRST_LOGIN);
if (!isFirstLogin) {
return;
}
try {
const res: any = await queryCatalogList({
search: 'DeepSeek R1',
page: 1
});
if (!res?.items?.length) {
return [];
}
const name = _.toLower(res?.items[0]?.name).replace(/\s/g, '-') || '';
const catalogSpecs: any = await queryCatalogItemSpec({
id: res?.items[0]?.id
});
const list = catalogSpecs?.items?.map((item: any) => {
item.name = name;
return item;
});
const deepseekr1dstill = _.toLower('DeepSeek-R1-Distill-Qwen-1.5B');
const resultList = list?.filter((item: any) => {
return (
item.backend === backendOptionsMap.llamaBox &&
(_.toLower(item?.huggingface_repo_id)?.indexOf(deepseekr1dstill) >
-1 ||
_.toLower(item?.model_scope_model_id)?.indexOf(deepseekr1dstill) >
-1)
);
});
return resultList || [];
} catch (error) {
// ignore
return [];
}
};
const init = async () => { const init = async () => {
const [modelRes, workerRes, modelFileList] = await Promise.all([ const [modelRes, workerRes] = await Promise.all([
getTableData(), getTableData(),
getWorkerList(), getWorkerList()
getModelFileList()
]); ]);
const dataList = generateModelFileOptions(
modelFileList,
workerRes.items || []
);
setDataSource({ setDataSource({
dataList: modelRes.items || [], dataList: modelRes.items || [],
loading: false, loading: false,
@@ -402,7 +342,6 @@ const Models: React.FC = () => {
deletedIds: [] deletedIds: []
}); });
setWorkerList(workerRes.items || []); setWorkerList(workerRes.items || []);
setModelFileOptions(dataList);
clearTimeout(timer); clearTimeout(timer);
timer = setTimeout(() => { timer = setTimeout(() => {
@@ -464,6 +403,7 @@ const Models: React.FC = () => {
dataSource={dataSource.dataList} dataSource={dataSource.dataList}
handleNameChange={handleNameChange} handleNameChange={handleNameChange}
handleCategoryChange={handleCategoryChange} handleCategoryChange={handleCategoryChange}
handleClusterChange={handleClusterChange}
handleSearch={handleSearch} handleSearch={handleSearch}
handlePageChange={handlePageChange} handlePageChange={handlePageChange}
handleDeleteSuccess={fetchData} handleDeleteSuccess={fetchData}
@@ -478,8 +418,6 @@ const Models: React.FC = () => {
total={dataSource.total} total={dataSource.total}
deleteIds={dataSource.deletedIds} deleteIds={dataSource.deletedIds}
workerList={workerList} workerList={workerList}
modelFileOptions={modelFileOptions}
catalogList={catalogList}
></TableList> ></TableList>
</TableContext.Provider> </TableContext.Provider>
); );
+4 -2
View File
@@ -5,9 +5,11 @@ export const WORKERS_API = '/workers';
export const GPU_DEVICES_API = '/gpu-devices'; export const GPU_DEVICES_API = '/gpu-devices';
export const MODEL_FILES_API = '/model-files'; export const MODEL_FILES_API = '/model-files';
export async function queryWorkersList(params: Global.SearchParams) { export async function queryWorkersList<T extends Record<string, any>>(
params: Global.SearchParams & T
) {
return request<Global.PageResponse<ListItem>>(`${WORKERS_API}`, { return request<Global.PageResponse<ListItem>>(`${WORKERS_API}`, {
methos: 'GET', method: 'GET',
params params
}); });
} }
@@ -5,12 +5,16 @@ import ContainerInstall from './container-install';
type ViewModalProps = { type ViewModalProps = {
open: boolean; open: boolean;
token: string; registrationInfo: {
token: string;
image: string;
server_url: string;
};
onCancel: () => void; onCancel: () => void;
}; };
const AddWorker: React.FC<ViewModalProps> = (props) => { const AddWorker: React.FC<ViewModalProps> = (props) => {
const { open, onCancel, token = '${token}' } = props || {}; const { open, onCancel, registrationInfo } = props || {};
const intl = useIntl(); const intl = useIntl();
return ( return (
@@ -30,7 +34,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
maxContentHeight={450} maxContentHeight={450}
footer={null} footer={null}
> >
<ContainerInstall token={token} /> <ContainerInstall registrationInfo={registrationInfo} />
</ScrollerModal> </ScrollerModal>
); );
}; };
@@ -9,7 +9,11 @@ import { addWorkerGuide, containerInstallOptions } from '../config';
import './styles/installation.less'; import './styles/installation.less';
type ViewModalProps = { type ViewModalProps = {
token: string; registrationInfo: {
token: string;
image: string;
server_url: string;
};
}; };
const npuOptions = [ const npuOptions = [
@@ -18,6 +22,7 @@ const npuOptions = [
]; ];
const AddWorker: React.FC<ViewModalProps> = (props) => { const AddWorker: React.FC<ViewModalProps> = (props) => {
const { registrationInfo } = props || {};
const intl = useIntl(); const intl = useIntl();
const origin = window.location.origin; const origin = window.location.origin;
@@ -40,12 +45,18 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
const tag = activeKey === 'cuda' ? version : `${version}-${activeKey}`; const tag = activeKey === 'cuda' ? version : `${version}-${activeKey}`;
return commandCode?.registerWorker({ return commandCode?.registerWorker({
server: origin, server: registrationInfo.server_url || origin,
tag: tag, tag: tag,
token: props.token || '${token}', token: registrationInfo.token || '${token}',
workerip: '${workerip}' workerip: '${workerip}'
}); });
}, [versionInfo, activeKey, props.token, npuKey]); }, [
versionInfo,
activeKey,
registrationInfo.token,
registrationInfo.server_url,
npuKey
]);
const handleOnChange = (value: string | number) => { const handleOnChange = (value: string | number) => {
setNpuKey(value as string); setNpuKey(value as string);
@@ -53,7 +64,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
return ( return (
<div className="container-install"> <div className="container-install">
<h3 className="m-t-10 font-size-14 font-600"> <h3 className="m-t-0 font-size-14 font-600">
1. {intl.formatMessage({ id: 'resources.worker.add.step2' })}{' '} 1. {intl.formatMessage({ id: 'resources.worker.add.step2' })}{' '}
<span <span
className="font-size-12" className="font-size-12"
+22 -64
View File
@@ -20,6 +20,7 @@ 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 DownloadModal from '@/pages/llmodels/download'; import DownloadModal from '@/pages/llmodels/download';
import { useGenerateWorkerOptions } from '@/pages/llmodels/hooks/use-form-initial-values';
import { convertFileSize } from '@/utils'; import { convertFileSize } from '@/utils';
import { import {
CheckCircleFilled, CheckCircleFilled,
@@ -32,19 +33,14 @@ import { ConfigProvider, Empty, Table, Tag, Typography, message } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import _ from 'lodash'; import _ from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { import { checkCurrentbackend } from '../../llmodels/hooks';
checkCurrentbackend,
useGenerateFormEditInitialValues,
useGenerateModelFileOptions
} from '../../llmodels/hooks';
import { import {
MODEL_FILES_API, MODEL_FILES_API,
deleteModelFile, deleteModelFile,
downloadModelFile, downloadModelFile,
queryModelFilesList, queryModelFilesList,
queryWorkersList,
retryDownloadModelFile retryDownloadModelFile
} from '../apis'; } from '../apis';
import { import {
@@ -53,10 +49,7 @@ import {
ModelfileStateMapValue, ModelfileStateMapValue,
WorkerStatusMap WorkerStatusMap
} from '../config'; } from '../config';
import { import { ModelFile as ListItem } from '../config/types';
ModelFile as ListItem,
ListItem as WorkerListItem
} from '../config/types';
const { Paragraph } = Typography; const { Paragraph } = Typography;
@@ -287,7 +280,8 @@ const ResolvedPathColumn = (props: { record: ListItem }) => {
}; };
const ModelFiles = () => { const ModelFiles = () => {
const { getGPUList } = useGenerateFormEditInitialValues(); const { getWorkerOptionList, workerOptions, clusterList, workersList } =
useGenerateWorkerOptions();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [modelsExpandKeys, setModelsExpandKeys] = useAtom(modelsExpandKeysAtom); const [modelsExpandKeys, setModelsExpandKeys] = useAtom(modelsExpandKeysAtom);
const navigate = useNavigate(); const navigate = useNavigate();
@@ -311,63 +305,36 @@ const ModelFiles = () => {
watch: true, watch: true,
contentForDelete: 'resources.modelfiles.modelfile' contentForDelete: 'resources.modelfiles.modelfile'
}); });
const { getModelFileList, generateModelFileOptions } =
useGenerateModelFileOptions();
const intl = useIntl(); const intl = useIntl();
const { showSuccess } = useAppUtils(); const { showSuccess } = useAppUtils();
const [workersList, setWorkersList] = useState<any[]>([]);
const [downloadModalStatus, setDownlaodMoalStatus] = useState<{ const [downloadModalStatus, setDownlaodMoalStatus] = useState<{
show: boolean; show: boolean;
width: number | string; width: number | string;
source: string; source: string;
hasLinuxWorker: boolean; hasLinuxWorker: boolean;
gpuOptions: any[];
}>({ }>({
show: false, show: false,
width: 600, width: 600,
hasLinuxWorker: false, hasLinuxWorker: false,
source: modelSourceMap.huggingface_value, source: modelSourceMap.huggingface_value
gpuOptions: []
}); });
const [openDeployModal, setOpenDeployModal] = useState<{ const [openDeployModal, setOpenDeployModal] = useState<{
show: boolean; show: boolean;
width: number | string; width: number | string;
source: SourceType; source: SourceType;
gpuOptions: any[];
modelFileOptions?: any[];
initialValues: any; initialValues: any;
isGGUF?: boolean; isGGUF?: boolean;
}>({ }>({
show: false, show: false,
width: 600, width: 600,
source: modelSourceMap.local_path_value as SourceType, source: modelSourceMap.local_path_value as SourceType,
gpuOptions: [],
modelFileOptions: [],
initialValues: {}, initialValues: {},
isGGUF: false isGGUF: false
}); });
useEffect(() => { useEffect(() => {
const fetchWorkerList = async () => { getWorkerOptionList();
try {
const res = await queryWorkersList({
page: 1,
perPage: 100
});
const list = res.items?.map((item: WorkerListItem) => {
return {
...item,
value: item.id,
label: item.name
};
});
setWorkersList(list);
} catch (error) {
// console.log('error', error);
}
};
fetchWorkerList();
}, []); }, []);
const extractFileName = (name: string) => { const extractFileName = (name: string) => {
@@ -440,16 +407,10 @@ const ModelFiles = () => {
showSuccess(); showSuccess();
} else if (val === 'deploy') { } else if (val === 'deploy') {
saveScrollHeight(); saveScrollHeight();
const [modelFileList, gpuList] = await Promise.all([
getModelFileList(), const initialValues = generateInitialValues(record, []);
getGPUList()
]);
const dataList = generateModelFileOptions(modelFileList, workersList);
const initialValues = generateInitialValues(record, gpuList);
setOpenDeployModal({ setOpenDeployModal({
...openDeployModal, ...openDeployModal,
modelFileOptions: dataList,
gpuOptions: gpuList,
initialValues: initialValues, initialValues: initialValues,
isGGUF: initialValues.isGGUF, isGGUF: initialValues.isGGUF,
show: true show: true
@@ -472,18 +433,15 @@ const ModelFiles = () => {
return <div></div>; return <div></div>;
}; };
const handleClickDropdown = useCallback( const handleClickDropdown = (item: any) => {
(item: any) => { const config = modalConfig[item.key];
const config = modalConfig[item.key]; const hasLinuxWorker = workersList.some(
const hasLinuxWorker = workersList.some( (worker) => _.toLower(worker.labels?.os) === 'linux'
(worker) => _.toLower(worker.labels?.os) === 'linux' );
); if (config) {
if (config) { setDownlaodMoalStatus({ ...config, hasLinuxWorker });
setDownlaodMoalStatus({ ...config, hasLinuxWorker, gpuOptions: [] }); }
} };
},
[workersList]
);
const handleDownloadCancel = () => { const handleDownloadCancel = () => {
setDownlaodMoalStatus({ setDownlaodMoalStatus({
@@ -709,6 +667,7 @@ const ModelFiles = () => {
width={downloadModalStatus.width} width={downloadModalStatus.width}
hasLinuxWorker={downloadModalStatus.hasLinuxWorker} hasLinuxWorker={downloadModalStatus.hasLinuxWorker}
workersList={readyWorkers} workersList={readyWorkers}
workerOptions={workerOptions}
></DownloadModal> ></DownloadModal>
<DeployModal <DeployModal
deploymentType="modelFiles" deploymentType="modelFiles"
@@ -719,10 +678,9 @@ const ModelFiles = () => {
action={PageAction.CREATE} action={PageAction.CREATE}
source={openDeployModal.source} source={openDeployModal.source}
width={openDeployModal.width} width={openDeployModal.width}
gpuOptions={openDeployModal.gpuOptions}
modelFileOptions={openDeployModal.modelFileOptions || []}
initialValues={openDeployModal.initialValues} initialValues={openDeployModal.initialValues}
isGGUF={openDeployModal.isGGUF} isGGUF={openDeployModal.isGGUF}
clusterList={clusterList}
></DeployModal> ></DeployModal>
</PageContainer> </PageContainer>
</> </>
+68 -27
View File
@@ -7,6 +7,7 @@ import ProgressBar from '@/components/progress-bar';
import InfoColumn from '@/components/simple-table/info-column'; import InfoColumn from '@/components/simple-table/info-column';
import StatusTag from '@/components/status-tag'; import StatusTag from '@/components/status-tag';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
import { queryClusterList } from '@/pages/cluster-management/apis';
import { convertFileSize } from '@/utils'; import { convertFileSize } from '@/utils';
import { import {
CodeOutlined, CodeOutlined,
@@ -18,7 +19,7 @@ import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { ConfigProvider, Empty, Table, Tooltip, message } from 'antd'; import { ConfigProvider, Empty, Table, Tooltip, message } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { useCallback, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import {
WORKERS_API, WORKERS_API,
deleteWorker, deleteWorker,
@@ -27,7 +28,6 @@ import {
} from '../apis'; } from '../apis';
import { WorkerStatusMapValue, status } from '../config'; import { WorkerStatusMapValue, status } from '../config';
import { Filesystem, GPUDeviceItem, ListItem } from '../config/types'; import { Filesystem, GPUDeviceItem, ListItem } from '../config/types';
import AddWorker from './add-worker';
import UpdateLabels from './update-labels'; import UpdateLabels from './update-labels';
const { Column } = Table; const { Column } = Table;
@@ -115,6 +115,7 @@ const Workers: React.FC = () => {
handlePageChange, handlePageChange,
handleTableChange, handleTableChange,
handleSearch, handleSearch,
handleQueryChange,
handleNameChange handleNameChange
} = useTableFetch<ListItem>({ } = useTableFetch<ListItem>({
fetchAPI: queryWorkersList, fetchAPI: queryWorkersList,
@@ -125,7 +126,6 @@ const Workers: React.FC = () => {
}); });
const intl = useIntl(); const intl = useIntl();
const [open, setOpen] = useState(false);
const [updateLabelsData, setUpdateLabelsData] = useState<{ const [updateLabelsData, setUpdateLabelsData] = useState<{
open: boolean; open: boolean;
data: ListItem; data: ListItem;
@@ -133,35 +133,65 @@ const Workers: React.FC = () => {
open: false, open: false,
data: {} as ListItem data: {} as ListItem
}); });
const [clusterData, setClusterData] = useState<{
list: Global.BaseOption<number>[];
data: Record<number, string>;
}>({
list: [],
data: {}
});
const handleAddWorker = () => { const getClusterList = async () => {
setOpen(true); try {
const params = {
page: 1,
perPage: 100
};
const res = await queryClusterList(params);
const clusterMap = res?.items?.reduce(
(acc: Record<number, string>, item: any) => {
acc[item.id] = item.name;
return acc;
},
{}
);
const list = res?.items?.map((item: any) => ({
label: item.name,
value: item.id
}));
setClusterData({
list,
data: clusterMap
});
} catch (error) {
setClusterData({
list: [],
data: {}
});
}
}; };
const handleUpdateLabelsOk = useCallback( const handleUpdateLabelsOk = async (values: Record<string, any>) => {
async (values: Record<string, any>) => { try {
try { console.log('updateLabelsData.data', updateLabelsData.data);
console.log('updateLabelsData.data', updateLabelsData.data); await updateWorker(updateLabelsData.data.id, {
await updateWorker(updateLabelsData.data.id, { ...updateLabelsData.data,
...updateLabelsData.data, labels: values.labels
labels: values.labels });
}); message.success(intl.formatMessage({ id: 'common.message.success' }));
message.success(intl.formatMessage({ id: 'common.message.success' })); fetchData();
fetchData(); setUpdateLabelsData({ open: false, data: {} as ListItem });
setUpdateLabelsData({ open: false, data: {} as ListItem }); } catch (error) {
} catch (error) { console.log('error', error);
console.log('error', error); }
} };
},
[updateLabelsData, fetchData]
);
const handleCancelUpdateLabels = useCallback(() => { const handleCancelUpdateLabels = () => {
setUpdateLabelsData({ setUpdateLabelsData({
...updateLabelsData, ...updateLabelsData,
open: false open: false
}); });
}, []); };
const handleUpdateLabels = (record: ListItem) => { const handleUpdateLabels = (record: ListItem) => {
console.log('record', record); console.log('record', record);
@@ -210,6 +240,17 @@ const Workers: React.FC = () => {
); );
}; };
const handleClusterChange = (value: number) => {
handleQueryChange({
page: 1,
cluster_id: value
});
};
useEffect(() => {
getClusterList();
}, []);
return ( return (
<> <>
<PageContainer <PageContainer
@@ -232,9 +273,10 @@ const Workers: React.FC = () => {
buttonText={intl.formatMessage({ id: 'resources.button.create' })} buttonText={intl.formatMessage({ id: 'resources.button.create' })}
handleDeleteByBatch={handleDeleteBatch} handleDeleteByBatch={handleDeleteBatch}
handleSearch={handleSearch} handleSearch={handleSearch}
handleClickPrimary={handleAddWorker} handleSelectChange={handleClusterChange}
handleInputChange={handleNameChange} handleInputChange={handleNameChange}
rowSelection={rowSelection} rowSelection={rowSelection}
selectOptions={clusterData.list}
width={{ input: 200 }} width={{ input: 200 }}
></FilterBar> ></FilterBar>
<ConfigProvider renderEmpty={renderEmpty}> <ConfigProvider renderEmpty={renderEmpty}>
@@ -309,7 +351,7 @@ const Workers: React.FC = () => {
render={(text, record: ListItem) => { render={(text, record: ListItem) => {
return ( return (
<AutoTooltip ghost maxWidth={240}> <AutoTooltip ghost maxWidth={240}>
<span>digital-ocean-cluster</span> <span>{clusterData.data[record.cluster_id]}</span>
</AutoTooltip> </AutoTooltip>
); );
}} }}
@@ -496,7 +538,6 @@ const Workers: React.FC = () => {
</Table> </Table>
</ConfigProvider> </ConfigProvider>
<DeleteModal ref={modalRef}></DeleteModal> <DeleteModal ref={modalRef}></DeleteModal>
<AddWorker open={open} onCancel={() => setOpen(false)}></AddWorker>
<UpdateLabels <UpdateLabels
open={updateLabelsData.open} open={updateLabelsData.open}
onOk={handleUpdateLabelsOk} onOk={handleUpdateLabelsOk}
+1
View File
@@ -64,6 +64,7 @@ export interface ListItem {
labels: Record<string, string>; labels: Record<string, string>;
state: string; state: string;
ip: string; ip: string;
cluster_id: number;
state_message: string; state_message: string;
status: { status: {
cpu: { cpu: {