| @@ -4,9 +4,8 @@ | |||
| * @Description: 用于 BasicInfo 和 BasicTableInfo 组件的子组件 | |||
| */ | |||
| import { Link } from '@umijs/max'; | |||
| import { Typography } from 'antd'; | |||
| import React from 'react'; | |||
| import BasicInfoItemValue from './BasicInfoItemValue'; | |||
| import { type BasicInfoData, type BasicInfoLink } from './types'; | |||
| type BasicInfoItemProps = { | |||
| @@ -15,12 +14,14 @@ type BasicInfoItemProps = { | |||
| classPrefix: string; | |||
| }; | |||
| export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) { | |||
| function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) { | |||
| const { label, value, format, ellipsis } = data; | |||
| const formatValue = format ? format(value) : value; | |||
| const myClassName = `${classPrefix}__item`; | |||
| let valueComponent = undefined; | |||
| if (Array.isArray(formatValue)) { | |||
| if (React.isValidElement(formatValue)) { | |||
| valueComponent = formatValue; | |||
| } else if (Array.isArray(formatValue)) { | |||
| valueComponent = ( | |||
| <div className={`${myClassName}__value-container`}> | |||
| {formatValue.map((item: BasicInfoLink) => ( | |||
| @@ -35,11 +36,6 @@ export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemPr | |||
| ))} | |||
| </div> | |||
| ); | |||
| } else if (React.isValidElement(formatValue)) { | |||
| // 这个判断必须在下面的判断之前 | |||
| valueComponent = ( | |||
| <BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} /> | |||
| ); | |||
| } else if (typeof formatValue === 'object' && formatValue) { | |||
| valueComponent = ( | |||
| <BasicInfoItemValue | |||
| @@ -65,49 +61,4 @@ export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemPr | |||
| ); | |||
| } | |||
| type BasicInfoItemValueProps = { | |||
| ellipsis?: boolean; | |||
| classPrefix: string; | |||
| value: string | React.ReactNode; | |||
| link?: string; | |||
| url?: string; | |||
| }; | |||
| export function BasicInfoItemValue({ | |||
| value, | |||
| link, | |||
| url, | |||
| ellipsis, | |||
| classPrefix, | |||
| }: BasicInfoItemValueProps) { | |||
| const myClassName = `${classPrefix}__item__value`; | |||
| let component = undefined; | |||
| if (url && value) { | |||
| component = ( | |||
| <a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer"> | |||
| {value} | |||
| </a> | |||
| ); | |||
| } else if (link && value) { | |||
| component = ( | |||
| <Link to={link} className={`${myClassName}__link`}> | |||
| {value} | |||
| </Link> | |||
| ); | |||
| } else if (React.isValidElement(value)) { | |||
| return value; | |||
| } else { | |||
| component = <span className={`${myClassName}__text`}>{value ?? '--'}</span>; | |||
| } | |||
| return ( | |||
| <div className={myClassName}> | |||
| <Typography.Text | |||
| ellipsis={ellipsis ? { tooltip: value } : false} | |||
| style={{ fontSize: 'inherit' }} | |||
| > | |||
| {component} | |||
| </Typography.Text> | |||
| </div> | |||
| ); | |||
| } | |||
| export default BasicInfoItem; | |||
| @@ -0,0 +1,54 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-11-29 09:27:19 | |||
| * @Description: 用于 BasicInfoItem 的组件 | |||
| */ | |||
| import { Link } from '@umijs/max'; | |||
| import { Typography } from 'antd'; | |||
| import React from 'react'; | |||
| type BasicInfoItemValueProps = { | |||
| ellipsis?: boolean; | |||
| classPrefix: string; | |||
| value: string | React.ReactNode; | |||
| link?: string; | |||
| url?: string; | |||
| }; | |||
| function BasicInfoItemValue({ value, link, url, ellipsis, classPrefix }: BasicInfoItemValueProps) { | |||
| if (React.isValidElement(value)) { | |||
| return value; | |||
| } | |||
| const myClassName = `${classPrefix}__item__value`; | |||
| let component = undefined; | |||
| if (url && value) { | |||
| component = ( | |||
| <a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer"> | |||
| {value} | |||
| </a> | |||
| ); | |||
| } else if (link && value) { | |||
| component = ( | |||
| <Link to={link} className={`${myClassName}__link`}> | |||
| {value} | |||
| </Link> | |||
| ); | |||
| } else { | |||
| component = <span className={`${myClassName}__text`}>{value ?? '--'}</span>; | |||
| } | |||
| return ( | |||
| <div className={myClassName}> | |||
| <Typography.Text | |||
| ellipsis={ellipsis ? { tooltip: value } : false} | |||
| style={{ fontSize: 'inherit' }} | |||
| > | |||
| {component} | |||
| </Typography.Text> | |||
| </div> | |||
| ); | |||
| } | |||
| export default BasicInfoItemValue; | |||
| @@ -1,48 +0,0 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-11-29 09:27:19 | |||
| * @Description: 用于 BasicInfo 和 BasicTableInfo 组件的常用转化格式 | |||
| */ | |||
| // 格式化日期 | |||
| export { formatDate } from '@/utils/date'; | |||
| /** | |||
| * 格式化字符串数组 | |||
| * @param value - 字符串数组 | |||
| * @returns 逗号分隔的字符串 | |||
| */ | |||
| export const formatList = (value: string[] | null | undefined): string => { | |||
| if ( | |||
| value === undefined || | |||
| value === null || | |||
| Array.isArray(value) === false || | |||
| value.length === 0 | |||
| ) { | |||
| return '--'; | |||
| } | |||
| return value.join(','); | |||
| }; | |||
| /** | |||
| * 格式化布尔值 | |||
| * @param value - 布尔值 | |||
| * @returns "是" 或 "否" | |||
| */ | |||
| export const formatBoolean = (value: boolean): string => { | |||
| return value ? '是' : '否'; | |||
| }; | |||
| type FormatEnum = (value: string | number) => string; | |||
| /** | |||
| * 格式化枚举 | |||
| * @param options - 枚举选项 | |||
| * @returns 格式化枚举函数 | |||
| */ | |||
| export const formatEnum = (options: { value: string | number; label: string }[]): FormatEnum => { | |||
| return (value: string | number) => { | |||
| const option = options.find((item) => item.value === value); | |||
| return option ? option.label : '--'; | |||
| }; | |||
| }; | |||
| @@ -1,9 +1,8 @@ | |||
| import classNames from 'classnames'; | |||
| import React from 'react'; | |||
| import { BasicInfoItem } from './components'; | |||
| import BasicInfoItem from './BasicInfoItem'; | |||
| import './index.less'; | |||
| import type { BasicInfoData, BasicInfoLink } from './types'; | |||
| export * from './format'; | |||
| export type { BasicInfoData, BasicInfoLink }; | |||
| type BasicInfoProps = { | |||
| @@ -3,12 +3,12 @@ export type BasicInfoData = { | |||
| label: string; | |||
| value?: any; | |||
| ellipsis?: boolean; | |||
| format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; | |||
| format?: (_value?: any) => string | React.ReactNode | BasicInfoLink | BasicInfoLink[] | undefined; | |||
| }; | |||
| // 值为链接的类型 | |||
| export type BasicInfoLink = { | |||
| value: string; | |||
| value?: string; | |||
| link?: string; | |||
| url?: string; | |||
| }; | |||
| @@ -1,8 +1,7 @@ | |||
| import classNames from 'classnames'; | |||
| import { BasicInfoItem } from '../BasicInfo/components'; | |||
| import BasicInfoItem from '../BasicInfo/BasicInfoItem'; | |||
| import { type BasicInfoData, type BasicInfoLink } from '../BasicInfo/types'; | |||
| import './index.less'; | |||
| export * from '../BasicInfo/format'; | |||
| export type { BasicInfoData, BasicInfoLink }; | |||
| type BasicTableInfoProps = { | |||
| @@ -4,16 +4,11 @@ import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import { type NodeStatus } from '@/types'; | |||
| import { parseJsonText } from '@/utils'; | |||
| import { elapsedTime } from '@/utils/date'; | |||
| import { formatDataset } from '@/utils/format'; | |||
| import { formatBoolean, formatDataset, formatDate, formatEnum } from '@/utils/format'; | |||
| import { Flex } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useMemo } from 'react'; | |||
| import ConfigInfo, { | |||
| formatBoolean, | |||
| formatDate, | |||
| formatEnum, | |||
| type BasicInfoData, | |||
| } from '../ConfigInfo'; | |||
| import ConfigInfo, { type BasicInfoData } from '../ConfigInfo'; | |||
| import styles from './index.less'; | |||
| // 格式化优化方向 | |||
| @@ -2,7 +2,6 @@ import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; | |||
| import InfoGroup from '@/components/InfoGroup'; | |||
| import classNames from 'classnames'; | |||
| import styles from './index.less'; | |||
| export * from '@/components/BasicInfo/format'; | |||
| export type { BasicInfoData }; | |||
| type ConfigInfoProps = { | |||
| @@ -6,9 +6,10 @@ import ResourceSelect, { | |||
| } from '@/components/ResourceSelect'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { hyperParameterOptimizedModeOptions } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | |||
| import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select } from 'antd'; | |||
| import { MinusCircleOutlined, PlusCircleOutlined, QuestionCircleOutlined } from '@ant-design/icons'; | |||
| import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select, Tooltip } from 'antd'; | |||
| import { isEqual } from 'lodash'; | |||
| import PopParameterRange from './PopParameterRange'; | |||
| import styles from './index.less'; | |||
| @@ -25,12 +26,48 @@ const schedulerAlgorithms = ['ASHA', 'HyperBand', 'MedianStopping', 'PopulationB | |||
| (name) => ({ label: name, value: name }), | |||
| ); | |||
| const parameterTooltip = `uniform(-5, -1) | |||
| 在 -5.0 和 -1.0 之间均匀采样浮点数 | |||
| quniform(3.2, 5.4, 0.2) | |||
| 在 3.2 和 5.4 之间均匀采样浮点数,四舍五入到 0.2 的倍数 | |||
| loguniform(1e-4, 1e-2) | |||
| 在 0.0001 和 0.01 之间均匀采样浮点数,对数空间采样 | |||
| qloguniform(1e-4, 1e-1, 5e-5) | |||
| 在 0.0001 和 0.01 之间均匀采样浮点数,对数空间采样并四舍五入到 0.00005 的倍数 | |||
| randn(10, 2) | |||
| 在均值为 10,方差为 2 的正态分布中进行随机浮点数抽样 | |||
| qrandn(10, 2, 0.2) | |||
| 在均值为 10,方差为 2 的正态分布中进行随机浮点数抽样,四舍五入到 0.2 的倍数 | |||
| randint(-9, 15) | |||
| 在 -9(包括)到 15(不包括)之间均匀采样整数 | |||
| qrandint(-21, 12, 3) | |||
| 在 -21(包括)到 12(不包括)之间均匀采样整数,四舍五入到 3 的倍数 | |||
| lograndint(1, 10) | |||
| 在 1(包括)到 10(不包括)之间均匀采样整数,对数空间采样 | |||
| qlograndint(1, 10, 2) | |||
| 在 1(包括)到 10(不包括)之间均匀采样整数,对数空间采样并四舍五入到 2 的倍数 | |||
| choice(["a", "b", "c"]) | |||
| 从指定的选项中采样一个选项 | |||
| grid([32, 64, 128]) | |||
| 对这些值进行网格搜索,每个值都将被采样 | |||
| `; | |||
| function ExecuteConfig() { | |||
| const form = Form.useFormInstance(); | |||
| const searchAlgorithm = Form.useWatch('search_alg', form); | |||
| const paramsTypeOptions = searchAlgorithm === 'Ax' ? axParameterOptions : parameterOptions; | |||
| // const parameters = Form.useWatch('parameters', form); | |||
| // console.log('parameters', parameters); | |||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); | |||
| const handleSearchAlgorithmChange = (value: string) => { | |||
| if ( | |||
| @@ -231,7 +268,17 @@ function ExecuteConfig() { | |||
| <div className={styles['hyper-parameter']}> | |||
| <Flex align="center" className={styles['hyper-parameter__header']}> | |||
| <div className={styles['hyper-parameter__header__name']}>参数名称</div> | |||
| <div className={styles['hyper-parameter__header__type']}>参数类型</div> | |||
| <div className={styles['hyper-parameter__header__type']}> | |||
| 参数类型 | |||
| <Tooltip | |||
| title={parameterTooltip} | |||
| placement="top" | |||
| arrow={{ pointAtCenter: true }} | |||
| overlayClassName={styles['hyper-parameter__header__tooltip']} | |||
| > | |||
| <QuestionCircleOutlined /> | |||
| </Tooltip> | |||
| </div> | |||
| <div className={styles['hyper-parameter__header__space']}>取值范围</div> | |||
| <div className={styles['hyper-parameter__header__operation']}>操作</div> | |||
| </Flex> | |||
| @@ -465,33 +512,25 @@ function ExecuteConfig() { | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="CPU 数量" | |||
| name="cpu" | |||
| label="资源规格" | |||
| name="resource" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入 CPU 数量', | |||
| message: '请选择资源规格', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入 CPU 数量" min={0} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="GPU 数量" | |||
| name="gpu" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入 GPU 数量', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入 GPU 数量" min={0} precision={0} /> | |||
| <Select | |||
| showSearch | |||
| placeholder="请选择资源规格" | |||
| filterOption={filterResourceStandard} | |||
| options={resourceStandardList} | |||
| fieldNames={{ | |||
| label: 'description', | |||
| value: 'standard', | |||
| }} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| @@ -48,6 +48,28 @@ | |||
| content: '*'; | |||
| margin-inline-end: 4px; | |||
| } | |||
| :global { | |||
| .anticon-question-circle { | |||
| vertical-align: middle; | |||
| cursor: help; | |||
| } | |||
| } | |||
| } | |||
| &__tooltip { | |||
| max-width: 600px; | |||
| :global { | |||
| .ant-tooltip-inner { | |||
| max-height: 400px; | |||
| overflow-y: auto; | |||
| white-space: pre-line; | |||
| &::-webkit-scrollbar-thumb { | |||
| background: rgba(255, 255, 255, 0.5); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| &__operation { | |||
| @@ -1,10 +1,11 @@ | |||
| import { hyperParameterOptimizedMode } from '@/enums'; | |||
| import ConfigInfo, { formatDate, type BasicInfoData } from '@/pages/AutoML/components/ConfigInfo'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import ConfigInfo, { type BasicInfoData } from '@/pages/AutoML/components/ConfigInfo'; | |||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import { HyperparameterData } from '@/pages/HyperParameter/types'; | |||
| import { type NodeStatus } from '@/types'; | |||
| import { elapsedTime } from '@/utils/date'; | |||
| import { formatDataset, formatSelectCodeConfig } from '@/utils/format'; | |||
| import { formatDataset, formatDate, formatSelectCodeConfig } from '@/utils/format'; | |||
| import { Flex } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useMemo } from 'react'; | |||
| @@ -29,6 +30,17 @@ function HyperParameterBasic({ | |||
| runStatus, | |||
| isInstance = false, | |||
| }: HyperParameterBasicProps) { | |||
| const getResourceDescription = useComputingResource()[2]; | |||
| // 格式化资源规格 | |||
| const formatResource = (resource?: string) => { | |||
| if (!resource) { | |||
| return undefined; | |||
| } | |||
| return getResourceDescription(resource); | |||
| }; | |||
| const basicDatas: BasicInfoData[] = useMemo(() => { | |||
| if (!info) { | |||
| return []; | |||
| @@ -120,13 +132,9 @@ function HyperParameterBasic({ | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: 'CPU 数量', | |||
| value: info.cpu, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: 'GPU 数量', | |||
| value: info.gpu, | |||
| label: '资源规格', | |||
| value: info.resource, | |||
| format: formatResource, | |||
| ellipsis: true, | |||
| }, | |||
| ]; | |||
| @@ -23,8 +23,7 @@ export type FormData = { | |||
| num_samples: number; // 总实验次数 | |||
| max_t: number; // 单次试验最大时间 | |||
| min_samples_required: number; // 计算中位数的最小试验数 | |||
| cpu: number; // cpu 数 | |||
| gpu: number; // gpu 数 | |||
| resource: string; // 资源规格 | |||
| parameters: FormParameter[]; | |||
| points_to_evaluate: { [key: string]: any }[]; | |||
| }; | |||
| @@ -432,7 +432,14 @@ function CreateServiceVersion() { | |||
| {(fields, { add, remove }) => ( | |||
| <> | |||
| {fields.map(({ key, name, ...restField }, index) => ( | |||
| <Flex key={key} align="center" gap="0 8px"> | |||
| <Flex | |||
| key={key} | |||
| align="center" | |||
| gap="0 8px" | |||
| style={{ | |||
| position: 'relative', | |||
| }} | |||
| > | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, 'key']} | |||
| @@ -460,9 +467,10 @@ function CreateServiceVersion() { | |||
| <Flex | |||
| style={{ | |||
| width: '76px', | |||
| marginLeft: '10px', | |||
| left: 'calc(100% + 10px)', | |||
| height: '46px', | |||
| marginBottom: '24px', | |||
| position: 'absolute', | |||
| }} | |||
| align="center" | |||
| > | |||
| @@ -519,7 +527,7 @@ function CreateServiceVersion() { | |||
| </Col> | |||
| </Row> | |||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }} style={{ marginTop: '20px' }}> | |||
| <Button type="primary" htmlType="submit"> | |||
| {buttonText} | |||
| </Button> | |||
| @@ -11,7 +11,7 @@ import { to } from '@/utils/promise'; | |||
| import { useParams } from '@umijs/max'; | |||
| import { Tabs, type TabsProps } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import BasicInfo from '../components/BasicInfo'; | |||
| import VersionBasicInfo from '../components/VersionBasicInfo'; | |||
| import ServerLog from '../components/ServerLog'; | |||
| import UserGuide from '../components/UserGuide'; | |||
| import { ServiceVersionData } from '../types'; | |||
| @@ -75,7 +75,7 @@ function ServiceVersionInfo() { | |||
| image={require('@/assets/img/mirror-basic.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| ></SubAreaTitle> | |||
| <BasicInfo info={versionInfo} /> | |||
| <VersionBasicInfo info={versionInfo} /> | |||
| <div className={styles['service-version-info__content__tabs']}> | |||
| <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> | |||
| </div> | |||
| @@ -1,120 +0,0 @@ | |||
| import LabelValue from '@/components/LabelValue'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { ServiceVersionData } from '@/pages/ModelDeployment/types'; | |||
| import { getGitUrl } from '@/utils'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { Link } from '@umijs/max'; | |||
| import { Col, Row } from 'antd'; | |||
| import ServiceRunStatusCell from '../ModelDeployStatusCell'; | |||
| import styles from './index.less'; | |||
| type BasicInfoProps = { | |||
| info?: ServiceVersionData; | |||
| }; | |||
| function BasicInfo({ info }: BasicInfoProps) { | |||
| const getResourceDescription = useComputingResource()[2]; | |||
| // 格式化环境变量 | |||
| const formatEnvText = () => { | |||
| if (!info?.env_variables || Object.keys(info.env_variables).length === 0) { | |||
| return '--'; | |||
| } | |||
| const env = info.env_variables; | |||
| return Object.entries(env) | |||
| .map(([key, value]) => `${key}: ${value}`) | |||
| .join('\n'); | |||
| }; | |||
| const formatCodeConfig = () => { | |||
| if (info && info.code_config && info.code_config.code_path) { | |||
| const { code_path, branch } = info.code_config; | |||
| const url = getGitUrl(code_path, branch); | |||
| return ( | |||
| <a href={url} target="_blank" rel="noreferrer"> | |||
| {info?.code_config?.show_value} | |||
| </a> | |||
| ); | |||
| } | |||
| return '--'; | |||
| }; | |||
| const formatResource = () => { | |||
| if (info && info.resource) { | |||
| return getResourceDescription(info.resource); | |||
| } | |||
| return undefined; | |||
| }; | |||
| const formatModel = () => { | |||
| if (info && info.model) { | |||
| const model = info.model; | |||
| const path = `/dataset/model/info/${model.id}?version=${model.version}&name=${model.name}&owner=${model.owner}&identifier=${model.identifier}`; | |||
| return <Link to={path}>{info?.model?.show_value}</Link>; | |||
| } | |||
| return undefined; | |||
| }; | |||
| return ( | |||
| <div className={styles['basic-info']}> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <LabelValue label="服务名称:" value={info?.service_name}></LabelValue> | |||
| </Col> | |||
| <Col span={10}> | |||
| <LabelValue label="版本名称:" value={info?.version}></LabelValue> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <LabelValue label="代码配置" value={formatCodeConfig()}></LabelValue> | |||
| </Col> | |||
| <Col span={10}> | |||
| <LabelValue label="镜 像:" value={info?.image}></LabelValue> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <LabelValue label="状 态:" value={ServiceRunStatusCell(info?.run_state)}></LabelValue> | |||
| </Col> | |||
| <Col span={10}> | |||
| <LabelValue label="模 型:" value={formatModel()}></LabelValue> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <LabelValue label="资源规格:" value={formatResource()}></LabelValue> | |||
| </Col> | |||
| <Col span={10}> | |||
| <LabelValue label="挂载路径:" value={info?.mount_path}></LabelValue> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <LabelValue label="API URL:" value={info?.url}></LabelValue> | |||
| </Col> | |||
| <Col span={10}> | |||
| <LabelValue label="副本数量:" value={info?.replicas}></LabelValue> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <LabelValue label="创建时间:" value={formatDate(info?.create_time)}></LabelValue> | |||
| </Col> | |||
| <Col span={10}> | |||
| <LabelValue label="更新时间:" value={formatDate(info?.update_time)}></LabelValue> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40}> | |||
| <Col span={10}> | |||
| <LabelValue label="环境变量:" value={formatEnvText()}></LabelValue> | |||
| </Col> | |||
| <Col span={10}> | |||
| <LabelValue label="描 述:" value={info?.description}></LabelValue> | |||
| </Col> | |||
| </Row> | |||
| </div> | |||
| ); | |||
| } | |||
| export default BasicInfo; | |||
| @@ -0,0 +1,134 @@ | |||
| import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; | |||
| import { ServiceRunStatus } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { ServiceVersionData } from '@/pages/ModelDeployment/types'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { formatModel, formatSelectCodeConfig } from '@/utils/format'; | |||
| import { Flex } from 'antd'; | |||
| import ModelDeployStatusCell from '../ModelDeployStatusCell'; | |||
| type BasicInfoProps = { | |||
| info?: ServiceVersionData; | |||
| }; | |||
| // 格式化状态 | |||
| const formatStatus = (status?: ServiceRunStatus) => { | |||
| if (!status) { | |||
| return undefined; | |||
| } | |||
| return ( | |||
| <Flex align="center" style={{ marginLeft: '16px', fontSize: '16px', lineHeight: 1.6 }}> | |||
| {ModelDeployStatusCell(status)} | |||
| </Flex> | |||
| ); | |||
| }; | |||
| // 格式化环境变量 | |||
| const formatEnvText = (env?: Record<string, string>) => { | |||
| if (!env || Object.keys(env).length === 0) { | |||
| return undefined; | |||
| } | |||
| return Object.entries(env).map(([key, value]) => ({ | |||
| value: `${key}: ${value}`, | |||
| })); | |||
| }; | |||
| function VersionBasicInfo({ info }: BasicInfoProps) { | |||
| const getResourceDescription = useComputingResource()[2]; | |||
| // 格式化资源规格 | |||
| const formatResource = (resource?: string) => { | |||
| if (!resource) { | |||
| return undefined; | |||
| } | |||
| return getResourceDescription(resource); | |||
| }; | |||
| const datas: BasicInfoData[] = [ | |||
| { | |||
| label: '服务名称', | |||
| value: info?.service_name, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '版本名称', | |||
| value: info?.version, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '代码配置', | |||
| value: info?.code_config, | |||
| format: formatSelectCodeConfig, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '镜像', | |||
| value: info?.image, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '状态', | |||
| value: info?.run_state, | |||
| format: formatStatus, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '模型', | |||
| value: info?.model, | |||
| format: formatModel, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '资源规格', | |||
| value: info?.resource, | |||
| format: formatResource, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '挂载路径', | |||
| value: info?.mount_path, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: 'API URL', | |||
| value: info?.url, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '副本数量', | |||
| value: info?.replicas, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '创建时间', | |||
| value: info?.create_time, | |||
| format: formatDate, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '更新时间', | |||
| value: info?.update_time, | |||
| format: formatDate, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '环境变量', | |||
| value: info?.env_variables, | |||
| format: formatEnvText, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '描述', | |||
| value: info?.description, | |||
| ellipsis: true, | |||
| }, | |||
| ]; | |||
| return <BasicInfo datas={datas} labelWidth={66}></BasicInfo>; | |||
| } | |||
| export default VersionBasicInfo; | |||
| @@ -1,6 +1,14 @@ | |||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | |||
| import { DataSource, DatasetData, ProjectDependency, TrainTask } from '@/pages/Dataset/config'; | |||
| import { | |||
| DataSource, | |||
| DatasetData, | |||
| ModelData, | |||
| ProjectDependency, | |||
| TrainTask, | |||
| } from '@/pages/Dataset/config'; | |||
| import { getGitUrl } from '@/utils'; | |||
| // 格式化日期 | |||
| export { formatDate } from '@/utils/date'; | |||
| // 格式化数据集数组 | |||
| export const formatDatasets = (datasets?: DatasetData[]) => { | |||
| @@ -9,7 +17,7 @@ export const formatDatasets = (datasets?: DatasetData[]) => { | |||
| } | |||
| return datasets.map((item) => ({ | |||
| value: item.name, | |||
| url: `${origin}/dataset/dataset/info/${item.id}?tab=${ResourceInfoTabKeys.Introduction}&version=${item.version}&name=${item.name}&owner=${item.owner}&identifier=${item.identifier}`, | |||
| link: `/dataset/dataset/info/${item.id}?tab=${ResourceInfoTabKeys.Introduction}&version=${item.version}&name=${item.name}&owner=${item.owner}&identifier=${item.identifier}`, | |||
| })); | |||
| }; | |||
| @@ -20,7 +28,19 @@ export const formatDataset = (dataset?: DatasetData) => { | |||
| } | |||
| return { | |||
| value: dataset.name, | |||
| url: `${origin}/dataset/dataset/info/${dataset.id}?tab=${ResourceInfoTabKeys.Introduction}&version=${dataset.version}&name=${dataset.name}&owner=${dataset.owner}&identifier=${dataset.identifier}`, | |||
| link: `/dataset/dataset/info/${dataset.id}?tab=${ResourceInfoTabKeys.Introduction}&version=${dataset.version}&name=${dataset.name}&owner=${dataset.owner}&identifier=${dataset.identifier}`, | |||
| }; | |||
| }; | |||
| // 格式化模型 | |||
| export const formatModel = (model: ModelData) => { | |||
| if (!model) { | |||
| return undefined; | |||
| } | |||
| return { | |||
| value: model.name, | |||
| link: `/dataset/model/info/${model.id}?tab=${ResourceInfoTabKeys.Introduction}&version=${model.version}&name=${model.name}&owner=${model.owner}&identifier=${model.identifier}`, | |||
| }; | |||
| }; | |||
| @@ -48,14 +68,15 @@ export const formatCodeConfig = (project?: ProjectDependency) => { | |||
| export const formatSelectCodeConfig = (value?: { | |||
| code_path: string; | |||
| branch: string; | |||
| showValue: string; | |||
| showValue?: string; | |||
| show_value?: string; | |||
| }) => { | |||
| if (!value) { | |||
| return undefined; | |||
| } | |||
| const { showValue, code_path, branch } = value; | |||
| const { showValue, show_value, code_path, branch } = value; | |||
| return { | |||
| value: showValue, | |||
| value: showValue || show_value, | |||
| url: getRepoUrl({ | |||
| url: code_path, | |||
| branch, | |||
| @@ -70,7 +91,7 @@ export const formatTrainTask = (task?: TrainTask) => { | |||
| } | |||
| return { | |||
| value: task.name, | |||
| url: `${origin}/pipeline/experiment/instance/${task.workflow_id}/${task.ins_id}`, | |||
| url: `/pipeline/experiment/instance/${task.workflow_id}/${task.ins_id}`, | |||
| }; | |||
| }; | |||
| @@ -85,3 +106,31 @@ export const formatSource = (source?: string) => { | |||
| } | |||
| return source; | |||
| }; | |||
| // 格式化字符串数组 | |||
| export const formatList = (value: string[] | null | undefined): string => { | |||
| if ( | |||
| value === undefined || | |||
| value === null || | |||
| Array.isArray(value) === false || | |||
| value.length === 0 | |||
| ) { | |||
| return '--'; | |||
| } | |||
| return value.join(','); | |||
| }; | |||
| // 格式化布尔值 | |||
| export const formatBoolean = (value: boolean): string => { | |||
| return value ? '是' : '否'; | |||
| }; | |||
| type FormatEnum = (value: string | number) => string; | |||
| // 格式化枚举 | |||
| export const formatEnum = (options: { value: string | number; label: string }[]): FormatEnum => { | |||
| return (value: string | number) => { | |||
| const option = options.find((item) => item.value === value); | |||
| return option ? option.label : '--'; | |||
| }; | |||
| }; | |||