fix: default instance type, storageMode

This commit is contained in:
jialin
2026-05-26 22:40:02 +08:00
committed by jialin
parent a51f44c135
commit 2f04e01f4f
21 changed files with 213 additions and 185 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+2
View File
@@ -105,6 +105,8 @@ export default {
'Please select at least {count} GPU card(s)',
'gpuservice.instance.gpuCount.noAvailable':
'No available GPU resources, please choose another instance type.',
'gpuservice.instance.gpuCount.zero':
'CPU-only setup for environment preparation.',
'gpuservice.instance.stock': 'Stock',
'gpuservice.instance.sliced': 'Sliced',
'gpuservice.instance.memory': 'Memory',
+1
View File
@@ -104,6 +104,7 @@ export default {
'少なくとも {count} 枚の GPU カードを選択してください',
'gpuservice.instance.gpuCount.noAvailable':
'利用可能な GPU リソースがありません。別のインスタンスタイプを選択してください。',
'gpuservice.instance.gpuCount.zero': 'CPU のみを使用し、環境準備用です。',
'gpuservice.instance.stock': '在庫',
'gpuservice.instance.sliced': '分割',
'gpuservice.instance.memory': 'Memory',
+1
View File
@@ -106,6 +106,7 @@ export default {
'gpuservice.instance.gpuCount.min': 'Выберите минимум {count} GPU-карт',
'gpuservice.instance.gpuCount.noAvailable':
'Нет доступных ресурсов GPU, выберите другой тип экземпляра.',
'gpuservice.instance.gpuCount.zero': 'Только CPU, для подготовки окружения.',
'gpuservice.instance.stock': 'Остаток',
'gpuservice.instance.sliced': 'Разделено',
'gpuservice.instance.memory': 'Память',
+1
View File
@@ -103,6 +103,7 @@ export default {
'gpuservice.instance.gpuCount.min': 'En az {count} GPU kartı seçin',
'gpuservice.instance.gpuCount.noAvailable':
'Kullanılabilir GPU kaynağı yok, lütfen başka bir örnek türü seçin.',
'gpuservice.instance.gpuCount.zero': 'Yalnızca CPU, ortam hazırlığı için.',
'gpuservice.instance.stock': 'Stok',
'gpuservice.instance.sliced': 'Bölünmüş',
'gpuservice.instance.memory': 'Bellek',
+1
View File
@@ -98,6 +98,7 @@ export default {
'gpuservice.instance.gpuCount.min': '至少选择 {count} 张卡',
'gpuservice.instance.gpuCount.noAvailable':
'没有可用的 GPU 资源,请选择其他实例类型。',
'gpuservice.instance.gpuCount.zero': '仅使用 CPU,用于环境准备。',
'gpuservice.instance.stock': '库存',
'gpuservice.instance.sliced': '切分',
'gpuservice.instance.memory': '显存',
@@ -1,6 +1,7 @@
import { QuestionCircleOutlined } from '@ant-design/icons';
import { LabelInfo } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Flex, InputNumber } from 'antd';
import { Flex, InputNumber, Tooltip } from 'antd';
import classNames from 'classnames';
import React, { useEffect, useState } from 'react';
import styles from './styles.less';
@@ -21,6 +22,7 @@ interface NumberSelectionProps {
};
labelExtra?: React.ReactNode;
maxCount?: number;
tips?: string;
onChange?: (value: number) => void;
}
@@ -36,6 +38,7 @@ const NumberSelection: React.FC<NumberSelectionProps> = ({
labelExtra,
className,
maxCount = 8,
tips,
style,
onChange
}) => {
@@ -108,25 +111,38 @@ const NumberSelection: React.FC<NumberSelectionProps> = ({
{items.map((num) => {
const itemDisabled = isItemDisabled(num);
return (
<div
key={num}
role="radio"
aria-checked={num === value}
aria-disabled={itemDisabled}
tabIndex={itemDisabled ? -1 : 0}
className={classNames(styles.numberItem, {
[styles.active]: num === value && value != null,
[styles.itemDisabled]: itemDisabled && !disabled
})}
onClick={() => handleSelect(num)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleSelect(num);
}
}}
>
{num}
<div key={num} style={{ flex: 1 }}>
<Tooltip title={num === 0 ? tips : false}>
<div
key={num}
role="radio"
aria-checked={num === value}
aria-disabled={itemDisabled}
tabIndex={itemDisabled ? -1 : 0}
className={classNames(styles.numberItem, {
[styles.active]: num === value && value != null,
[styles.itemDisabled]: itemDisabled && !disabled
})}
onClick={() => handleSelect(num)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleSelect(num);
}
}}
>
{num}
{num === 0 && (
<QuestionCircleOutlined
style={{
marginLeft: 4,
fontSize: 11,
color: 'var(--ant-color-text-tertiary)'
}}
/>
)}
</div>
</Tooltip>
</div>
);
})}
@@ -91,14 +91,13 @@ const AddModal: React.FC<AddModalProps> = ({
const [loading, setLoading] = useState(false);
const {
detailData,
detailData: instanceTypeList,
loading: instanceTypesLoading,
fetchData
} = useQueryInstanceTypes();
const { detailData: templatesData, fetchData: fetchTemplates } =
useQueryTemplates();
const instanceTypeList = detailData?.items || [];
const templateList = templatesData?.items || [];
// const readonly = action === PageAction.VIEW;
const readonly = false;
@@ -180,7 +179,7 @@ const AddModal: React.FC<AddModalProps> = ({
);
};
// initial
// initial for first
const applyAutoSelection = (
instanceTypes: InstanceTypeItem[],
templates: TemplateItem[]
@@ -230,13 +229,12 @@ const AddModal: React.FC<AddModalProps> = ({
if (action === PageAction.CREATE) {
const session = ++sessionRef.current;
Promise.all([
fetchData({ page: 1, perPage: 100 }),
fetchTemplates({ page: -1 })
]).then(([instanceRes, templatesRes]) => {
if (sessionRef.current !== session) return;
applyAutoSelection(instanceRes?.items || [], templatesRes?.items || []);
});
Promise.all([fetchData({ page: -1 }), fetchTemplates({ page: -1 })]).then(
([instanceResItems, templatesRes]) => {
if (sessionRef.current !== session) return;
applyAutoSelection(instanceResItems || [], templatesRes?.items || []);
}
);
}
}, [open, shouldAutoSelectResource, action]);
@@ -3,7 +3,6 @@ import { TemplateCard } from '@gpustack/core-ui';
import { Empty, Spin } from 'antd';
import _ from 'lodash';
import styled from 'styled-components';
import { getAcceleratorMax } from '../config';
import { InstanceTypeItem as InstanceTypeItemModel } from '../config/types';
import InstanceTypeItem from './instance-type-item';
@@ -20,34 +19,17 @@ interface InstanceTypeListProps {
loading?: boolean;
}
// CPU types always have stock (no accelerator). Accelerated types are
// considered available when at least one tier offers onceMaxRequest > 0.
const isAvailable = (item: InstanceTypeItemModel) => {
if (!item.spec?.acceleratable) return true;
return getAcceleratorMax(item.status?.acceleratorTiers) > 0;
};
const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
value,
onChange,
dataList = [],
loading
}) => {
const handleSelect = (
item: InstanceTypeItemModel & { disabled?: boolean }
) => {
const handleSelect = (item: InstanceTypeItemModel) => {
if (item.disabled || value === item.name) return;
onChange?.(item);
};
const filterList = dataList.map((item) => {
const available = isAvailable(item);
return {
...item,
disabled: !available
};
});
if (!dataList.length) {
if (loading) {
return (
@@ -65,7 +47,7 @@ const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
return (
<TypeGrid>
{filterList.map((item) => {
{dataList.map((item) => {
const name = item.name;
return (
<TemplateCard
@@ -8,6 +8,7 @@ export interface FormData {
displayName?: string | null;
description?: string | null;
enable_ssh?: boolean;
storageMode?: string;
spec: {
type: string;
image: string;
+35 -15
View File
@@ -31,7 +31,7 @@ import { DefaultImagePullPolicy } from '../../templates/config';
import TemplateBasicForm, {
BasicResourceMax
} from '../../templates/forms/basic';
import { pickCandidateForAccelerator } from '../config';
import { pickCandidateForAccelerator, StorageModeValueMap } from '../config';
import { FormData, InstanceTypeItem, ListItem } from '../config/types';
import instanceStyles from '../styles/instances.module.less';
import Basic from './basic';
@@ -142,11 +142,10 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
updateActiveKey
} = useScrollActiveChange({
initalActiveKeys: [TABKeysMap.BASIC],
initialCollapseKeys: [
TABKeysMap.INSTANCE_TYPE,
TABKeysMap.TEMPLATE,
TABKeysMap.STORAGE
]
initialCollapseKeys:
action === PageAction.EDIT
? []
: [TABKeysMap.INSTANCE_TYPE, TABKeysMap.TEMPLATE, TABKeysMap.STORAGE]
});
useEffect(() => {
@@ -166,10 +165,14 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
);
useEffect(() => {
if (hasSSHPort && !form.getFieldValue('enable_ssh')) {
if (
hasSSHPort &&
!form.getFieldValue('enable_ssh') &&
action === PageAction.CREATE
) {
form.setFieldValue('enable_ssh', true);
}
}, [hasSSHPort, form]);
}, [hasSSHPort, form, action]);
const segmentOptions = useMemo(
() => [
@@ -272,6 +275,12 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
}));
rawHandleOnFinishFailed({ ...errorInfo, errorFields });
};
const detectMode = (volume?: FormData['spec']['volume']) => {
if (volume?.persistent?.name || volume?.persistentTemplate?.name) {
return StorageModeValueMap.Persistent;
}
return StorageModeValueMap.Temporary;
};
useEffect(() => {
if (!open) {
@@ -286,7 +295,8 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
) {
form.setFieldsValue({
...currentData,
enable_ssh: !!currentData?.spec?.sshPublicKeys?.length
enable_ssh: !!currentData?.spec?.sshPublicKeys?.length,
storageMode: detectMode(currentData?.spec?.volume)
});
}
}, [action, currentData, form, open, realAction, instanceTypeList]);
@@ -297,14 +307,13 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
(item: any) => item?.protocol === 'TCP' && item?.port === SSH_PORT
);
if (values.enable_ssh && !submittedHasSSHPort) {
if (!submittedHasSSHPort) {
submittedPorts.push({
protocol: 'TCP',
port: SSH_PORT,
name: 'SSH'
});
}
console.log('submit values', values, submittedPorts);
await onFinish({
..._.omit(values, ['enable_ssh']),
spec: {
@@ -348,6 +357,14 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
navigate('/gpu-service/public-keys');
};
const handleOnEnableSSHChange = (e: any) => {
const checked = e.target.checked;
console.log('enable ssh change', checked);
if (!checked) {
form.setFieldValue(['spec', 'sshPublicKeys'], []);
}
};
return (
<ScrollSpyTabs
ref={scrollTabsRef}
@@ -387,7 +404,8 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
}
}
},
enable_ssh: false
enable_ssh: false,
storageMode: StorageModeValueMap.Temporary
}}
>
<Basic action={formAction} disabled={disabled} />
@@ -451,19 +469,21 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
style={{ marginBottom: 8 }}
>
<CheckboxField
onChange={handleOnEnableSSHChange}
disabled={disabled}
label={intl.formatMessage({
id: 'gpuservice.instance.ssh.enable'
})}
></CheckboxField>
</Form.Item>
{sshEnabled && (
{
<div className={instanceStyles.sshkeySelection}>
<Form.Item<FormData>
name={['spec', 'sshPublicKeys']}
style={{
marginBottom: 12
}}
hidden={!sshEnabled}
normalize={(value) =>
Array.isArray(value)
? value?.map((item) => ({ name: item }))
@@ -476,7 +496,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
})}
rules={[
{
required: true,
required: sshEnabled,
message: getRuleMessage('select', 'gpuservice.publicKey')
}
]}
@@ -525,7 +545,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
></MultipleSelect>
</Form.Item>
</div>
)}
}
</Form>
</ScrollSpyTabs>
);
@@ -82,11 +82,15 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
const renderInstanceType = () => {
const description = JSON.parse(currentData?.description || '{}').spec || {};
return (
<SelectedCard>
<SelectedCard
style={{
background: 'var(--ant-color-bg-container-disabled)',
color: 'var(--ant-color-text-disabled)'
}}
>
<Flex align="flex-start" orientation="vertical">
<span
style={{
color: 'var(--ant-color-text)',
fontWeight: 400
}}
>
@@ -123,9 +127,9 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
<Form.Item<FormData>
name={['spec', 'resources', 'accelerator']}
hidden={action === PageAction.EDIT}
normalize={(value) => (value ? _.toString(value) : undefined)}
normalize={(value) => (value != null ? _.toString(value) : undefined)}
getValueProps={(value) => ({
value: value ? _.toNumber(value) : undefined
value: value != null ? _.toNumber(value) : undefined
})}
rules={[
{
@@ -142,12 +146,12 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
)
);
}
if (num < 1) {
if (num < 0) {
return Promise.reject(
new Error(
intl.formatMessage(
{ id: 'gpuservice.instance.gpuCount.min' },
{ count: 1 }
{ count: 0 }
)
)
);
@@ -158,11 +162,14 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
]}
>
<NumberSelection
min={1}
min={0}
onChange={handleOnGPUCountChange}
max={maxGpuCount}
step={1}
required
tips={intl.formatMessage({
id: 'gpuservice.instance.gpuCount.zero'
})}
disabled={disabled || action === PageAction.EDIT}
labelExtra={
!maxGpuCount &&
@@ -9,7 +9,6 @@ import {
import { useIntl } from '@umijs/max';
import { Button, Flex, Form, Radio } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { FormData as StorageFormData } from '../../storage/config/types';
import useCreateStorage from '../../storage/services/use-create-storage';
import useQueryStorage from '../../storage/services/use-query-storage';
@@ -19,13 +18,6 @@ import StorageOverlay from './storage-overlay';
const DEFAULT_TEMP_CAPACITY_GB = 50;
const detectMode = (volume?: FormData['spec']['volume']) => {
if (volume?.persistent || volume?.persistentTemplate) {
return StorageModeValueMap.Persistent;
}
return StorageModeValueMap.Temporary;
};
const StorageVolume = ({
disabled,
action
@@ -36,16 +28,7 @@ const StorageVolume = ({
const intl = useIntl();
const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance<FormData>();
const currentVolume = Form.useWatch(['spec', 'volume'], form);
const [storageMode, setStorageMode] = useState<string>(
StorageModeValueMap.Temporary
);
useEffect(() => {
if (!currentVolume) return;
setStorageMode(detectMode(currentVolume));
}, [currentVolume]);
const storageMode = Form.useWatch('storageMode', form);
const { fetchData: createStorage } = useCreateStorage();
const { detailData: storageData, fetchData: fetchStorage } =
useQueryStorage();
@@ -81,7 +64,6 @@ const StorageVolume = ({
};
const handleModeChange = (mode: string) => {
setStorageMode(mode);
applyMode(mode);
};
@@ -99,49 +81,55 @@ const StorageVolume = ({
return (
<>
<div data-field="storage"></div>
<Flex align="center" justify="space-between" style={{ marginBlock: 16 }}>
<Radio.Group
disabled={disabled}
value={storageMode}
style={{ display: 'flex', gap: 12 }}
onChange={(e) => handleModeChange(e.target.value)}
options={[
{
label: (
<LabelInfo
description={intl.formatMessage({
id: 'gpuservice.storage.temporary.tips'
})}
label={
<span className="text-primary">
{intl.formatMessage({
id: 'gpuservice.storage.temporary'
})}
</span>
}
/>
),
value: StorageModeValueMap.Temporary
},
{
label: (
<LabelInfo
description={intl.formatMessage({
id: 'gpuservice.storage.persistentVolume.tips'
})}
label={
<span className="text-primary">
{intl.formatMessage({
id: 'gpuservice.storage.persistentVolume'
})}
</span>
}
/>
),
value: StorageModeValueMap.Persistent
}
]}
/>
<Flex
align="center"
justify="space-between"
style={{ marginBottom: 16, paddingTop: 8 }}
>
<Form.Item name="storageMode" noStyle>
<Radio.Group
disabled={disabled}
value={storageMode}
style={{ display: 'flex', gap: 12 }}
onChange={(e) => handleModeChange(e.target.value)}
options={[
{
label: (
<LabelInfo
description={intl.formatMessage({
id: 'gpuservice.storage.temporary.tips'
})}
label={
<span className="text-primary">
{intl.formatMessage({
id: 'gpuservice.storage.temporary'
})}
</span>
}
/>
),
value: StorageModeValueMap.Temporary
},
{
label: (
<LabelInfo
description={intl.formatMessage({
id: 'gpuservice.storage.persistentVolume.tips'
})}
label={
<span className="text-primary">
{intl.formatMessage({
id: 'gpuservice.storage.persistentVolume'
})}
</span>
}
/>
),
value: StorageModeValueMap.Persistent
}
]}
/>
</Form.Item>
{action === PageAction.CREATE &&
storageMode === StorageModeValueMap.Persistent && (
<Button
@@ -35,7 +35,6 @@ type ConnectEntry =
const getConnectEntries = (record: ListItem): ConnectEntry[] => {
const ip = record.status?.hostIPs?.[0]?.ip;
const ports = record.status?.ports || [];
const hasSshKey = !!record.spec?.sshPublicKeys?.length;
const configPorts = record.spec?.ports || [];
if (!ip) {
@@ -45,7 +44,9 @@ const getConnectEntries = (record: ListItem): ConnectEntry[] => {
return ports
.filter((p) => p.nodePort)
.map<ConnectEntry>((p) => {
const isSsh = hasSshKey && p.protocol === 'TCP' && p.port === 22;
const isSsh =
(p.protocol === 'TCP' && p.port === 22) ||
_.includes(_.toLower(p.name), 'ssh');
return isSsh
? {
type: 'ssh',
+5 -33
View File
@@ -1,17 +1,10 @@
import { PageAction } from '@/config';
import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
import useTableFetch from '@/hooks/use-table-fetch';
import { PlusOutlined } from '@ant-design/icons';
import {
DeleteModal,
DropdownButtons,
FilterBar,
IconFont,
NoResult
} from '@gpustack/core-ui';
import { DeleteModal, FilterBar, IconFont, NoResult } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { Button, ConfigProvider, message, Modal, Space, Table } from 'antd';
import { ConfigProvider, message, Modal, Table } from 'antd';
import _ from 'lodash';
import PageBox from '../../_components/page-box';
import {
@@ -22,7 +15,6 @@ import {
import AddModal from './components/add-modal';
import ViewEventsModal from './components/view-events-modal';
import ViewLogsModal from './components/view-logs-modal';
import { batchActionList } from './config';
import { FormData, ListItem } from './config/types';
import useCreateInstance from './hooks/use-create-instance';
import useInstancesColumns from './hooks/use-instances-columns';
@@ -164,29 +156,9 @@ const GPUService: React.FC = () => {
handleInputChange={handleNameChange}
rowSelection={rowSelection}
widths={{ input: 300 }}
right={
<Space size={16}>
<Button
icon={<PlusOutlined />}
type="primary"
onClick={openCreateInstanceModal}
>
{intl.formatMessage({ id: 'gpuservice.instance.add' })}
</Button>
<DropdownButtons
items={batchActionList}
extra={
rowSelection.selectedRowKeys.length > 0 && (
<span>({rowSelection.selectedRowKeys.length})</span>
)
}
size="large"
showText={true}
disabled={!rowSelection.selectedRowKeys.length}
onSelect={handleBatchActionSelect}
/>
</Space>
}
handleClickPrimary={openCreateInstanceModal}
buttonText={intl.formatMessage({ id: 'gpuservice.instance.add' })}
handleDeleteByBatch={handleDeleteBatch}
/>
<ConfigProvider renderEmpty={renderEmpty}>
<Table
@@ -1,14 +1,18 @@
import { useQueryData } from '@gpustack/core-ui';
import { useCallback } from 'react';
import React from 'react';
import { queryGPUServiceInstanceTypes } from '../apis';
import { getAcceleratorMax } from '../config';
import { InstanceTypeItem } from '../config/types';
type InstanceType = InstanceTypeItem & {
disabled?: boolean;
};
export default function useQueryInstanceTypes() {
const fetchDetail = useCallback(
(params: Global.SearchParams = { page: 1, perPage: 100 }, options?: any) =>
queryGPUServiceInstanceTypes(params, options),
[]
);
const fetchDetail = (
params: Global.SearchParams = { page: 1, perPage: 100 },
options?: any
) => queryGPUServiceInstanceTypes(params, options);
const { detailData, loading, cancelRequest, fetchData } = useQueryData<
Global.PageResponse<InstanceTypeItem>,
@@ -18,10 +22,31 @@ export default function useQueryInstanceTypes() {
key: 'instanceTypes'
});
const [dataList, setDataList] = React.useState<InstanceType[]>([]);
const isAvailable = (item: InstanceTypeItem) => {
if (!item.spec?.acceleratable) return true;
return getAcceleratorMax(item.status?.acceleratorTiers) > 0;
};
const queryInstanceTypes = async (
params: Global.SearchParams = { page: -1 }
) => {
const res = await fetchData(params);
const list = (res?.items || []).map((item) => ({
...item,
disabled: !isAvailable(item)
}));
console.log('queryInstanceTypes', list);
setDataList(list);
return list;
};
return {
detailData,
detailData: dataList,
loading,
cancelRequest,
fetchData
fetchData: queryInstanceTypes
};
}
+1 -1
View File
@@ -98,7 +98,7 @@ const GPUServicePublicKeys: React.FC = () => {
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataSource.dataList}
image={<IconFont type="icon-storage-outlined" />}
image={<IconFont type="icon-ssh-outlined" />}
filters={_.pick(queryParams, ['search'])}
noFoundText={intl.formatMessage({
id: 'noresult.gpuservice.sshkey.nofound'
+1 -1
View File
@@ -112,7 +112,7 @@ const GPUServiceStorage: React.FC = () => {
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataSource.dataList}
image={<IconFont type="icon-storage-outlined" />}
image={<IconFont type="icon-database-outlined" />}
filters={_.pick(queryParams, ['search'])}
noFoundText={intl.formatMessage({
id: 'noresult.gpuservice.storage.nofound'
@@ -2,7 +2,7 @@ import ascendLogo from '@/assets/logo/ascend.png';
import CambriconPNG from '@/assets/logo/cambricon.png';
import hyponPNG from '@/assets/logo/hygon.png';
import iluvatarWEBP from '@/assets/logo/Iluvatar.png';
import jupyterLogo from '@/assets/logo/jupyter.png';
import jupyterLogo from '@/assets/logo/jupyter_logo.png';
import metaxLogo from '@/assets/logo/metax.png';
import mooreLogo from '@/assets/logo/moore-logo.png';
import nvidiaLogo from '@/assets/logo/nvidia.png';
@@ -11,7 +11,7 @@ import sgLangLogo from '@/assets/logo/sglang.png';
import theadLogoEN from '@/assets/logo/t-head-en.png';
import theadLogoZH from '@/assets/logo/t-head-zh.png';
import tensorflowkLogo from '@/assets/logo/tensorflow.svg';
import ubuntuLogo from '@/assets/logo/ubuntu.png';
import ubuntuLogo from '@/assets/logo/ubuntu_logo.png';
import vllmLogo from '@/assets/logo/vllm.png';
import {
GPUsConfigs,
@@ -50,7 +50,9 @@ const manufacturerLabelMap: Record<string, string> = Object.values(
{ cpu: 'CPU' } as Record<string, string>
);
const matchImageLogo = (image: string | undefined): string | null => {
const matchImageLogo = (
image: string | undefined
): { logo: string; type: string } | null => {
if (!image) return null;
const lower = image.toLowerCase();
let matched: keyof typeof imageLogoMap | null = null;
@@ -64,7 +66,12 @@ const matchImageLogo = (image: string | undefined): string | null => {
}
}
);
return matched ? imageLogoMap[matched] : null;
return matched
? {
logo: imageLogoMap[matched],
type: matched
}
: null;
};
const StyledCard = styled(TemplateCard)`
@@ -147,8 +154,13 @@ const TemplateCardItem: React.FC<TemplateCardProps> = ({ data, onSelect }) => {
const renderLogo = () => {
const imageLogo = matchImageLogo(data.spec?.image);
if (imageLogo) {
return <LogoImg src={imageLogo} height={22} />;
if (imageLogo?.logo) {
return (
<LogoImg
src={imageLogo.logo}
height={imageLogo.type === 'ubuntu' ? 26 : 22}
/>
);
}
switch (data.manufacturer) {
case manfacturerValueMap.NVIDIA: