diff --git a/src/assets/styles/common.less b/src/assets/styles/common.less index 8b44ff2b..0d6af475 100644 --- a/src/assets/styles/common.less +++ b/src/assets/styles/common.less @@ -104,6 +104,11 @@ justify-content: space-between; } +.justify-between { + display: flex; + justify-content: space-between; +} + .justify-center { display: flex; justify-content: center; diff --git a/src/components/echarts/gauge.tsx b/src/components/echarts/gauge.tsx index ae84c10b..7146a856 100644 --- a/src/components/echarts/gauge.tsx +++ b/src/components/echarts/gauge.tsx @@ -4,6 +4,16 @@ import EmptyData from '@/components/empty-data'; import React, { memo } from 'react'; import { ChartProps } from './types'; +const strokeColorFunc = (percent: number) => { + if (percent <= 50 || percent === undefined) { + return 'rgb(84, 204, 152, 80%)'; + } + if (percent <= 80) { + return 'rgba(250, 173, 20, 80%)'; + } + return 'rgba(255, 77, 79, 80%)'; +}; + const GaugeChart: React.FC> = ( props ) => { @@ -18,6 +28,7 @@ const GaugeChart: React.FC> = ( } const setDataOptions = () => { + const colorValue = color || strokeColorFunc(value); return { title: { ...titleConfig, @@ -33,7 +44,7 @@ const GaugeChart: React.FC> = ( lineStyle: { ...gaugeItemConfig.axisLine.lineStyle, color: [ - [value / 100, color], + [value / 100, colorValue], [1, chartColorMap.gaugeBgColor] ] } diff --git a/src/components/seal-form/checkbox-field.tsx b/src/components/seal-form/checkbox-field.tsx new file mode 100644 index 00000000..b215fef2 --- /dev/null +++ b/src/components/seal-form/checkbox-field.tsx @@ -0,0 +1,27 @@ +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { Checkbox, Tooltip } from 'antd'; +import { CheckboxChangeEvent } from 'antd/es/checkbox'; +import React from 'react'; + +const CheckboxField: React.FC<{ + description?: React.ReactNode; + label: React.ReactNode; + checked?: boolean; + onChange?: (e: CheckboxChangeEvent) => void; +}> = ({ description, label, checked, onChange }) => { + return ( + + + {label} + {!!description && ( + + )} + + + ); +}; + +export default CheckboxField; diff --git a/src/components/templates/card-list.tsx b/src/components/templates/card-list.tsx index 97cd2cb6..7f06a89f 100644 --- a/src/components/templates/card-list.tsx +++ b/src/components/templates/card-list.tsx @@ -15,14 +15,18 @@ const SpinWrapper = styled.div` left: 0; max-height: 400px; right: 0; + .skelton-wrapper { + width: 100%; + } `; interface CatalogListProps { + defaultSpan?: number; + resizable?: boolean; dataList: any[]; loading: boolean; activeId: number; isFirst: boolean; - onDeploy: (data: any) => void; renderItem: (data: any) => React.ReactNode; Skeleton: React.ComponentType<{ span: number }>; } @@ -60,8 +64,16 @@ const ListSkeleton: React.FC<{ }; const CardList: React.FC = (props) => { - const { dataList, loading, isFirst, Skeleton, renderItem } = props; - const [span, setSpan] = React.useState(8); + const { + dataList, + loading, + isFirst, + defaultSpan = 8, + resizable = true, + Skeleton, + renderItem + } = props; + const [span, setSpan] = React.useState(defaultSpan); const getSpanByWidth = (width: number) => { if (width < breakpoints.md) return 24; @@ -78,8 +90,8 @@ const CardList: React.FC = (props) => { return (
- -
+ +
{dataList.map((item: any, index) => { return ( diff --git a/src/pages/llmodels/components/catalog-skelton.tsx b/src/components/templates/card-skelton.tsx similarity index 86% rename from src/pages/llmodels/components/catalog-skelton.tsx rename to src/components/templates/card-skelton.tsx index 4c3b72bb..293d0b90 100644 --- a/src/pages/llmodels/components/catalog-skelton.tsx +++ b/src/components/templates/card-skelton.tsx @@ -5,7 +5,7 @@ interface CatalogSkeltonProps { span: number; } -const CatalogSkelton: React.FC = (props) => { +const CardSkelton: React.FC = (props) => { return ( {Array(6) @@ -31,4 +31,4 @@ const CatalogSkelton: React.FC = (props) => { ); }; -export default React.memo(CatalogSkelton); +export default React.memo(CardSkelton); diff --git a/src/components/templates/card.tsx b/src/components/templates/card.tsx index 6455bce1..78266460 100644 --- a/src/components/templates/card.tsx +++ b/src/components/templates/card.tsx @@ -6,6 +6,9 @@ interface CardProps { height?: string | number; className?: string; children?: React.ReactNode; + clickable?: boolean; + ghost?: boolean; + onClick?: () => void; } const CardWrapper = styled.div` @@ -17,24 +20,43 @@ const CardWrapper = styled.div` align-items: flex-start; border: 1px solid var(--ant-color-border); border-radius: var(--border-radius-base); - cursor: pointer; + cursor: default; width: 100%; &:hover { background-color: var(--ant-color-fill-tertiary); + transition: background-color 0.2s ease; + } + + &.ghost { + background-color: transparent; } &.active { background-color: var(--ant-color-fill-tertiary); } + &.clickable { + cursor: pointer; + } `; const Card: React.FC = (props) => { - const { className, height, children } = props; + const { + className, + height, + children, + clickable = true, + ghost = false, + onClick + } = props; return ( {children} diff --git a/src/config/index.ts b/src/config/index.ts index 9c59835f..32b8a77c 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -33,7 +33,7 @@ export const StatusColorMap: Record< } }; -export const StatusMaps = { +export const StatusMaps: Record = { error: 'error', warning: 'warning', transitioning: 'transitioning', diff --git a/src/locales/en-US/models.ts b/src/locales/en-US/models.ts index 8c7a6134..da51318e 100644 --- a/src/locales/en-US/models.ts +++ b/src/locales/en-US/models.ts @@ -45,7 +45,8 @@ export default { 'models.form.scheduletype': 'Schedule Type', 'models.form.categories': 'Model Category', 'models.form.scheduletype.auto': 'Auto', - 'models.form.scheduletype.manual': 'Manual', + 'models.form.scheduletype.manual': 'Specify GPU', + 'models.form.scheduletype.gpuType': 'Specify GPU Type', 'models.form.scheduletype.auto.tips': 'Automatically deploys model instances to appropriate GPUs/Workers based on current resource conditions.', 'models.form.scheduletype.manual.tips': @@ -162,5 +163,9 @@ export default { 'See the related issue: #1979 on GitHub.', 'models.ollama.deprecated.notice': `The Ollama model source has been deprecated as of v0.6.1. For more information, see the related GitHub issue.`, 'models.backend.mindie.310p': - 'Ascend 310P only supports FP16, so you need to set --dtype=float16.' + 'Ascend 310P only supports FP16, so you need to set --dtype=float16.', + 'models.form.gpuCount': 'GPUs per Replica', + 'models.form.gpuType': 'GPU Type', + 'models.form.optimizeLongPrompt': 'Optimize Long Prompt', + 'models.form.enableSpeculativeDecoding': 'Enable Speculative Decoding' }; diff --git a/src/locales/ja-JP/models.ts b/src/locales/ja-JP/models.ts index b7e829c6..5b336a86 100644 --- a/src/locales/ja-JP/models.ts +++ b/src/locales/ja-JP/models.ts @@ -47,7 +47,8 @@ export default { 'models.form.scheduletype': 'スケジュールタイプ', 'models.form.categories': 'モデルカテゴリ', 'models.form.scheduletype.auto': '自動', - 'models.form.scheduletype.manual': '手動', + 'models.form.scheduletype.manual': 'GPUを指定', + 'models.form.scheduletype.gpuType': 'GPUタイプを指定', 'models.form.scheduletype.auto.tips': '現在のリソース状況に基づいて、モデルインスタンスを適切なGPU/ワーカーに自動的にデプロイします。', 'models.form.scheduletype.manual.tips': @@ -159,7 +160,11 @@ export default { 'See the related issue: #1979 on GitHub.', 'models.ollama.deprecated.notice': `The Ollama model source has been deprecated as of v0.6.1. For more information, see the related GitHub issue.`, 'models.backend.mindie.310p': - 'Ascend 310P only supports FP16, so you need to set --dtype=float16.' + 'Ascend 310P only supports FP16, so you need to set --dtype=float16.', + 'models.form.gpuCount': '各レプリカのGPU数', + 'models.form.gpuType': 'GPU タイプ', + 'models.form.optimizeLongPrompt': '長いプロンプトを最適化', + 'models.form.enableSpeculativeDecoding': '推測デコーディングを有効にする' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== diff --git a/src/locales/ru-RU/models.ts b/src/locales/ru-RU/models.ts index cde1080e..50b10179 100644 --- a/src/locales/ru-RU/models.ts +++ b/src/locales/ru-RU/models.ts @@ -45,7 +45,8 @@ export default { 'models.form.scheduletype': 'Тип планирования', 'models.form.categories': 'Категория модели', 'models.form.scheduletype.auto': 'Авто', - 'models.form.scheduletype.manual': 'Вручную', + 'models.form.scheduletype.manual': 'Указать GPU', + 'models.form.scheduletype.gpuType': 'Указать тип GPU', 'models.form.scheduletype.auto.tips': 'Автоматическое развертывание инстансов модели на подходящие GPU/воркеры в зависимости от текущих ресурсов.', 'models.form.scheduletype.manual.tips': @@ -161,9 +162,19 @@ export default { 'См. связанную проблему: #1979 on GitHub.', 'models.ollama.deprecated.notice': `Источник моделей Ollama объявлен устаревшим начиная с версии v0.6.1. Подробности см. в соответствующем issue на GitHub.`, 'models.backend.mindie.310p': - 'Ascend 310P поддерживает только FP16, поэтому необходимо установить --dtype=float16.' + 'Ascend 310P only supports FP16, so you need to set --dtype=float16.', + 'models.form.gpuCount': 'GPUs per Replica', + 'models.form.gpuType': 'GPU Type', + 'models.form.optimizeLongPrompt': 'Optimize Long Prompt', + 'models.form.enableSpeculativeDecoding': 'Enable Speculative Decoding' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== - +// 1. 'models.ollama.deprecated.title': 'Deprecation Notice', +// 2. 'models.ollama.deprecated.notice': `The Ollama model source has been deprecated as of v0.6.1. For more information, see the related GitHub issue.` +// 3. 'models.backend.mindie.310p':'Ascend 310P only supports FP16, so you need to set --dtype=float16.', +// 4. 'models.form.gpuCount': 'GPUs per Replica', +// 5. 'models.form.gpuType': 'GPU Type', +// 6. 'models.form.optimizeLongPrompt': 'Optimize Long Prompt', +// 7. 'models.form.enableSpeculativeDecoding': 'Enable Speculative Decoding' // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/models.ts b/src/locales/zh-CN/models.ts index c096ca73..c50751ea 100644 --- a/src/locales/zh-CN/models.ts +++ b/src/locales/zh-CN/models.ts @@ -46,7 +46,8 @@ export default { 'models.form.categories': '模型类别', 'models.form.scheduletype': '调度方式', 'models.form.scheduletype.auto': '自动', - 'models.form.scheduletype.manual': '手动', + 'models.form.scheduletype.manual': '指定 GPU', + 'models.form.scheduletype.gpuType': '指定 GPU 类型', 'models.form.scheduletype.auto.tips': '自动根据当前资源情况部署模型实例到合适的 GPU/Worker。', 'models.form.scheduletype.manual.tips': @@ -152,5 +153,9 @@ export default { '参见 GitHub 上的问题 #1979。', 'models.ollama.deprecated.notice': `Ollama 模型来源自 v0.6.1 起已被弃用。更多信息请参见相关的 GitHub 问题。`, 'models.backend.mindie.310p': - 'Ascend 310P 仅支持 FP16,需要设置 --dtype=float16。' + 'Ascend 310P 仅支持 FP16,需要设置 --dtype=float16。', + 'models.form.gpuCount': '每副本 GPU 数量', + 'models.form.gpuType': 'GPU 类型', + 'models.form.optimizeLongPrompt': '优化长提示', + 'models.form.enableSpeculativeDecoding': '启用推测解码' }; diff --git a/src/pages/cluster-management/clusters-copy.tsx b/src/pages/cluster-management/clusters-copy.tsx new file mode 100644 index 00000000..1a5057a9 --- /dev/null +++ b/src/pages/cluster-management/clusters-copy.tsx @@ -0,0 +1,489 @@ +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 IconFont from '@/components/icon-font'; +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, + SyncOutlined +} from '@ant-design/icons'; +import { PageContainer } from '@ant-design/pro-components'; +import { useIntl } from '@umijs/max'; +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 AddPool from './components/add-pool'; +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: + }, + { + label: 'Digital Ocean', + locale: false, + value: 'digitalocean', + key: 'digitalocean', + 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: 'addPool', + label: 'Add Node Pool', + locale: false, + 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 intl = useIntl(); + const [open, setOpen] = useState(false); + const [openAddModal, setOpenAddModal] = useState(false); + const [provider, setProvider] = useState('custom'); + const [action, setAction] = useState(PageAction.CREATE); + const [title, setTitle] = useState(''); + const [addPoolStatus, setAddPoolStatus] = useState<{ + open: boolean; + action: PageActionType; + title: string; + provider: string; + }>({ + open: false, + action: PageAction.CREATE, + title: '', + provider: 'digitalocean' + }); + const [currentData, setCurrentData] = useState( + undefined + ); + + const setActions = (row: ListItem) => { + if (row.provider !== 'custom') { + return ActionList.filter((item) => item.key !== 'add'); + } + return ActionList; + }; + + const handleAddCluster = (value: string) => { + setOpenAddModal(true); + setAction(PageAction.CREATE); + setProvider(value); + const label = addActions.find((item) => item.value === value)?.label; + setTitle(`Add ${label} Cluster`); + }; + + const handleAddPool = (value: string) => { + setAddPoolStatus({ + open: true, + action: PageAction.CREATE, + title: `Add Node Pool`, + provider: value + }); + }; + + const handleClickDropdown = (item: any) => { + handleAddCluster(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 ${row.name} 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); + } else if (val === 'addPool') { + handleAddPool(row.provider); + } + }; + + const renderEmpty = (type?: string) => { + if (type !== 'Table') return; + if ( + !dataSource.loading && + dataSource.loadend && + !dataSource.dataList.length + ) { + return ; + } + return
; + }; + + return ( + <> + + + + + + } + right={ + + + + + + + } + > + + + { + return ( + + {text} + + ); + }} + /> + { + return ( + + {addActions.find((item) => item.value === record.provider) + ?.label || 'N/A'} + + ); + }} + /> + { + return ( + + + + 3 + + + + 1 + + + ); + }} + /> + { + return ( + + {text} + + ); + }} + /> + + { + return ( + + {text} + + ); + }} + /> + { + return ( + handleSelect(val, record)} + > + ); + }} + /> +
+
+
+ + { + setAddPoolStatus({ + open: false, + action: PageAction.CREATE, + title: '', + provider: 'digitalocean' + }); + }} + onOk={() => { + setAddPoolStatus({ + open: false, + action: PageAction.CREATE, + title: '', + provider: 'digitalocean' + }); + }} + > + setOpen(false)}> + + + ); +}; + +export default Credentials; diff --git a/src/pages/cluster-management/clusters.tsx b/src/pages/cluster-management/clusters.tsx index 1a5057a9..8aa0a648 100644 --- a/src/pages/cluster-management/clusters.tsx +++ b/src/pages/cluster-management/clusters.tsx @@ -1,32 +1,21 @@ -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 IconFont from '@/components/icon-font'; -import PageTools from '@/components/page-tools'; +import { FilterBar } from '@/components/page-tools'; +import CardList from '@/components/templates/card-list'; +import CardSkeleton from '@/components/templates/card-skelton'; 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, - SyncOutlined + ProfileOutlined } from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; import { useIntl } from '@umijs/max'; -import { - Button, - ConfigProvider, - Empty, - Input, - Space, - Table, - message -} from 'antd'; +import { Empty, Table, message } from 'antd'; import { useState } from 'react'; import styled from 'styled-components'; import { @@ -37,6 +26,7 @@ import { } from './apis'; import AddCluster from './components/add-cluster'; import AddPool from './components/add-pool'; +import ClusterItem from './components/cluster-item'; import { ClusterDataList } from './config'; import { ClusterFormData as FormData, @@ -244,7 +234,7 @@ const Credentials: React.FC = () => { handleEditUser(row); } else if (val === 'delete') { handleDelete({ ...row, name: row.name }); - } else if (val === 'add') { + } else if (val === 'add_worker') { setOpen(true); setCurrentData(row); } else if (val === 'addPool') { @@ -252,6 +242,10 @@ const Credentials: React.FC = () => { } }; + const renderCard = (item: ListItem) => { + return ; + }; + const renderEmpty = (type?: string) => { if (type !== 'Table') return; if ( @@ -279,175 +273,31 @@ const Credentials: React.FC = () => { }} extra={[]} > - - - - - } - right={ - - - - - - - } - > - - - { - return ( - - {text} - - ); - }} - /> - { - return ( - - {addActions.find((item) => item.value === record.provider) - ?.label || 'N/A'} - - ); - }} - /> - { - return ( - - - - 3 - - - - 1 - - - ); - }} - /> - { - return ( - - {text} - - ); - }} - /> - - { - return ( - - {text} - - ); - }} - /> - { - return ( - handleSelect(val, record)} - > - ); - }} - /> -
-
+ marginTop={30} + handleInputChange={handleNameChange} + handleSearch={handleSearch} + width={{ input: 200 }} + buttonText="Add Cluster" + actionType="dropdown" + actionItems={addActions} + handleClickPrimary={handleClickDropdown} + > + = ({ const [form] = Form.useForm(); const intl = useIntl(); - const handleSumit = () => { + const handleSubmit = () => { form.submit(); }; @@ -62,7 +61,7 @@ const AddCluster: React.FC = ({ width={600} styles={{}} footer={ - + } >
@@ -85,8 +84,9 @@ const AddCluster: React.FC = ({ required > - {provider === 'digitalocean' && } - {provider === 'kubernetes' && } + {provider === 'digitalocean' && ( + + )} name="description" rules={[{ required: false }]}> = ({ width={600} styles={{}} footer={ - + Validation Test} + > } > @@ -70,29 +74,6 @@ const AddModal: React.FC = ({ required > - {/* - name="provider" - rules={[ - { - required: true, - message: intl.formatMessage( - { id: 'common.form.rule.input' }, - { - name: 'Provider' - } - ) - } - ]} - > - ({ - label: item, - value: item - }))} - > - */} {provider === 'digital_ocean' && ( <> diff --git a/src/pages/cluster-management/components/cluster-item.tsx b/src/pages/cluster-management/components/cluster-item.tsx new file mode 100644 index 00000000..b588c8fe --- /dev/null +++ b/src/pages/cluster-management/components/cluster-item.tsx @@ -0,0 +1,237 @@ +import DropdownButtons from '@/components/drop-down-buttons'; +import GaugeChart from '@/components/echarts/gauge'; +import IconFont from '@/components/icon-font'; +import StatusTag from '@/components/status-tag'; +import Card from '@/components/templates/card'; +import { + DeleteOutlined, + EditOutlined, + KubernetesOutlined +} from '@ant-design/icons'; +import { Card as ACard, Button, Col, Row, Tag } from 'antd'; +import React, { useMemo } from 'react'; +import styled from 'styled-components'; +import { + ClusterStatus, + ClusterStatusLabelMap, + ProviderLabelMap, + ProviderValueMap +} from '../config'; +import { ClusterListItem as ListItem } from '../config/types'; +import WorkerPools from './worker-pools'; + +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` + display: flex; + justify-content: space-between; + align-items: center; + .chart-wrapper { + flex: 1; + } +`; + +const CardWrapper = styled(ACard)` + text-align: center; + box-shadow: none; + .ant-card { + box-shadow: none; + } + .ant-card-body { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + gap: 8px; + } + + .label { + font-weight: 500; + font-size: var(--font-size-middle); + } + .value { + font-weight: 500; + color: var(--ant-color-text); + } +`; + +const actionItems = [ + { + key: 'edit', + label: 'common.button.edit', + icon: + }, + { + key: 'add_worker', + label: 'Add Worker', + provider: ProviderValueMap.Custom, + locale: false, + icon: + }, + { + key: 'register_cluster', + label: 'Register Cluster', + provider: ProviderValueMap.Kubernetes, + locale: false, + icon: + }, + { + key: 'addPool', + label: 'Add Node Pool', + provider: ProviderValueMap.DigitalOcean, + locale: false, + icon: + }, + { + key: 'delete', + label: 'common.button.delete', + icon: , + props: { + danger: true + } + } +]; + +const Inner = styled.div` + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + cursor: default; + + .title { + margin-bottom: 16px; + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 8px; + + .text { + display: flex; + align-items: center; + font-size: var(--font-size-middle); + font-weight: 500; + color: var(--ant-color-text); + } + } + + .content { + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1; + } +`; + +interface CardProps { + data: ListItem; + onSelect?: (key: string, row: ListItem) => void; +} + +const CardItem: React.FC = (props) => { + const { data, onSelect } = props; + const [show, setShow] = React.useState(false); + + const handleOnSelect = (key: string) => { + console.log('Selected action:', key); + onSelect?.(key, data); + }; + + const actions = useMemo(() => { + return actionItems.filter((item) => { + if (item.provider) { + return item.provider === data.provider; + } + return true; + }); + }, [data.provider]); + + return ( + + +
+ + {data.name} + + {ProviderLabelMap[data.provider]} + + + + + + +
+ +
+ +
Workers
+
1
+
+ +
GPUs
+
12
+
+ +
Deployments
+
2
+
+
+
+ + + + + + + + + + + + + + +
+
+ {data.provider === ProviderValueMap.DigitalOcean && ( + <> + + + + + + )} +
+
+ ); +}; + +export default CardItem; diff --git a/src/pages/cluster-management/components/register-cluster.tsx b/src/pages/cluster-management/components/register-cluster.tsx new file mode 100644 index 00000000..6896d709 --- /dev/null +++ b/src/pages/cluster-management/components/register-cluster.tsx @@ -0,0 +1,104 @@ +import ModalFooter from '@/components/modal-footer'; +import ScrollerModal from '@/components/scroller-modal/index'; +import SealInput from '@/components/seal-form/seal-input'; +import { PageActionType } from '@/config/types'; +import { CloseOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Button, Form } from 'antd'; +import React from 'react'; +import { + ClusterFormData as FormData, + ClusterListItem as ListItem +} from '../config/types'; +import CloudProvider from './cloud-provider-form'; +import K8SProvider from './k8s-provider-form'; + +type AddModalProps = { + title: string; + action: PageActionType; + open: boolean; + provider: string; // 'kubernetes' | 'custom' | 'digitalocean'; + onOk: (values: FormData) => void; + data?: ListItem; + onCancel: () => void; +}; +const AddCluster: React.FC = ({ + title, + action, + open, + provider, + onOk, + data, + onCancel +}) => { + const [form] = Form.useForm(); + const intl = useIntl(); + + const handleSubmit = () => { + form.submit(); + }; + + const handleCancel = () => { + form.resetFields(); + onCancel(); + }; + + return ( + + {title} + +
+ } + open={open} + onClose={onCancel} + destroyOnClose={true} + closeIcon={false} + maskClosable={false} + keyboard={false} + width={600} + styles={{}} + footer={ + + } + > + + + name="display_name" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: intl.formatMessage({ id: 'common.table.name' }) + } + ) + } + ]} + > + + + {provider === 'digitalocean' && ( + + )} + {provider === 'kubernetes' && ( + + )} + name="description" rules={[{ required: false }]}> + + + + + ); +}; + +export default AddCluster; diff --git a/src/pages/cluster-management/components/worker-pools.tsx b/src/pages/cluster-management/components/worker-pools.tsx new file mode 100644 index 00000000..14a13973 --- /dev/null +++ b/src/pages/cluster-management/components/worker-pools.tsx @@ -0,0 +1,117 @@ +import DropdownButtons from '@/components/drop-down-buttons'; +import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; +import { Table } from 'antd'; + +const actionItems = [ + { + key: 'edit', + label: 'common.button.edit', + icon: + }, + + { + key: 'delete', + label: 'common.button.delete', + icon: , + props: { + danger: true + } + } +]; + +interface WorkerPoolsProps { + dataSource: any[]; + loading?: boolean; + provider: string; + height?: string | number; + onAction?: (action: string, record: any) => void; +} + +const WorkerPools: React.FC = ({ + dataSource, + loading = false, + provider, + height = 'auto', + onAction +}) => { + // dataindex: type, replicas, Batchsize, GPU, Memory, CPU, Storage, CreateTime, Operations + const columns = [ + { + title: 'Type', + dataIndex: 'type', + key: 'type' + }, + { + title: 'Replicas', + dataIndex: 'replicas', + key: 'replicas' + }, + { + title: 'Batch Size', + dataIndex: 'batchSize', + key: 'batchSize' + }, + { + title: 'GPU', + dataIndex: 'gpu', + key: 'gpu' + }, + { + title: 'Memory', + dataIndex: 'memory', + key: 'memory' + }, + { + title: 'CPU', + dataIndex: 'cpu', + key: 'cpu' + }, + { + title: 'Storage', + dataIndex: 'storage', + key: 'storage' + }, + { + title: 'Create Time', + dataIndex: 'createTime', + key: 'createTime' + }, + { + title: 'Operations', + key: 'operations', + render: (_, record) => ( + onAction?.(key, record)} + /> + ) + } + ]; + + // mock dataSource + const mockData = Array.from({ length: 3 }, (_, index) => ({ + key: index, + type: `Type ${index + 1}`, + replicas: Math.floor(Math.random() * 10) + 1, + batchSize: Math.floor(Math.random() * 100) + 1, + gpu: `NVIDIA 4090`, + memory: `${Math.floor(Math.random() * 16) + 1} GB`, + cpu: `${Math.floor(Math.random() * 8) + 1} Cores`, + storage: `${Math.floor(Math.random() * 500) + 50} GB`, + createTime: new Date().toLocaleDateString() + })); + + return ( +
+ + + ); +}; + +export default WorkerPools; diff --git a/src/pages/cluster-management/config/index.ts b/src/pages/cluster-management/config/index.ts index dc85ec45..b182c4dc 100644 --- a/src/pages/cluster-management/config/index.ts +++ b/src/pages/cluster-management/config/index.ts @@ -1,3 +1,6 @@ +import { StatusMaps } from '@/config'; +import { StatusType } from '@/config/types'; + export const ClusterDataList = [ { id: 3, @@ -6,6 +9,7 @@ export const ClusterDataList = [ clusterType: 'Custom', workers: 4, gpus: 8, + status: 'ready', deployments: 3 }, { @@ -15,6 +19,7 @@ export const ClusterDataList = [ clusterType: 'Kubernetes', workers: 2, gpus: 4, + status: 'ready', deployments: 1 }, { @@ -23,6 +28,34 @@ export const ClusterDataList = [ provider: 'digitalocean', workers: 3, gpus: 6, + status: 'error', deployments: 2 } ]; + +export const ClusterStatusValueMap = { + Ready: 'ready', + Error: 'error' +}; + +export const ClusterStatusLabelMap = { + [ClusterStatusValueMap.Ready]: 'Ready', + [ClusterStatusValueMap.Error]: 'Error' +}; + +export const ClusterStatus: Record = { + [ClusterStatusValueMap.Ready]: StatusMaps.success, + [ClusterStatusValueMap.Error]: StatusMaps.error +}; + +export const ProviderValueMap = { + Kubernetes: 'kubernetes', + DigitalOcean: 'digitalocean', + Custom: 'custom' +}; + +export const ProviderLabelMap = { + [ProviderValueMap.Kubernetes]: 'Kubernetes', + [ProviderValueMap.DigitalOcean]: 'Digital Ocean', + [ProviderValueMap.Custom]: 'Custom' +}; diff --git a/src/pages/cluster-management/config/types.ts b/src/pages/cluster-management/config/types.ts index 18dadabd..c50dedb6 100644 --- a/src/pages/cluster-management/config/types.ts +++ b/src/pages/cluster-management/config/types.ts @@ -18,16 +18,6 @@ export interface ListItem { updated_at: string; } -export interface ClusterListItem { - name: string; - provider: string; - workers: number; - created_at: string; - gpus: number; - deployments: number; - id: number; -} - export interface ClusterFormData { display_name: string; description: string; @@ -55,3 +45,19 @@ export interface NodePoolFormData { labels: Record; cloud_options: Record; } + +export interface ClusterListItem { + name: string; + display_name: string; + description: string; + provider: string; + credential_id: number; + zone: string; + region: string; + gpus: number; + deployments: number; + id: number; + status: string; + state_message: string; + worker_pools: NodePoolListItem[]; +} diff --git a/src/pages/dashboard/components/system-load.tsx b/src/pages/dashboard/components/system-load.tsx index 40b47033..e47cc382 100644 --- a/src/pages/dashboard/components/system-load.tsx +++ b/src/pages/dashboard/components/system-load.tsx @@ -10,16 +10,6 @@ import { memo, useContext, useEffect, useMemo, useState } from 'react'; import { DashboardContext } from '../config/dashboard-context'; import ResourceUtilization from './resource-utilization'; -const strokeColorFunc = (percent: number) => { - if (percent <= 50 || percent === undefined) { - return 'rgb(84, 204, 152, 80%)'; - } - if (percent <= 80) { - return 'rgba(250, 173, 20, 80%)'; - } - return 'rgba(255, 77, 79, 80%)'; -}; - const SystemLoad = () => { const intl = useIntl(); const data = useContext(DashboardContext)?.system_load?.current || {}; @@ -33,24 +23,22 @@ const SystemLoad = () => { const chartData = useMemo(() => { return { gpu: { - data: _.round(data.gpu || 0, 1), - color: strokeColorFunc(data.gpu) + data: _.round(data.gpu || 0, 1) }, vram: { - data: _.round(data.vram || 0, 1), - color: strokeColorFunc(data.vram) + data: _.round(data.vram || 0, 1) }, cpu: { - data: _.round(data.cpu || 0, 1), - color: strokeColorFunc(data.cpu) + data: _.round(data.cpu || 0, 1) }, ram: { - data: _.round(data.ram || 0, 1), - color: strokeColorFunc(data.ram) + data: _.round(data.ram || 0, 1) } }; }, [data]); + console.log('SystemLoad data:', chartData); + useEffect(() => { if (size.width < breakpoints.xl) { setPaddingRight('0'); @@ -90,7 +78,6 @@ const SystemLoad = () => { { id: 'dashboard.vramutilization' })} height={smallChartHeight} - color={chartData.vram.color} value={chartData.vram.data} > @@ -112,7 +98,6 @@ const SystemLoad = () => { id: 'dashboard.cpuutilization' })} height={smallChartHeight} - color={chartData.cpu.color} value={chartData.cpu.data} > @@ -122,7 +107,6 @@ const SystemLoad = () => { id: 'dashboard.memoryutilization' })} height={smallChartHeight} - color={chartData.ram.color} value={chartData.ram.data} > diff --git a/src/pages/llmodels/components/advance-config.tsx b/src/pages/llmodels/components/advance-config.tsx index a1062e15..15d01d7a 100644 --- a/src/pages/llmodels/components/advance-config.tsx +++ b/src/pages/llmodels/components/advance-config.tsx @@ -1,6 +1,7 @@ import IconFont from '@/components/icon-font'; import LabelSelector from '@/components/label-selector'; import ListInput from '@/components/list-input'; +import CheckboxField from '@/components/seal-form/checkbox-field'; import SealCascader from '@/components/seal-form/seal-cascader'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; @@ -8,17 +9,8 @@ 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'; import { useIntl } from '@umijs/max'; -import { - Checkbox, - Collapse, - Form, - FormInstance, - Tooltip, - Typography -} from 'antd'; -import { CheckboxChangeEvent } from 'antd/es/checkbox'; +import { Collapse, Form, FormInstance, Typography } from 'antd'; import _ from 'lodash'; import React, { useCallback, useMemo } from 'react'; import { @@ -32,7 +24,6 @@ import { placementStrategyOptions } from '../config'; import { useFormContext } from '../config/form-context'; -import llamaConfig from '../config/llama-config'; import mindieConfig from '../config/mindie-config'; import { FormData } from '../config/types'; import vllmConfig from '../config/vllm-config'; @@ -62,42 +53,6 @@ const placementStrategyTips = [ } ]; -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 AdvanceConfig: React.FC = (props) => { const { form, isGGUF, gpuOptions, source, backendOptions, action } = props; const { getRuleMessage } = useAppUtils(); @@ -115,9 +70,6 @@ const AdvanceConfig: React.FC = (props) => { const { onValuesChange } = useFormContext(); const paramsConfig = useMemo(() => { - if (backend === backendOptionsMap.llamaBox) { - return llamaConfig; - } if (backend === backendOptionsMap.vllm) { return vllmConfig; } @@ -220,14 +172,6 @@ const AdvanceConfig: React.FC = (props) => { description={} 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, @@ -261,27 +205,6 @@ const AdvanceConfig: React.FC = (props) => { } > - {/* - } - options={[ - { - label: intl.formatMessage({ - id: 'models.form.scheduletype.auto' - }), - value: 'auto' - }, - { - label: intl.formatMessage({ - id: 'models.form.scheduletype.manual' - }), - value: 'manual' - } - ]} - > - */} {scheduleType === 'auto' && ( <> name="placement_strategy"> @@ -482,31 +405,10 @@ const AdvanceConfig: React.FC = (props) => { > - {backend === backendOptionsMap.llamaBox && ( -
- - name="cpu_offloading" - valuePropName="checked" - style={{ padding: '0 10px', marginBottom: 0 }} - noStyle - > - - -
- )} {scheduleType === 'auto' && - [ - backendOptionsMap.llamaBox, - backendOptionsMap.vllm, - backendOptionsMap.ascendMindie - ].includes(backend) && ( + [backendOptionsMap.vllm, backendOptionsMap.ascendMindie].includes( + backend + ) && (
name="distributed_inference_across_workers" @@ -515,7 +417,7 @@ const AdvanceConfig: React.FC = (props) => { noStyle > = (props) => { noStyle > = forwardRef((props, ref) => { description={} 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, @@ -300,28 +292,6 @@ const DataForm: React.FC = forwardRef((props, ref) => { > */} - {/* - name="replicas" - rules={[ - { - required: true, - message: getRuleMessage('input', 'models.form.replicas') - } - ]} - > - - */} name="description"> = forwardRef((props, ref) => { ); }); -export default React.memo(DataForm); +export default DataForm; diff --git a/src/pages/llmodels/components/deploy-modal.tsx b/src/pages/llmodels/components/deploy-modal.tsx index 0b21d5d4..8a92b4c0 100644 --- a/src/pages/llmodels/components/deploy-modal.tsx +++ b/src/pages/llmodels/components/deploy-modal.tsx @@ -568,6 +568,7 @@ const AddModal: FC = (props) => { isGGUF: isGGUF, pageAction: action, modelFileOptions: props.modelFileOptions, + gpuOptions: props.gpuOptions, onValuesChange: onValuesChange }} > diff --git a/src/pages/llmodels/components/model-item.tsx b/src/pages/llmodels/components/model-item.tsx index 2cc94b4f..9a6bcc10 100644 --- a/src/pages/llmodels/components/model-item.tsx +++ b/src/pages/llmodels/components/model-item.tsx @@ -1,18 +1,145 @@ +import IconFont from '@/components/icon-font'; +import StatusTag from '@/components/status-tag'; +import ThemeTag from '@/components/tags-wrapper/theme-tag'; import Card from '@/components/templates/card'; +import { Button } from 'antd'; +import dayjs from 'dayjs'; +import _ from 'lodash'; import React from 'react'; +import styled from 'styled-components'; +import { + InstanceStatusMap, + InstanceStatusMapValue, + modelCategories, + modelSourceMap, + status +} from '../config'; -const ModelItem: React.FC<{ model: any; onDeploy: (model: any) => void }> = ( - props -) => { - const { model, onDeploy } = props; +const ModelItemContent = styled.div` + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + cursor: default; + .title { + margin-bottom: 16px; + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 8px; + .anticon { + font-size: 16px; + color: var(--ant-color-text-secondary); + } + .text { + display: flex; + align-items: center; + font-size: var(--font-size-middle); + font-weight: 500; + color: var(--ant-color-text); + } + } + .content { + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1; + } + .footer { + width: 100%; + display: flex; + justify-content: space-between; + align-items: flex-end; + .btn { + display: none; + } + } + &:hover { + .btn { + display: block; + } + } + .time { + color: var(--ant-color-text-secondary); + font-size: var(--font-size-small); + font-weight: 400; + } + .extra-info { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; + .tag-item { + margin-right: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 2px 6px; + border-radius: 4px; + font-size: 12px; + height: 22px; + opacity: 0.7; + } + } +`; + +const sourceIconMap = { + [modelSourceMap.local_path_value]: 'icon-hard-disk', + [modelSourceMap.huggingface_value]: 'icon-huggingface', + [modelSourceMap.modelscope_value]: 'icon-modelscope' +}; + +const ModelItem: React.FC<{ + model: Record; + onClick: (model: any) => void; +}> = (props) => { + const { model, onClick } = props; return ( - -
onDeploy(model)}> -

{model.name}

-

{model.description}

- Deploy -
+ onClick(model)} clickable={false} ghost> + +
+ + + {model.name} + + +
+
+
+ + {_.find(modelCategories, { value: model.categories?.[0] }) + ?.label || model.categories?.[0]} + + + 128K context + +
+
+ + {dayjs(model.created_at).format('YYYY-MM-DD HH:mm:ss')} + + +
+
+
); }; diff --git a/src/pages/llmodels/components/ollama-tips.tsx b/src/pages/llmodels/components/ollama-tips.tsx deleted file mode 100644 index ef24958f..00000000 --- a/src/pages/llmodels/components/ollama-tips.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import AlertBlockInfo from '@/components/alert-info/block'; -import IconFont from '@/components/icon-font'; -import { useIntl } from '@umijs/max'; -import { Button } from 'antd'; -import { useState } from 'react'; -import styled from 'styled-components'; - -const Wrapper = styled.div` - padding-inline: 24px; -`; - -const Tips = styled.div` - margin-top: 8px !important; - ul { - margin: 0 !important; - padding: 0 !important; - margin-top: 8px !important; - li { - position: relative; - list-style: none; - line-height: 24px; - margin: 0 !important; - padding-inline: 16px 0 !important; - &::before { - content: ''; - position: absolute; - left: 0px; - top: 0; - width: 6px; - height: 6px; - border-radius: 50%; - background-color: var(--ant-color-text-secondary); - margin-top: 8px; - } - } - } - .bold { - font-weight: 700; - color: var(--ant-color-text); - } - .notice { - margin-top: 8px !important; - } -`; - -const TitleWrapper = styled.div` - display: flex; - align-items: center; - font-weight: 700; - color: var(--ant-color-text); - justify-content: space-between; -`; - -const RenderMessage = () => { - const intl = useIntl(); - return ( - -
-
-
-
- ); -}; - -const OllamaTips = () => { - const intl = useIntl(); - const [maxHeight, setMaxHeight] = useState(1); - const handleShowMore = () => { - setMaxHeight(maxHeight === 1 ? 600 : 1); - }; - return ( - - } - title={ - -
- {intl.formatMessage({ id: 'models.ollama.deprecated.title' })} -
- -
- } - type={'warning'} - >
-
- ); -}; - -export default OllamaTips; diff --git a/src/pages/llmodels/components/performance.tsx b/src/pages/llmodels/components/performance.tsx index 956f10bb..e916b57a 100644 --- a/src/pages/llmodels/components/performance.tsx +++ b/src/pages/llmodels/components/performance.tsx @@ -1,12 +1,15 @@ +import CheckboxField from '@/components/seal-form/checkbox-field'; +import SealCascader from '@/components/seal-form/seal-cascader'; 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 { Form } from 'antd'; import React from 'react'; +import { backendOptionsMap } from '../config'; import { useFormContext } from '../config/form-context'; +import { FormData } from '../config/types'; +import GPUCard from './gpu-card'; const scheduleTypeTips = [ { @@ -25,29 +28,15 @@ const scheduleTypeTips = [ } ]; -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 { + onValuesChange, + onQuantizationChange, + gpuOptions, + source, + quantizationOptions + } = useFormContext(); const { getRuleMessage } = useAppUtils(); const form = Form.useFormInstance(); @@ -61,6 +50,8 @@ const Performance: React.FC = () => { onQuantizationChange?.(val); }; + const handleGpuSelectorChange = (value: any) => {}; + return ( <> @@ -80,30 +71,102 @@ const Performance: React.FC = () => { id: 'models.form.scheduletype.manual' }), value: 'manual' + }, + { + label: intl.formatMessage({ + id: 'models.form.scheduletype.gpuType' + }), + value: 'specific_gpu_type' } ]} > - - name="quantization" - key="quantization" - rules={[ - { - required: true, - message: getRuleMessage('select', 'quantization', false) - } - ]} - > - - + {form.getFieldValue('scheduleType') === 'specific_gpu_type' && ( + <> + + + + + + + + )} + {form.getFieldValue('scheduleType') === 'manual' && + !form.getFieldValue('fix_gpu_type') && ( + <> + + triggerNode.parentNode} + onChange={handleGpuSelectorChange} + > + + + )}
name="optimize_long_prompt" @@ -112,8 +175,7 @@ const Performance: React.FC = () => { noStyle >
@@ -125,8 +187,9 @@ const Performance: React.FC = () => { noStyle >
diff --git a/src/pages/llmodels/components/table-list.tsx b/src/pages/llmodels/components/table-list.tsx index 6983c467..25f2f7bf 100644 --- a/src/pages/llmodels/components/table-list.tsx +++ b/src/pages/llmodels/components/table-list.tsx @@ -111,6 +111,36 @@ interface ModelsProps { total: number; } +const clusterList = [ + { + label: 'Custom', + value: 'custom' + }, + { + label: 'Kubernetes', + value: 'kubernetes' + }, + { + label: 'Digital Ocean', + value: 'digital_ocean' + } +]; + +const statusList = [ + { + label: 'Running', + value: 'running' + }, + { + label: 'Stopped', + value: 'stopped' + }, + { + label: 'Error', + value: 'error' + } +]; + const getFormattedData = (record: any, extraData = {}) => ({ id: record.id, data: { @@ -605,7 +635,6 @@ const Models: React.FC = ({ title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: 'name', key: 'name', - width: 400, span: 5, render: (text: string, record: ListItem) => ( @@ -620,10 +649,10 @@ const Models: React.FC = ({ title: 'Cluster', dataIndex: 'cluster', key: 'cluster', - span: 4, + span: 3, render: (text: string, record: ListItem) => ( - Custom + {['Custom', 'Kubernetes', 'Digital Ocean'][record.id] || 'Custom'} ) }, @@ -631,7 +660,7 @@ const Models: React.FC = ({ title: intl.formatMessage({ id: 'models.form.source' }), dataIndex: 'source', key: 'source', - span: 4, + span: 5, render: (text: string, record: ListItem) => ( {generateSource(record)} @@ -778,7 +807,7 @@ const Models: React.FC = ({ = ({ placeholder={intl.formatMessage({ id: 'models.filter.category' })} - style={{ width: 180 }} + style={{ width: 160 }} size="large" maxTagCount={1} onChange={handleCategoryChange} @@ -798,11 +827,20 @@ const Models: React.FC = ({ + + + } + > + */} + > + ); }; diff --git a/src/pages/resources/config/index.ts b/src/pages/resources/config/index.ts index fb780bdb..91302f5b 100644 --- a/src/pages/resources/config/index.ts +++ b/src/pages/resources/config/index.ts @@ -41,14 +41,11 @@ export const addWorkerGuide: Record = { token: string; workerip: string; }) { - return `docker run -d --name gpustack \\ - --restart=unless-stopped \\ - --gpus all \\ - --network=host \\ - --ipc=host \\ - -v gpustack-data:/var/lib/gpustack \\ - gpustack/gpustack:${params.tag} \\ - --server-url ${params.server} --token ${params.token} --worker-ip ${params.workerip}`; + return `docker run -d \\ + --net=host \\ + -v /var/lib/gpustack:/var/lib/gpustack \\ + --privileged gpustack/gpustack:xxxx \\ + --registration ${params.token}`; } }, npu: {