| @@ -4,9 +4,8 @@ | |||||
| * @Description: 用于 BasicInfo 和 BasicTableInfo 组件的子组件 | * @Description: 用于 BasicInfo 和 BasicTableInfo 组件的子组件 | ||||
| */ | */ | ||||
| import { Link } from '@umijs/max'; | |||||
| import { Typography } from 'antd'; | |||||
| import React from 'react'; | import React from 'react'; | ||||
| import BasicInfoItemValue from './BasicInfoItemValue'; | |||||
| import { type BasicInfoData, type BasicInfoLink } from './types'; | import { type BasicInfoData, type BasicInfoLink } from './types'; | ||||
| type BasicInfoItemProps = { | type BasicInfoItemProps = { | ||||
| @@ -15,12 +14,14 @@ type BasicInfoItemProps = { | |||||
| classPrefix: string; | classPrefix: string; | ||||
| }; | }; | ||||
| export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) { | |||||
| function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) { | |||||
| const { label, value, format, ellipsis } = data; | const { label, value, format, ellipsis } = data; | ||||
| const formatValue = format ? format(value) : value; | const formatValue = format ? format(value) : value; | ||||
| const myClassName = `${classPrefix}__item`; | const myClassName = `${classPrefix}__item`; | ||||
| let valueComponent = undefined; | let valueComponent = undefined; | ||||
| if (Array.isArray(formatValue)) { | |||||
| if (React.isValidElement(formatValue)) { | |||||
| valueComponent = formatValue; | |||||
| } else if (Array.isArray(formatValue)) { | |||||
| valueComponent = ( | valueComponent = ( | ||||
| <div className={`${myClassName}__value-container`}> | <div className={`${myClassName}__value-container`}> | ||||
| {formatValue.map((item: BasicInfoLink) => ( | {formatValue.map((item: BasicInfoLink) => ( | ||||
| @@ -35,11 +36,6 @@ export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemPr | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| } else if (React.isValidElement(formatValue)) { | |||||
| // 这个判断必须在下面的判断之前 | |||||
| valueComponent = ( | |||||
| <BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} /> | |||||
| ); | |||||
| } else if (typeof formatValue === 'object' && formatValue) { | } else if (typeof formatValue === 'object' && formatValue) { | ||||
| valueComponent = ( | valueComponent = ( | ||||
| <BasicInfoItemValue | <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 classNames from 'classnames'; | ||||
| import React from 'react'; | import React from 'react'; | ||||
| import { BasicInfoItem } from './components'; | |||||
| import BasicInfoItem from './BasicInfoItem'; | |||||
| import './index.less'; | import './index.less'; | ||||
| import type { BasicInfoData, BasicInfoLink } from './types'; | import type { BasicInfoData, BasicInfoLink } from './types'; | ||||
| export * from './format'; | |||||
| export type { BasicInfoData, BasicInfoLink }; | export type { BasicInfoData, BasicInfoLink }; | ||||
| type BasicInfoProps = { | type BasicInfoProps = { | ||||
| @@ -3,12 +3,12 @@ export type BasicInfoData = { | |||||
| label: string; | label: string; | ||||
| value?: any; | value?: any; | ||||
| ellipsis?: boolean; | ellipsis?: boolean; | ||||
| format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; | |||||
| format?: (_value?: any) => string | React.ReactNode | BasicInfoLink | BasicInfoLink[] | undefined; | |||||
| }; | }; | ||||
| // 值为链接的类型 | // 值为链接的类型 | ||||
| export type BasicInfoLink = { | export type BasicInfoLink = { | ||||
| value: string; | |||||
| value?: string; | |||||
| link?: string; | link?: string; | ||||
| url?: string; | url?: string; | ||||
| }; | }; | ||||
| @@ -1,8 +1,7 @@ | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { BasicInfoItem } from '../BasicInfo/components'; | |||||
| import BasicInfoItem from '../BasicInfo/BasicInfoItem'; | |||||
| import { type BasicInfoData, type BasicInfoLink } from '../BasicInfo/types'; | import { type BasicInfoData, type BasicInfoLink } from '../BasicInfo/types'; | ||||
| import './index.less'; | import './index.less'; | ||||
| export * from '../BasicInfo/format'; | |||||
| export type { BasicInfoData, BasicInfoLink }; | export type { BasicInfoData, BasicInfoLink }; | ||||
| type BasicTableInfoProps = { | type BasicTableInfoProps = { | ||||
| @@ -4,16 +4,11 @@ import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||||
| import { type NodeStatus } from '@/types'; | import { type NodeStatus } from '@/types'; | ||||
| import { parseJsonText } from '@/utils'; | import { parseJsonText } from '@/utils'; | ||||
| import { elapsedTime } from '@/utils/date'; | import { elapsedTime } from '@/utils/date'; | ||||
| import { formatDataset } from '@/utils/format'; | |||||
| import { formatBoolean, formatDataset, formatDate, formatEnum } from '@/utils/format'; | |||||
| import { Flex } from 'antd'; | import { Flex } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import ConfigInfo, { | |||||
| formatBoolean, | |||||
| formatDate, | |||||
| formatEnum, | |||||
| type BasicInfoData, | |||||
| } from '../ConfigInfo'; | |||||
| import ConfigInfo, { type BasicInfoData } from '../ConfigInfo'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| // 格式化优化方向 | // 格式化优化方向 | ||||
| @@ -2,7 +2,6 @@ import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; | |||||
| import InfoGroup from '@/components/InfoGroup'; | import InfoGroup from '@/components/InfoGroup'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export * from '@/components/BasicInfo/format'; | |||||
| export type { BasicInfoData }; | export type { BasicInfoData }; | ||||
| type ConfigInfoProps = { | type ConfigInfoProps = { | ||||
| @@ -6,9 +6,10 @@ import ResourceSelect, { | |||||
| } from '@/components/ResourceSelect'; | } from '@/components/ResourceSelect'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { hyperParameterOptimizedModeOptions } from '@/enums'; | import { hyperParameterOptimizedModeOptions } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { modalConfirm } from '@/utils/ui'; | 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 { isEqual } from 'lodash'; | ||||
| import PopParameterRange from './PopParameterRange'; | import PopParameterRange from './PopParameterRange'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -25,12 +26,48 @@ const schedulerAlgorithms = ['ASHA', 'HyperBand', 'MedianStopping', 'PopulationB | |||||
| (name) => ({ label: name, value: name }), | (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() { | function ExecuteConfig() { | ||||
| const form = Form.useFormInstance(); | const form = Form.useFormInstance(); | ||||
| const searchAlgorithm = Form.useWatch('search_alg', form); | const searchAlgorithm = Form.useWatch('search_alg', form); | ||||
| const paramsTypeOptions = searchAlgorithm === 'Ax' ? axParameterOptions : parameterOptions; | const paramsTypeOptions = searchAlgorithm === 'Ax' ? axParameterOptions : parameterOptions; | ||||
| // const parameters = Form.useWatch('parameters', form); | |||||
| // console.log('parameters', parameters); | |||||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); | |||||
| const handleSearchAlgorithmChange = (value: string) => { | const handleSearchAlgorithmChange = (value: string) => { | ||||
| if ( | if ( | ||||
| @@ -231,7 +268,17 @@ function ExecuteConfig() { | |||||
| <div className={styles['hyper-parameter']}> | <div className={styles['hyper-parameter']}> | ||||
| <Flex align="center" className={styles['hyper-parameter__header']}> | <Flex align="center" className={styles['hyper-parameter__header']}> | ||||
| <div className={styles['hyper-parameter__header__name']}>参数名称</div> | <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__space']}>取值范围</div> | ||||
| <div className={styles['hyper-parameter__header__operation']}>操作</div> | <div className={styles['hyper-parameter__header__operation']}>操作</div> | ||||
| </Flex> | </Flex> | ||||
| @@ -465,33 +512,25 @@ function ExecuteConfig() { | |||||
| <Row gutter={8}> | <Row gutter={8}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="CPU 数量" | |||||
| name="cpu" | |||||
| label="资源规格" | |||||
| name="resource" | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | 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> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -48,6 +48,28 @@ | |||||
| content: '*'; | content: '*'; | ||||
| margin-inline-end: 4px; | 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 { | &__operation { | ||||
| @@ -1,10 +1,11 @@ | |||||
| import { hyperParameterOptimizedMode } from '@/enums'; | 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 { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { HyperparameterData } from '@/pages/HyperParameter/types'; | import { HyperparameterData } from '@/pages/HyperParameter/types'; | ||||
| import { type NodeStatus } from '@/types'; | import { type NodeStatus } from '@/types'; | ||||
| import { elapsedTime } from '@/utils/date'; | import { elapsedTime } from '@/utils/date'; | ||||
| import { formatDataset, formatSelectCodeConfig } from '@/utils/format'; | |||||
| import { formatDataset, formatDate, formatSelectCodeConfig } from '@/utils/format'; | |||||
| import { Flex } from 'antd'; | import { Flex } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| @@ -29,6 +30,17 @@ function HyperParameterBasic({ | |||||
| runStatus, | runStatus, | ||||
| isInstance = false, | isInstance = false, | ||||
| }: HyperParameterBasicProps) { | }: HyperParameterBasicProps) { | ||||
| const getResourceDescription = useComputingResource()[2]; | |||||
| // 格式化资源规格 | |||||
| const formatResource = (resource?: string) => { | |||||
| if (!resource) { | |||||
| return undefined; | |||||
| } | |||||
| return getResourceDescription(resource); | |||||
| }; | |||||
| const basicDatas: BasicInfoData[] = useMemo(() => { | const basicDatas: BasicInfoData[] = useMemo(() => { | ||||
| if (!info) { | if (!info) { | ||||
| return []; | return []; | ||||
| @@ -120,13 +132,9 @@ function HyperParameterBasic({ | |||||
| ellipsis: true, | ellipsis: true, | ||||
| }, | }, | ||||
| { | { | ||||
| label: 'CPU 数量', | |||||
| value: info.cpu, | |||||
| ellipsis: true, | |||||
| }, | |||||
| { | |||||
| label: 'GPU 数量', | |||||
| value: info.gpu, | |||||
| label: '资源规格', | |||||
| value: info.resource, | |||||
| format: formatResource, | |||||
| ellipsis: true, | ellipsis: true, | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -23,8 +23,7 @@ export type FormData = { | |||||
| num_samples: number; // 总实验次数 | num_samples: number; // 总实验次数 | ||||
| max_t: number; // 单次试验最大时间 | max_t: number; // 单次试验最大时间 | ||||
| min_samples_required: number; // 计算中位数的最小试验数 | min_samples_required: number; // 计算中位数的最小试验数 | ||||
| cpu: number; // cpu 数 | |||||
| gpu: number; // gpu 数 | |||||
| resource: string; // 资源规格 | |||||
| parameters: FormParameter[]; | parameters: FormParameter[]; | ||||
| points_to_evaluate: { [key: string]: any }[]; | points_to_evaluate: { [key: string]: any }[]; | ||||
| }; | }; | ||||
| @@ -432,7 +432,14 @@ function CreateServiceVersion() { | |||||
| {(fields, { add, remove }) => ( | {(fields, { add, remove }) => ( | ||||
| <> | <> | ||||
| {fields.map(({ key, name, ...restField }, index) => ( | {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 | <Form.Item | ||||
| {...restField} | {...restField} | ||||
| name={[name, 'key']} | name={[name, 'key']} | ||||
| @@ -460,9 +467,10 @@ function CreateServiceVersion() { | |||||
| <Flex | <Flex | ||||
| style={{ | style={{ | ||||
| width: '76px', | width: '76px', | ||||
| marginLeft: '10px', | |||||
| left: 'calc(100% + 10px)', | |||||
| height: '46px', | height: '46px', | ||||
| marginBottom: '24px', | marginBottom: '24px', | ||||
| position: 'absolute', | |||||
| }} | }} | ||||
| align="center" | align="center" | ||||
| > | > | ||||
| @@ -519,7 +527,7 @@ function CreateServiceVersion() { | |||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }} style={{ marginTop: '20px' }}> | |||||
| <Button type="primary" htmlType="submit"> | <Button type="primary" htmlType="submit"> | ||||
| {buttonText} | {buttonText} | ||||
| </Button> | </Button> | ||||
| @@ -11,7 +11,7 @@ import { to } from '@/utils/promise'; | |||||
| import { useParams } from '@umijs/max'; | import { useParams } from '@umijs/max'; | ||||
| import { Tabs, type TabsProps } from 'antd'; | import { Tabs, type TabsProps } from 'antd'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import BasicInfo from '../components/BasicInfo'; | |||||
| import VersionBasicInfo from '../components/VersionBasicInfo'; | |||||
| import ServerLog from '../components/ServerLog'; | import ServerLog from '../components/ServerLog'; | ||||
| import UserGuide from '../components/UserGuide'; | import UserGuide from '../components/UserGuide'; | ||||
| import { ServiceVersionData } from '../types'; | import { ServiceVersionData } from '../types'; | ||||
| @@ -75,7 +75,7 @@ function ServiceVersionInfo() { | |||||
| image={require('@/assets/img/mirror-basic.png')} | image={require('@/assets/img/mirror-basic.png')} | ||||
| style={{ marginBottom: '26px' }} | style={{ marginBottom: '26px' }} | ||||
| ></SubAreaTitle> | ></SubAreaTitle> | ||||
| <BasicInfo info={versionInfo} /> | |||||
| <VersionBasicInfo info={versionInfo} /> | |||||
| <div className={styles['service-version-info__content__tabs']}> | <div className={styles['service-version-info__content__tabs']}> | ||||
| <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> | <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> | ||||
| </div> | </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 { 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'; | import { getGitUrl } from '@/utils'; | ||||
| // 格式化日期 | |||||
| export { formatDate } from '@/utils/date'; | |||||
| // 格式化数据集数组 | // 格式化数据集数组 | ||||
| export const formatDatasets = (datasets?: DatasetData[]) => { | export const formatDatasets = (datasets?: DatasetData[]) => { | ||||
| @@ -9,7 +17,7 @@ export const formatDatasets = (datasets?: DatasetData[]) => { | |||||
| } | } | ||||
| return datasets.map((item) => ({ | return datasets.map((item) => ({ | ||||
| value: item.name, | 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 { | return { | ||||
| value: dataset.name, | 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?: { | export const formatSelectCodeConfig = (value?: { | ||||
| code_path: string; | code_path: string; | ||||
| branch: string; | branch: string; | ||||
| showValue: string; | |||||
| showValue?: string; | |||||
| show_value?: string; | |||||
| }) => { | }) => { | ||||
| if (!value) { | if (!value) { | ||||
| return undefined; | return undefined; | ||||
| } | } | ||||
| const { showValue, code_path, branch } = value; | |||||
| const { showValue, show_value, code_path, branch } = value; | |||||
| return { | return { | ||||
| value: showValue, | |||||
| value: showValue || show_value, | |||||
| url: getRepoUrl({ | url: getRepoUrl({ | ||||
| url: code_path, | url: code_path, | ||||
| branch, | branch, | ||||
| @@ -70,7 +91,7 @@ export const formatTrainTask = (task?: TrainTask) => { | |||||
| } | } | ||||
| return { | return { | ||||
| value: task.name, | 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; | 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 : '--'; | |||||
| }; | |||||
| }; | |||||