fix: alignment api

This commit is contained in:
jialin
2026-05-11 19:49:21 +08:00
committed by jialin
parent 9e0dd5b003
commit b13ba0a229
33 changed files with 738 additions and 365 deletions
@@ -1,3 +1,4 @@
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import Separator from '@/pages/llmodels/components/separator';
import { SearchOutlined } from '@ant-design/icons';
@@ -61,20 +62,32 @@ const AddModal: React.FC<AddModalProps> = ({
}) => {
const intl = useIntl();
const form = useRef<any>(null);
const autoSelectedRef = useRef(false);
const [selectedInstanceType, setSelectedInstanceType] = useState<string>();
const [selectedManufacturer, setSelectedManufacturer] = useState<string>();
const [templateId, setTemplateId] = useState<number>();
const [instanceKeyword, setInstanceKeyword] = useState('');
const [templateKeyword, setTemplateKeyword] = useState('');
const { detailData, fetchData } = useQueryInstanceTypes();
const {
detailData,
loading: instanceTypesLoading,
fetchData
} = useQueryInstanceTypes();
const { detailData: templatesData, fetchData: fetchTemplates } =
useQueryTemplates();
useEffect(() => {
if (open) {
autoSelectedRef.current = false;
fetchData({});
fetchTemplates({ page: 1, perPage: 100 });
} else {
setSelectedInstanceType(undefined);
setSelectedManufacturer(undefined);
setTemplateId(undefined);
setInstanceKeyword('');
setTemplateKeyword('');
}
}, [open, fetchData, fetchTemplates]);
@@ -119,6 +132,59 @@ const AddModal: React.FC<AddModalProps> = ({
);
}, [templateKeyword, manufacturerMatchedTemplates]);
useEffect(() => {
if (!open) return;
if (action !== PageAction.CREATE) return;
if (autoSelectedRef.current) return;
if (!instanceTypeList.length) return;
if (!templatesData) return;
const firstInstanceType = instanceTypeList[0];
const manufacturer = firstInstanceType.spec?.manufacturer;
const name = firstInstanceType.metadata?.name;
const matchedTemplate = manufacturer
? templateList.find((t) => t.manufacturer === manufacturer)
: templateList[0];
autoSelectedRef.current = true;
setSelectedInstanceType(name);
setSelectedManufacturer(manufacturer);
if (matchedTemplate) {
setTemplateId(matchedTemplate.id);
}
const applyToForm = () => {
const currentSpec = form.current?.getFieldsValue()?.spec || {};
const updatedSpec = {
...currentSpec,
type: name,
resources: { ...(currentSpec.resources || {}), accelerator: '1' }
};
if (matchedTemplate) {
form.current?.setFieldsValue({
manufacturer: matchedTemplate.manufacturer,
spec: {
...updatedSpec,
...matchedTemplate.spec,
resources: {
...(updatedSpec.resources || {}),
...(matchedTemplate.spec?.resources || {})
}
}
});
} else {
form.current?.setFieldsValue({ spec: updatedSpec });
}
};
if (form.current) {
applyToForm();
} else {
queueMicrotask(applyToForm);
}
}, [open, action, instanceTypeList, templateList, templatesData]);
const handleSubmit = () => {
form.current?.submit();
};
@@ -229,15 +295,12 @@ const AddModal: React.FC<AddModalProps> = ({
onChange={(e) => setInstanceKeyword(e.target.value)}
/>
</div>
{filteredInstanceTypes.length > 0 ? (
<InstanceTypeList
value={selectedInstanceType}
dataList={filteredInstanceTypes}
onChange={handleInstanceTypeChange}
/>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
<InstanceTypeList
value={selectedInstanceType}
dataList={filteredInstanceTypes}
loading={instanceTypesLoading}
onChange={handleInstanceTypeChange}
/>
</PanelBody>
</ColumnWrapper>
<Separator></Separator>
@@ -0,0 +1,155 @@
import { AutoTooltip, IconFont, StatusTag, ThemeTag } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Flex } from 'antd';
import styled from 'styled-components';
import {
convertKiToGi,
InstanceTypePhaseLabelMap,
InstanceTypePhaseStatus
} from '../config';
import { InstanceTypeItem as InstanceTypeItemModel } from '../config/types';
const Title = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 12px;
color: var(--ant-color-text);
font-weight: 500;
`;
const Meta = styled.div`
display: grid;
gap: 8px;
color: var(--ant-color-text-secondary);
font-size: 13px;
.meta-row {
display: flex;
align-items: center;
gap: 8px;
color: var(--ant-color-text-tertiary);
}
.dot {
width: 3px;
height: 3px;
border-radius: 50%;
background-color: var(--ant-color-text-quaternary);
flex: none;
}
.meta-item {
display: inline-flex;
align-items: center;
gap: 4px;
}
.meta-icon {
font-size: 14px;
color: var(--ant-color-text-quaternary);
}
`;
interface InstanceTypeItemProps {
item: InstanceTypeItemModel;
showStatus?: boolean;
}
const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({
item,
showStatus = true
}) => {
const intl = useIntl();
const name = item.metadata?.name;
const acceleratable = item.spec?.acceleratable;
const product = item.spec?.product;
const displayName = product || (!acceleratable ? 'CPU' : name);
const manufacturer = item.spec?.manufacturer?.toUpperCase();
return (
<>
<Title>
<Flex gap={8} align="center">
<AutoTooltip ghost minWidth={20} maxWidth={180}>
{displayName}
</AutoTooltip>
{acceleratable && manufacturer && (
<span
style={{
color: 'var(--ant-color-text-tertiary)',
fontWeight: 400
}}
>
<ThemeTag color="purple">{manufacturer}</ThemeTag>
</span>
)}
</Flex>
{showStatus && (
<Flex gap={8} align="center">
{item.status?.phase && (
<StatusTag
statusValue={{
status:
InstanceTypePhaseStatus[item.status.phase] ?? 'inactive',
text:
InstanceTypePhaseLabelMap[item.status.phase] ??
item.status.phase,
message: ''
}}
/>
)}
</Flex>
)}
</Title>
<Meta>
<span style={{ display: 'flex', height: 15 }}>
{acceleratable && (
<span className="meta-row">
<span className="meta-item">
<IconFont type="icon-gpu1" className="meta-icon" />
<Flex align="center" gap={4}>
<span>
{intl.formatMessage({ id: 'gpuservice.instance.memory' })}
</span>
<span>{convertKiToGi(item.spec?.memory) ?? '-'}</span>
</Flex>
</span>
<span className="dot"></span>
<span className="meta-item">
<IconFont type="icon-cube" className="meta-icon" />
<Flex align="center" gap={4}>
<span>
{intl.formatMessage({ id: 'gpuservice.instance.stock' })}
</span>
<span>{item.status?.accelerator?.remaining ?? '-'}</span>
</Flex>
</span>
</span>
)}
</span>
<span className="meta-row">
<span className="meta-item">
<IconFont type="icon-ram-02" className="meta-icon" />
<Flex align="center" gap={4}>
<span>
{intl.formatMessage({ id: 'gpuservice.instance.ram' })}
</span>
<span>{item.status?.ram?.capacity ?? '-'}</span>
</Flex>
</span>
<span className="dot"></span>
<span className="meta-item">
<IconFont type="icon-cpu" className="meta-icon" />
<Flex align="center" gap={4}>
<span>vCPU</span>
<span>{item.status?.cpu?.capacity ?? '-'}</span>
</Flex>
</span>
</span>
</Meta>
</>
);
};
export default InstanceTypeItem;
@@ -1,13 +1,11 @@
import { AutoTooltip, StatusTag, TemplateCard } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Flex, Tag } from 'antd';
import FileSkeleton from '@/pages/llmodels/components/model-source/file-skeleton';
import { TemplateCard } from '@gpustack/core-ui';
import { Empty, Spin } from 'antd';
import _ from 'lodash';
import styled from 'styled-components';
import {
InstanceTypePhaseLabelMap,
InstanceTypePhaseStatus,
InstanceTypePhaseValueMap
} from '../config';
import { InstanceTypeItem } from '../config/types';
import { InstanceTypePhaseValueMap } from '../config';
import { InstanceTypeItem as InstanceTypeItemModel } from '../config/types';
import InstanceTypeItem from './instance-type-item';
const TypeGrid = styled.div`
display: flex;
@@ -15,53 +13,42 @@ const TypeGrid = styled.div`
gap: 16px;
`;
const TypeName = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 12px;
color: var(--ant-color-text);
font-weight: 500;
`;
const TypeMeta = styled.div`
display: grid;
gap: 8px;
color: var(--ant-color-text-secondary);
font-size: 13px;
.meta-row {
display: flex;
align-items: center;
gap: 16px;
}
.icon {
color: var(--ant-color-text-quaternary);
}
`;
interface InstanceTypeListProps {
value?: string;
onChange?: (item: InstanceTypeItem) => void;
dataList?: InstanceTypeItem[];
onChange?: (item: InstanceTypeItemModel) => void;
dataList?: InstanceTypeItemModel[];
loading?: boolean;
}
const isAvailable = (item: InstanceTypeItem) =>
const isAvailable = (item: InstanceTypeItemModel) =>
item.status?.phase === InstanceTypePhaseValueMap.Active;
const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
value,
onChange,
dataList = []
dataList = [],
loading
}) => {
const intl = useIntl();
const handleSelect = (item: InstanceTypeItem) => {
const handleSelect = (item: InstanceTypeItemModel) => {
if (!isAvailable(item)) return;
onChange?.(item);
};
if (!dataList.length) {
if (loading) {
return (
<Spin spinning size="middle">
<TypeGrid style={{ minHeight: 200 }}>
{_.times(6, (index: number) => (
<FileSkeleton key={index} counts={3} itemHeight={106} />
))}
</TypeGrid>
</Spin>
);
}
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />;
}
return (
<TypeGrid>
{dataList.map((item) => {
@@ -78,58 +65,7 @@ const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
disabled={disabled}
onClick={() => handleSelect(item)}
>
<TypeName>
<Flex gap={8} align="center">
<AutoTooltip ghost minWidth={20}>
{name}
</AutoTooltip>
</Flex>
<Flex gap={8} align="center">
{item.status?.phase && (
<StatusTag
statusValue={{
status:
InstanceTypePhaseStatus[item.status.phase] ??
'inactive',
text:
InstanceTypePhaseLabelMap[item.status.phase] ??
item.status.phase,
message: ''
}}
/>
)}
{item.spec?.acceleratable && (
<Tag
style={{
fontWeight: 400,
color: 'var(--ant-color-text-tertiary)'
}}
>
<span>
{intl.formatMessage({ id: 'gpuservice.instance.stock' })}{' '}
{item.status?.accelerator?.remaining ?? '-'}
</span>
</Tag>
)}
</Flex>
</TypeName>
<TypeMeta>
<span style={{ display: 'flex', height: 15 }}>
{item.spec?.acceleratable && (
<span className="meta-row">
{intl.formatMessage({ id: 'gpuservice.instance.memory' })}{' '}
{item.spec?.memory ?? '-'}
</span>
)}
</span>
<span className="meta-row gap-16">
<span>
{intl.formatMessage({ id: 'gpuservice.instance.ram' })}{' '}
{item.status?.ram?.capacity ?? '-'}
</span>
<span>vCPU {item.status?.cpu?.capacity ?? '-'}</span>
</span>
</TypeMeta>
<InstanceTypeItem item={item} />
</TemplateCard>
);
})}