From 89001dbb925c8fdd4d678ce9c308392b165bb93f Mon Sep 17 00:00:00 2001 From: jialin Date: Fri, 25 Jul 2025 10:31:36 +0800 Subject: [PATCH] feat: add cluster list --- src/components/icon-font/icons.ts | 2 + src/components/icon-font/index.tsx | 2 +- src/components/page-tools/index.tsx | 62 +-- src/pages/cluster-management/apis/index.ts | 31 ++ src/pages/cluster-management/clusters.tsx | 456 +++++++++++++++++- .../components/add-cluster.tsx | 175 +++++++ .../components/add-credential.tsx | 135 ++++++ src/pages/cluster-management/config/index.ts | 29 ++ src/pages/cluster-management/config/types.ts | 40 ++ src/pages/cluster-management/credentials.tsx | 353 +++++++++++++- .../llmodels/components/advance-config.tsx | 89 +++- src/pages/llmodels/components/data-form.tsx | 14 +- .../llmodels/components/deploy-modal.tsx | 13 +- src/pages/llmodels/components/gguf-result.tsx | 20 + .../llmodels/components/instance-item.tsx | 7 + src/pages/llmodels/components/performance.tsx | 137 ++++++ src/pages/llmodels/components/scaling.tsx | 157 ++++++ .../llmodels/components/search-model.tsx | 10 +- src/pages/llmodels/components/table-list.tsx | 26 +- src/pages/llmodels/config/button-actions.ts | 5 + src/pages/llmodels/config/form-context.ts | 1 + src/pages/llmodels/forms/hugging-face.tsx | 6 +- src/pages/resources/components/add-worker.tsx | 10 +- src/pages/resources/components/workers.tsx | 25 +- 24 files changed, 1695 insertions(+), 110 deletions(-) create mode 100644 src/pages/cluster-management/apis/index.ts create mode 100644 src/pages/cluster-management/components/add-cluster.tsx create mode 100644 src/pages/cluster-management/components/add-credential.tsx create mode 100644 src/pages/cluster-management/config/index.ts create mode 100644 src/pages/cluster-management/config/types.ts create mode 100644 src/pages/llmodels/components/gguf-result.tsx create mode 100644 src/pages/llmodels/components/performance.tsx create mode 100644 src/pages/llmodels/components/scaling.tsx diff --git a/src/components/icon-font/icons.ts b/src/components/icon-font/icons.ts index 541678e6..96436c47 100644 --- a/src/components/icon-font/icons.ts +++ b/src/components/icon-font/icons.ts @@ -5,6 +5,7 @@ import { DownloadOutlined, EditOutlined, ExperimentOutlined, + FileTextOutlined, RetweetOutlined, ThunderboltOutlined } from '@ant-design/icons'; @@ -17,6 +18,7 @@ const icons = { ThunderboltOutlined: React.createElement(ThunderboltOutlined), RetweetOutlined: React.createElement(RetweetOutlined), DownloadOutlined: React.createElement(DownloadOutlined), + FileTextOutlined: React.createElement(FileTextOutlined), ApiOutlined: React.createElement(ApiOutlined), Stop: React.createElement(IconFont, { type: 'icon-stop1' }), Play: React.createElement(IconFont, { type: 'icon-outline-play' }), diff --git a/src/components/icon-font/index.tsx b/src/components/icon-font/index.tsx index 1ff34042..61a42720 100644 --- a/src/components/icon-font/index.tsx +++ b/src/components/icon-font/index.tsx @@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons'; // import './iconfont/iconfont.js'; const IconFont = createFromIconfontCN({ - scriptUrl: '//at.alicdn.com/t/c/font_4613488_oxxxb51iqbp.js' + scriptUrl: '//at.alicdn.com/t/c/font_4613488_qgb5xlexehp.js' }); export default IconFont; diff --git a/src/components/page-tools/index.tsx b/src/components/page-tools/index.tsx index c2fbde87..1d9ca313 100644 --- a/src/components/page-tools/index.tsx +++ b/src/components/page-tools/index.tsx @@ -94,7 +94,7 @@ export const FilterBar: React.FC = (props) => { marginBottom = 10, marginTop = 10, inputHolder = 'common.filter.name', - selectHolder, + selectHolder = '', showPrimaryButton = true, showDeleteButton = true, width @@ -107,43 +107,47 @@ export const FilterBar: React.FC = (props) => { } return ( - {actionType === 'dropdown' ? ( - + {showPrimaryButton ? ( + actionType === 'dropdown' ? ( + + + + ) : ( - - ) : ( + ) + ) : null} + {showDeleteButton && ( )} - ); }, [ diff --git a/src/pages/cluster-management/apis/index.ts b/src/pages/cluster-management/apis/index.ts new file mode 100644 index 00000000..502ebde1 --- /dev/null +++ b/src/pages/cluster-management/apis/index.ts @@ -0,0 +1,31 @@ +import { request } from '@umijs/max'; +import { FormData } from '../config/types'; + +export const CREDENTIALS_API = '/credentials'; + +export async function queryCredentialList(params: Global.SearchParams) { + // return request>(`${CREDENTIALS_API}`, { + // method: 'GET', + // params + // }); +} + +export async function createCredential(params: { data: FormData }) { + return request(`${CREDENTIALS_API}`, { + method: 'POST', + data: params.data + }); +} + +export async function updateCredential(params: { data: FormData }) { + return request(`${CREDENTIALS_API}/${params.data.id}`, { + method: 'PUT', + data: params.data + }); +} + +export async function deleteCredential(id: number) { + return request(`${CREDENTIALS_API}/${id}`, { + method: 'DELETE' + }); +} diff --git a/src/pages/cluster-management/clusters.tsx b/src/pages/cluster-management/clusters.tsx index a7712a23..7a384cb5 100644 --- a/src/pages/cluster-management/clusters.tsx +++ b/src/pages/cluster-management/clusters.tsx @@ -1,25 +1,449 @@ +import AutoTooltip from '@/components/auto-tooltip'; +import DeleteModal from '@/components/delete-modal'; +import DropDownActions from '@/components/drop-down-actions'; +import DropdownButtons from '@/components/drop-down-buttons'; +import PageTools from '@/components/page-tools'; +import { PageAction } from '@/config'; +import type { PageActionType } from '@/config/types'; +import useTableFetch from '@/hooks/use-table-fetch'; +import AddWorker from '@/pages/resources/components/add-worker'; +import { + DeleteOutlined, + DownOutlined, + EditOutlined, + KubernetesOutlined, + ProfileOutlined, + SnippetsOutlined, + SyncOutlined +} from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; import { useIntl } from '@umijs/max'; -import React from 'react'; +import { + Button, + ConfigProvider, + Empty, + Input, + Space, + Table, + message +} from 'antd'; +import { useState } from 'react'; +import styled from 'styled-components'; +import { + createCredential, + deleteCredential, + queryCredentialList, + updateCredential +} from './apis'; +import AddCluster from './components/add-cluster'; +import { ClusterDataList } from './config'; +import { + ClusterFormData as FormData, + ClusterListItem as ListItem +} from './config/types'; +const { Column } = Table; + +const addActions = [ + { + label: 'Custom', + locale: false, + value: 'custom', + key: 'custom', + icon: + }, + { + label: 'Kubernetes', + locale: false, + value: 'kubernetes', + key: 'kubernetes', + icon: + } +]; + +const WorkerWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + align-items: flex-start; + .worker { + display: flex; + align-items: center; + gap: 5px; + .value { + line-height: 1em; + color: var(--ant-color-text-secondary); + } + } + .dot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 4px; + &.ready { + background-color: var(--ant-color-success); + } + &.error { + background-color: var(--ant-color-error); + } + + &.transition { + background-color: var(--ant-blue-5); + } + } +`; + +const ActionList = [ + { + key: 'edit', + label: 'common.button.edit', + icon: + }, + { + key: 'add', + label: 'Add Worker', + locale: false, + icon: + }, + { + key: 'terminal', + label: 'common.button.detail', + icon: + }, + { + key: 'delete', + props: { + danger: true + }, + label: 'common.button.delete', + icon: + } +]; + +const Credentials: React.FC = () => { + const { + dataSource, + rowSelection, + queryParams, + sortOrder, + modalRef, + handleDelete, + handleDeleteBatch, + fetchData, + handlePageChange, + handleTableChange, + handleSearch, + handleNameChange + } = useTableFetch({ + fetchAPI: queryCredentialList, + deleteAPI: deleteCredential, + contentForDelete: 'users.table.user' + }); -const Cluster: React.FC = () => { const intl = useIntl(); + const [open, setOpen] = useState(false); + const [openAddModal, setOpenAddModal] = useState(false); + const [clusterType, setClusterType] = useState('custom'); + const [action, setAction] = useState(PageAction.CREATE); + const [title, setTitle] = useState(''); + const [currentData, setCurrentData] = useState( + undefined + ); + + const handleAddUser = (value: string) => { + setOpenAddModal(true); + setAction(PageAction.CREATE); + setClusterType(value); + setTitle('Add Cluster'); + }; + + const handleClickDropdown = (item: any) => { + console.log('handleClickDropdown', item); + handleAddUser(item.key); + }; + + const handleModalOk = async (data: FormData) => { + const params = { + ...data + }; + try { + if (action === PageAction.EDIT) { + await updateCredential({ + data: { + ...params, + id: currentData?.id + } + }); + } else { + await createCredential({ data: params }); + } + fetchData(); + setOpenAddModal(false); + message.success(intl.formatMessage({ id: 'common.message.success' })); + } catch (error) { + setOpenAddModal(false); + } + }; + + const handleModalCancel = () => { + console.log('handleModalCancel'); + setOpenAddModal(false); + }; + + const handleEditUser = (row: ListItem) => { + setCurrentData(row); + setOpenAddModal(true); + setAction(PageAction.EDIT); + setTitle('Edit Cluster'); + }; + + const handleSelect = (val: any, row: ListItem) => { + if (val === 'edit') { + handleEditUser(row); + } else if (val === 'delete') { + handleDelete({ ...row, name: row.name }); + } else if (val === 'add') { + setOpen(true); + setCurrentData(row); + } + }; + + const renderEmpty = (type?: string) => { + if (type !== 'Table') return; + if ( + !dataSource.loading && + dataSource.loadend && + !dataSource.dataList.length + ) { + return ; + } + return
; + }; return ( - - {intl.formatMessage({ id: 'menu.clusterManagement.clusters' })} - + <> + + + + + + } + right={ + + + + + + + } + > + + + { + return ( + + {text} + + ); + }} + /> + { + return ( + + {text} + + ); + }} + /> + { + return ( + + + + 3 + + + + 1 + + + ); + }} + /> + { + return ( + + {text} + + ); + }} + /> + + { + return ( + + {text} + + ); + }} + /> + { + return ( + + + + 3 + + + + 1 + + + + 1 + + + ); + }} + /> + + { + return ( + handleSelect(val, record)} + > + ); + }} + /> +
+
+
+ + setOpen(false)}> + + ); }; -export default Cluster; +export default Credentials; diff --git a/src/pages/cluster-management/components/add-cluster.tsx b/src/pages/cluster-management/components/add-cluster.tsx new file mode 100644 index 00000000..b37cb0e1 --- /dev/null +++ b/src/pages/cluster-management/components/add-cluster.tsx @@ -0,0 +1,175 @@ +import ModalFooter from '@/components/modal-footer'; +import GSDrawer from '@/components/scroller-modal/gs-drawer'; +import SealInput from '@/components/seal-form/seal-input'; +import SealSelect from '@/components/seal-form/seal-select'; +import { PageActionType } from '@/config/types'; +import { useIntl } from '@umijs/max'; +import { Form } from 'antd'; +import React from 'react'; +import { + ClusterFormData as FormData, + ClusterListItem as ListItem +} from '../config/types'; + +type AddModalProps = { + title: string; + action: PageActionType; + open: boolean; + clusterType: string; // 'kubernetes' | 'custom' + onOk: (values: FormData) => void; + data?: ListItem; + onCancel: () => void; +}; +const AddCluster: React.FC = ({ + title, + action, + open, + clusterType, + onOk, + data, + onCancel +}) => { + const [form] = Form.useForm(); + const intl = useIntl(); + + const handleSumit = () => { + form.submit(); + }; + + return ( + + } + > +
+ + name="name" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: intl.formatMessage({ id: 'common.table.name' }) + } + ) + } + ]} + > + + + {clusterType === 'kubernetes' && ( + <> + {' '} + + name="provider" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: 'Provider' + } + ) + } + ]} + > + ({ + label: item, + value: item + }))} + > + + + name="region" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: 'Region' + } + ) + } + ]} + > + ({ + label: item, + value: item + }))} + > + + + )} + + name="gpuPlan" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: 'GPU Plan' + } + ) + } + ]} + > + ({ + label: item, + value: item + }))} + > + + + name="workers" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: 'Workers' + } + ) + } + ]} + > + + + + name="description" rules={[{ required: false }]}> + + + +
+ ); +}; + +export default AddCluster; diff --git a/src/pages/cluster-management/components/add-credential.tsx b/src/pages/cluster-management/components/add-credential.tsx new file mode 100644 index 00000000..0aed23ff --- /dev/null +++ b/src/pages/cluster-management/components/add-credential.tsx @@ -0,0 +1,135 @@ +import ModalFooter from '@/components/modal-footer'; +import ScrollerModal from '@/components/scroller-modal'; +import SealInput from '@/components/seal-form/seal-input'; +import SealSelect from '@/components/seal-form/seal-select'; +import { PageAction, PasswordReg } from '@/config'; +import { PageActionType } from '@/config/types'; +import { useIntl } from '@umijs/max'; +import { Form } from 'antd'; +import React from 'react'; +import { FormData, ListItem } from '../config/types'; + +type AddModalProps = { + title: string; + action: PageActionType; + open: boolean; + onOk: (values: FormData) => void; + data?: ListItem; + onCancel: () => void; +}; +const AddModal: React.FC = ({ + title, + action, + open, + onOk, + data, + onCancel +}) => { + const [form] = Form.useForm(); + const intl = useIntl(); + + const handleSumit = () => { + form.submit(); + }; + + return ( + + } + > +
+ + name="name" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: intl.formatMessage({ id: 'common.table.name' }) + } + ) + } + ]} + > + + + + name="provider" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: 'Provider' + } + ) + } + ]} + > + ({ + label: item, + value: item + }))} + > + + + name="access_key" + rules={[ + { + required: action === PageAction.CREATE, + pattern: PasswordReg, + message: intl.formatMessage({ id: 'users.form.rule.password' }) + } + ]} + > + + + + name="secret_key" + rules={[ + { + required: action === PageAction.CREATE, + pattern: PasswordReg, + message: intl.formatMessage({ id: 'users.form.rule.password' }) + } + ]} + > + + + name="description" rules={[{ required: false }]}> + + + +
+ ); +}; + +export default AddModal; diff --git a/src/pages/cluster-management/config/index.ts b/src/pages/cluster-management/config/index.ts new file mode 100644 index 00000000..e6f704dc --- /dev/null +++ b/src/pages/cluster-management/config/index.ts @@ -0,0 +1,29 @@ +export const ClusterDataList = [ + { + id: 1, + name: 'custom-cluster', + provider: 'Custom', + clusterType: 'Kubernetes', + workers: 2, + gpus: 4, + deployments: 1 + }, + { + id: 2, + name: 'Digital-Ocean-cluster', + provider: 'Digital Ocean', + clusterType: 'Custom', + workers: 3, + gpus: 6, + deployments: 2 + }, + { + id: 3, + name: 'AutoDL-cluster', + provider: 'AutoDL', + clusterType: 'Custom', + workers: 4, + gpus: 8, + deployments: 3 + } +]; diff --git a/src/pages/cluster-management/config/types.ts b/src/pages/cluster-management/config/types.ts new file mode 100644 index 00000000..707a2ab7 --- /dev/null +++ b/src/pages/cluster-management/config/types.ts @@ -0,0 +1,40 @@ +export interface FormData { + name: string; + provider: string; + access_key: string; + secret_key: string; + description?: string; + id?: number; +} + +export interface ListItem { + id: number; + name: string; + provider: string; + access_key: string; + secret_key: string; + description?: string; + created_at: string; + updated_at: string; +} + +export interface ClusterListItem { + name: string; + provider: string; + workers: number; + created_at: string; + gpus: number; + deployments: number; + id: number; +} + +export interface ClusterFormData { + name: string; + provider: string; + credential: string; + region: string; + workers: number; + gpuPlan: string; + description?: string; + id?: number; +} diff --git a/src/pages/cluster-management/credentials.tsx b/src/pages/cluster-management/credentials.tsx index ca59c65c..85f30599 100644 --- a/src/pages/cluster-management/credentials.tsx +++ b/src/pages/cluster-management/credentials.tsx @@ -1,24 +1,345 @@ +import AutoTooltip from '@/components/auto-tooltip'; +import DeleteModal from '@/components/delete-modal'; +import DropDownActions from '@/components/drop-down-actions'; +import DropdownButtons from '@/components/drop-down-buttons'; +import PageTools from '@/components/page-tools'; +import { PageAction } from '@/config'; +import type { PageActionType } from '@/config/types'; +import useTableFetch from '@/hooks/use-table-fetch'; +import { + CloudOutlined, + DeleteOutlined, + DownOutlined, + EditOutlined, + KeyOutlined, + SyncOutlined +} from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; import { useIntl } from '@umijs/max'; -import React from 'react'; +import { + Button, + ConfigProvider, + Empty, + Input, + Space, + Table, + message +} from 'antd'; +import dayjs from 'dayjs'; +import { useState } from 'react'; +import { + createCredential, + deleteCredential, + queryCredentialList, + updateCredential +} from './apis'; +import AddModal from './components/add-credential'; +import { FormData, ListItem } from './config/types'; +const { Column } = Table; + +const ActionList = [ + { + key: 'edit', + label: 'common.button.edit', + icon: + }, + { + key: 'delete', + props: { + danger: true + }, + label: 'common.button.delete', + icon: + } +]; + +const addActions = [ + { + label: 'Cloud Credential', + locale: false, + value: 'cloud_credential', + key: 'cloud_credential', + icon: + }, + { + label: 'SSH Key', + locale: false, + value: 'ssh_key', + key: 'ssh_key', + icon: + } +]; + +const Credentials: React.FC = () => { + const { + dataSource, + rowSelection, + queryParams, + sortOrder, + modalRef, + handleDelete, + handleDeleteBatch, + fetchData, + handlePageChange, + handleTableChange, + handleSearch, + handleNameChange + } = useTableFetch({ + fetchAPI: queryCredentialList, + deleteAPI: deleteCredential, + contentForDelete: 'users.table.user' + }); -const Credential: React.FC = () => { const intl = useIntl(); + const [openAddModal, setOpenAddModal] = useState(false); + + const [action, setAction] = useState(PageAction.CREATE); + const [title, setTitle] = useState(''); + const [currentData, setCurrentData] = useState( + undefined + ); + + const handleAddUser = () => { + setOpenAddModal(true); + setAction(PageAction.CREATE); + setTitle('Add Credential'); + }; + + const handleModalOk = async (data: FormData) => { + const params = { + ...data + }; + try { + if (action === PageAction.EDIT) { + await updateCredential({ + data: { + ...params, + id: currentData?.id + } + }); + } else { + await createCredential({ data: params }); + } + fetchData(); + setOpenAddModal(false); + message.success(intl.formatMessage({ id: 'common.message.success' })); + } catch (error) { + setOpenAddModal(false); + } + }; + + const handleModalCancel = () => { + console.log('handleModalCancel'); + setOpenAddModal(false); + }; + + const handleEditUser = (row: ListItem) => { + setCurrentData(row); + setOpenAddModal(true); + setAction(PageAction.EDIT); + setTitle('Edit Credential'); + }; + + const handleSelect = (val: any, row: ListItem) => { + if (val === 'edit') { + handleEditUser(row); + } else if (val === 'delete') { + handleDelete({ ...row, name: row.name }); + } + }; + + const renderEmpty = (type?: string) => { + if (type !== 'Table') return; + if ( + !dataSource.loading && + dataSource.loadend && + !dataSource.dataList.length + ) { + return ; + } + return
; + }; + + const handleClickDropdown = (e: any) => { + handleAddUser(); + }; + return ( - - {intl.formatMessage({ id: 'menu.clusterManagement.credentials' })} - + <> + + + + + + } + right={ + + + + + + + } + > + + + { + return ( + + {text} + + ); + }} + /> + { + return ( + + {text} + + ); + }} + /> + { + return ( + + {dayjs(text).format('YYYY-MM-DD HH:mm:ss')} + + ); + }} + /> + { + return ( + + {text} + + ); + }} + /> + { + return ( + handleSelect(val, record)} + > + ); + }} + /> +
+
+
+ + + ); }; -export default Credential; +export default Credentials; diff --git a/src/pages/llmodels/components/advance-config.tsx b/src/pages/llmodels/components/advance-config.tsx index 8940c62a..a1062e15 100644 --- a/src/pages/llmodels/components/advance-config.tsx +++ b/src/pages/llmodels/components/advance-config.tsx @@ -5,6 +5,7 @@ import SealCascader from '@/components/seal-form/seal-cascader'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import TooltipList from '@/components/tooltip-list'; +import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import useAppUtils from '@/hooks/use-app-utils'; import { QuestionCircleOutlined } from '@ant-design/icons'; @@ -24,8 +25,10 @@ import { backendLabelMap, backendOptionsMap, backendParamsHolderTips, + backendTipsList, getBackendParamsTips, modelCategories, + modelSourceMap, placementStrategyOptions } from '../config'; import { useFormContext } from '../config/form-context'; @@ -35,6 +38,8 @@ import { FormData } from '../config/types'; import vllmConfig from '../config/vllm-config'; import dataformStyles from '../style/data-form.less'; import GPUCard from './gpu-card'; +import Performance from './performance'; +import Scaling from './scaling'; interface AdvanceConfigProps { isGGUF: boolean; @@ -42,6 +47,8 @@ interface AdvanceConfigProps { gpuOptions: Array; action: PageActionType; source: string; + backendOptions?: Global.BaseOption[]; + handleBackendChange?: (value: string) => void; } const placementStrategyTips = [ @@ -92,7 +99,7 @@ const CheckboxField: React.FC<{ }; const AdvanceConfig: React.FC = (props) => { - const { form, isGGUF, gpuOptions, source } = props; + const { form, isGGUF, gpuOptions, source, backendOptions, action } = props; const { getRuleMessage } = useAppUtils(); const intl = useIntl(); const wokerSelector = Form.useWatch('worker_selector', form); @@ -205,7 +212,56 @@ const AdvanceConfig: React.FC = (props) => { options={modelCategories} > - + + } + options={ + backendOptions ?? [ + { + label: backendLabelMap[backendOptionsMap.llamaBox], + value: backendOptionsMap.llamaBox, + disabled: + props.source === modelSourceMap.local_path_value + ? false + : !isGGUF + }, + { + label: backendLabelMap[backendOptionsMap.vllm], + value: backendOptionsMap.vllm, + disabled: + props.source === modelSourceMap.local_path_value + ? false + : isGGUF + }, + { + label: backendLabelMap[backendOptionsMap.ascendMindie], + value: backendOptionsMap.ascendMindie, + disabled: + props.source === modelSourceMap.local_path_value + ? false + : isGGUF + }, + { + label: backendLabelMap[backendOptionsMap.voxBox], + value: backendOptionsMap.voxBox, + disabled: + props.source === modelSourceMap.local_path_value + ? false + : props.source === modelSourceMap.ollama_library_value || + isGGUF + } + ] + } + disabled={ + action === PageAction.EDIT && + props.source !== modelSourceMap.local_path_value + } + > + + {/* = (props) => { } ]} > - + */} {scheduleType === 'auto' && ( <> name="placement_strategy"> @@ -489,6 +545,32 @@ const AdvanceConfig: React.FC = (props) => { ); return [ + { + key: '2', + label: ( + + Performance + + ), + forceRender: true, + children: + }, + { + key: '3', + label: ( + + Scaling + + ), + forceRender: true, + children: + }, { key: '1', label: ( @@ -527,6 +609,7 @@ const AdvanceConfig: React.FC = (props) => { expandIconPosition="start" bordered={false} ghost + accordion destroyInactivePanel={false} className={dataformStyles['advanced-collapse']} expandIcon={({ isActive }) => ( diff --git a/src/pages/llmodels/components/data-form.tsx b/src/pages/llmodels/components/data-form.tsx index 7fb05715..87d221d4 100644 --- a/src/pages/llmodels/components/data-form.tsx +++ b/src/pages/llmodels/components/data-form.tsx @@ -1,7 +1,5 @@ import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; -import TooltipList from '@/components/tooltip-list'; -import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import useAppUtils from '@/hooks/use-app-utils'; import { useIntl } from '@umijs/max'; @@ -9,9 +7,7 @@ import { Form } from 'antd'; import _ from 'lodash'; import React, { forwardRef, useImperativeHandle } from 'react'; import { - backendLabelMap, backendOptionsMap, - backendTipsList, excludeFields, modelSourceMap, sourceOptions @@ -254,7 +250,7 @@ const DataForm: React.FC = forwardRef((props, ref) => { - + {/* = forwardRef((props, ref) => { props.source !== modelSourceMap.local_path_value } > - + */} - + {/* name="replicas" rules={[ { @@ -325,7 +321,7 @@ const DataForm: React.FC = forwardRef((props, ref) => { )} min={0} > - + */} name="description"> = forwardRef((props, ref) => { isGGUF={isGGUF} action={action} source={props.source} + backendOptions={backendOptions} + handleBackendChange={handleBackendChange} > ); diff --git a/src/pages/llmodels/components/deploy-modal.tsx b/src/pages/llmodels/components/deploy-modal.tsx index 94f8baa4..0b21d5d4 100644 --- a/src/pages/llmodels/components/deploy-modal.tsx +++ b/src/pages/llmodels/components/deploy-modal.tsx @@ -27,7 +27,7 @@ import { import ColumnWrapper from './column-wrapper'; import CompatibilityAlert from './compatible-alert'; import DataForm from './data-form'; -import HFModelFile from './hf-model-file'; +import GGUFResult from './gguf-result'; import ModelCard from './model-card'; import SearchModel from './search-model'; import Separator from './separator'; @@ -556,16 +556,7 @@ const AddModal: FC = (props) => { modelSource={props.source} setIsGGUF={handleSetIsGGUF} > - {isGGUF && ( - - )} + {isGGUF && } diff --git a/src/pages/llmodels/components/gguf-result.tsx b/src/pages/llmodels/components/gguf-result.tsx new file mode 100644 index 00000000..d45b7b4a --- /dev/null +++ b/src/pages/llmodels/components/gguf-result.tsx @@ -0,0 +1,20 @@ +import { WarningOutlined } from '@ant-design/icons'; +import { Result } from 'antd'; +import React from 'react'; + +const GGUFResult: React.FC = () => { + return ( + + } + title={false} + subTitle="GGUF model is not supported yet." + /> + ); +}; + +export default GGUFResult; diff --git a/src/pages/llmodels/components/instance-item.tsx b/src/pages/llmodels/components/instance-item.tsx index 79f49e64..cc30162f 100644 --- a/src/pages/llmodels/components/instance-item.tsx +++ b/src/pages/llmodels/components/instance-item.tsx @@ -12,6 +12,7 @@ import useDownloadStream from '@/hooks/use-download-stream'; import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; import { convertFileSize } from '@/utils'; import { + CodeOutlined, DeleteOutlined, DownloadOutlined, HddFilled, @@ -270,6 +271,12 @@ const childActionList = [ ], icon: }, + { + label: 'Terminal', + locale: false, + key: 'terminal', + icon: + }, { label: 'common.button.downloadLog', key: 'download', diff --git a/src/pages/llmodels/components/performance.tsx b/src/pages/llmodels/components/performance.tsx new file mode 100644 index 00000000..956f10bb --- /dev/null +++ b/src/pages/llmodels/components/performance.tsx @@ -0,0 +1,137 @@ +import SealSelect from '@/components/seal-form/seal-select'; +import TooltipList from '@/components/tooltip-list'; +import useAppUtils from '@/hooks/use-app-utils'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Checkbox, Form, Tooltip } from 'antd'; +import { CheckboxChangeEvent } from 'antd/es/checkbox'; +import React from 'react'; +import { useFormContext } from '../config/form-context'; + +const scheduleTypeTips = [ + { + title: { + text: 'models.form.scheduletype.auto', + locale: true + }, + tips: 'models.form.scheduletype.auto.tips' + }, + { + title: { + text: 'models.form.scheduletype.manual', + locale: true + }, + tips: 'models.form.scheduletype.manual.tips' + } +]; + +const CheckboxField: React.FC<{ + title: string; + label: string; + checked?: boolean; + onChange?: (e: CheckboxChangeEvent) => void; +}> = ({ title, label, checked, onChange }) => { + return ( + + + {label} + + + + ); +}; + +const Performance: React.FC = () => { + const intl = useIntl(); + const { onValuesChange, onQuantizationChange, source, quantizationOptions } = + useFormContext(); + const { getRuleMessage } = useAppUtils(); + const form = Form.useFormInstance(); + + const handleScheduleTypeChange = (value: string) => { + if (value === 'auto') { + onValuesChange?.({}, form.getFieldsValue()); + } + }; + + const handleOnQuantizationChange = (val: any) => { + onQuantizationChange?.(val); + }; + + return ( + <> + + } + options={[ + { + label: intl.formatMessage({ + id: 'models.form.scheduletype.auto' + }), + value: 'auto' + }, + { + label: intl.formatMessage({ + id: 'models.form.scheduletype.manual' + }), + value: 'manual' + } + ]} + > + + + name="quantization" + key="quantization" + rules={[ + { + required: true, + message: getRuleMessage('select', 'quantization', false) + } + ]} + > + + +
+ + name="optimize_long_prompt" + valuePropName="checked" + style={{ padding: '0 10px', marginBottom: 0 }} + noStyle + > + + +
+
+ + name="enable_speculative_decoding" + valuePropName="checked" + style={{ padding: '0 10px', marginBottom: 0 }} + noStyle + > + + +
+ + ); +}; + +export default Performance; diff --git a/src/pages/llmodels/components/scaling.tsx b/src/pages/llmodels/components/scaling.tsx new file mode 100644 index 00000000..39063308 --- /dev/null +++ b/src/pages/llmodels/components/scaling.tsx @@ -0,0 +1,157 @@ +import SealInputNumber from '@/components/seal-form/input-number'; +import useAppUtils from '@/hooks/use-app-utils'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Checkbox, Form, Tooltip } from 'antd'; +import { CheckboxChangeEvent } from 'antd/es/checkbox'; +import React from 'react'; +import { useFormContext } from '../config/form-context'; + +const scheduleTypeTips = [ + { + title: { + text: 'models.form.scheduletype.auto', + locale: true + }, + tips: 'models.form.scheduletype.auto.tips' + }, + { + title: { + text: 'models.form.scheduletype.manual', + locale: true + }, + tips: 'models.form.scheduletype.manual.tips' + } +]; + +const CheckboxField: React.FC<{ + title: string; + label: string; + checked?: boolean; + onChange?: (e: CheckboxChangeEvent) => void; +}> = ({ title, label, checked, onChange }) => { + return ( + + + {label} + + + + ); +}; + +const Scaling: React.FC = () => { + const intl = useIntl(); + const { onValuesChange, onQuantizationChange, source, quantizationOptions } = + useFormContext(); + const { getRuleMessage } = useAppUtils(); + const form = Form.useFormInstance(); + + const handleScheduleTypeChange = (value: string) => { + if (value === 'auto') { + onValuesChange?.({}, form.getFieldsValue()); + } + }; + + const handleOnQuantizationChange = (val: any) => { + onQuantizationChange?.(val); + }; + + return ( + <> +
+ + name="enable_auto_scaling" + valuePropName="checked" + style={{ padding: '0 10px', marginBottom: 0 }} + noStyle + > + + +
+ + name="min_replicas" + rules={[ + { + required: true + } + ]} + > + + + + name="max_replicas" + rules={[ + { + required: true + } + ]} + > + + + + name="scale_to_zero_window" + rules={[ + { + required: true + } + ]} + > + + + + name="scale_down_window" + rules={[ + { + required: true + } + ]} + > + + + + name="scale_up_window" + rules={[ + { + required: true + } + ]} + > + + + + ); +}; + +export default Scaling; diff --git a/src/pages/llmodels/components/search-model.tsx b/src/pages/llmodels/components/search-model.tsx index 22a59ca7..74142bbb 100644 --- a/src/pages/llmodels/components/search-model.tsx +++ b/src/pages/llmodels/components/search-model.tsx @@ -2,7 +2,7 @@ import { getRequestId, setRquestId } from '@/atoms/models'; import { createAxiosToken } from '@/hooks/use-chunk-request'; import { QuestionCircleOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Checkbox, Pagination, Select, Tooltip } from 'antd'; +import { Pagination, Select, Tooltip } from 'antd'; import _ from 'lodash'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; @@ -167,7 +167,7 @@ const SearchModel: React.FC = (props) => { search: { query: searchInputRef.current || '', sort: sort, - tags: filterGGUFRef.current ? ['gguf'] : [], + tags: [], task: HuggingFaceTaskMap[filterTaskRef.current] || task } }; @@ -199,7 +199,7 @@ const SearchModel: React.FC = (props) => { try { const params = { Name: `${searchInputRef.current}`, - tags: filterGGUFRef.current ? ['gguf'] : [], + tags: [], tasks: filterTaskRef.current ? ([ModelscopeTaskMap[filterTaskRef.current]] as string[]) : [], @@ -562,13 +562,13 @@ const SearchModel: React.FC = (props) => { size="middle" style={{ width: '150px' }} > - {renderGGUFTips} - + */} = ({ dataIndex: 'name', key: 'name', width: 400, - span: 6, + span: 5, render: (text: string, record: ListItem) => ( @@ -616,11 +616,22 @@ const Models: React.FC = ({ ) }, + { + title: 'Cluster', + dataIndex: 'cluster', + key: 'cluster', + span: 4, + render: (text: string, record: ListItem) => ( + + Custom + + ) + }, { title: intl.formatMessage({ id: 'models.form.source' }), dataIndex: 'source', key: 'source', - span: 7, + span: 4, render: (text: string, record: ListItem) => ( {generateSource(record)} @@ -767,7 +778,7 @@ const Models: React.FC = ({ = ({ onChange={handleCategoryChange} options={modelCategories.filter((item) => item.value)} > +