| @@ -50,5 +50,6 @@ Thumbs.db | |||
| mvnw.cmd | |||
| mvnw | |||
| # Files or folders need to be retained | |||
| # ... | |||
| # web | |||
| **/node_modules | |||
| @@ -0,0 +1,45 @@ | |||
| { | |||
| "name": "ci4sManagement-cloud", | |||
| "lockfileVersion": 3, | |||
| "requires": true, | |||
| "packages": { | |||
| "": { | |||
| "dependencies": { | |||
| "clipboard": "~2.0.11" | |||
| } | |||
| }, | |||
| "node_modules/clipboard": { | |||
| "version": "2.0.11", | |||
| "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", | |||
| "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", | |||
| "dependencies": { | |||
| "good-listener": "^1.2.2", | |||
| "select": "^1.1.2", | |||
| "tiny-emitter": "^2.0.0" | |||
| } | |||
| }, | |||
| "node_modules/delegate": { | |||
| "version": "3.2.0", | |||
| "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", | |||
| "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" | |||
| }, | |||
| "node_modules/good-listener": { | |||
| "version": "1.2.2", | |||
| "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", | |||
| "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", | |||
| "dependencies": { | |||
| "delegate": "^3.1.2" | |||
| } | |||
| }, | |||
| "node_modules/select": { | |||
| "version": "1.1.2", | |||
| "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", | |||
| "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" | |||
| }, | |||
| "node_modules/tiny-emitter": { | |||
| "version": "2.1.0", | |||
| "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", | |||
| "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| { | |||
| "dependencies": { | |||
| "clipboard": "~2.0.11" | |||
| } | |||
| } | |||
| @@ -135,6 +135,27 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: '自动机器学习', | |||
| path: 'automl', | |||
| routes: [ | |||
| { | |||
| name: '自动机器学习', | |||
| path: '', | |||
| component: './AutoML/List/index', | |||
| }, | |||
| { | |||
| name: '自动机器学习详情', | |||
| path: 'info/:id', | |||
| component: './AutoML/Info/index', | |||
| }, | |||
| { | |||
| name: '创建自动机器学习', | |||
| path: 'create', | |||
| component: './AutoML/Create/index', | |||
| }, | |||
| ], | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| @@ -21,6 +21,7 @@ import './styles/menu.less'; | |||
| export { requestConfig as request } from './requestConfig'; | |||
| // const isDev = process.env.NODE_ENV === 'development'; | |||
| import { type GlobalInitialState } from '@/types'; | |||
| import '@/utils/clipboard'; | |||
| import { menuItemRender } from '@/utils/menuRender'; | |||
| import ErrorBoundary from './components/ErrorBoundary'; | |||
| import { gotoLoginPage } from './utils/ui'; | |||
| @@ -1,6 +1,7 @@ | |||
| import { Link } from '@umijs/max'; | |||
| import { Typography } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import React from 'react'; | |||
| import './index.less'; | |||
| export type BasicInfoLink = { | |||
| @@ -29,9 +30,12 @@ type BasicInfoItemProps = { | |||
| classPrefix: string; | |||
| }; | |||
| type BasicInfoItemValueProps = BasicInfoLink & { | |||
| type BasicInfoItemValueProps = { | |||
| ellipsis?: boolean; | |||
| classPrefix: string; | |||
| value: string | React.ReactNode; | |||
| link?: string; | |||
| url?: string; | |||
| }; | |||
| export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) { | |||
| @@ -69,6 +73,11 @@ 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 | |||
| @@ -115,13 +124,18 @@ export function BasicInfoItemValue({ | |||
| {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}> | |||
| <Typography.Text | |||
| ellipsis={ellipsis ? { tooltip: value } : false} | |||
| style={{ fontSize: 'inherit' }} | |||
| > | |||
| {component} | |||
| </Typography.Text> | |||
| </div> | |||
| @@ -21,13 +21,13 @@ interface KFIconProps extends IconFontProps { | |||
| className?: string; | |||
| } | |||
| function KFIcon({ type, font = 15, color = '', style = {}, className }: KFIconProps) { | |||
| function KFIcon({ type, font = 15, color = '', style = {}, className, ...rest }: KFIconProps) { | |||
| const iconStyle = { | |||
| ...style, | |||
| fontSize: font, | |||
| color, | |||
| }; | |||
| return <Icon type={type} className={className} style={iconStyle} />; | |||
| return <Icon {...rest} type={type} className={className} style={iconStyle} />; | |||
| } | |||
| export default KFIcon; | |||
| @@ -0,0 +1,47 @@ | |||
| .create-service-version { | |||
| height: 100%; | |||
| &__content { | |||
| height: calc(100% - 60px); | |||
| margin-top: 10px; | |||
| padding: 30px 30px 10px; | |||
| overflow: auto; | |||
| color: @text-color; | |||
| font-size: @font-size-content; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| &__type { | |||
| color: @text-color; | |||
| font-size: @font-size-input-lg; | |||
| } | |||
| :global { | |||
| .ant-input-number { | |||
| width: 100%; | |||
| } | |||
| .ant-form-item { | |||
| margin-bottom: 20px; | |||
| } | |||
| .image-url { | |||
| margin-top: -15px; | |||
| .ant-form-item-label > label::after { | |||
| content: ''; | |||
| } | |||
| } | |||
| .ant-btn.ant-btn-icon-only .anticon { | |||
| color: #565658; | |||
| font-size: 20px; | |||
| } | |||
| .anticon-question-circle { | |||
| margin-top: -12px; | |||
| color: @text-color-tertiary !important; | |||
| font-size: 12px !important; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,220 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 创建服务版本 | |||
| */ | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { type ParameterInputObject } from '@/components/ResourceSelect'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { | |||
| createServiceVersionReq, | |||
| getServiceInfoReq, | |||
| updateServiceVersionReq, | |||
| } from '@/services/modelDeployment'; | |||
| import { changePropertyName } from '@/utils'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import { useNavigate, useParams } from '@umijs/max'; | |||
| import { App, Button, Form } from 'antd'; | |||
| import { omit, pick } from 'lodash'; | |||
| import { useEffect, useState } from 'react'; | |||
| import BasicConfig from '../components/CreateForm/BasicConfig'; | |||
| import ExecuteConfig from '../components/CreateForm/ExecuteConfig'; | |||
| import SearchConfig from '../components/CreateForm/SearchConfig'; | |||
| import TrialConfig from '../components/CreateForm/TrialConfig'; | |||
| import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types'; | |||
| import styles from './index.less'; | |||
| // 表单数据 | |||
| export type FormData = { | |||
| service_name: string; // 服务名称 | |||
| version: string; // 服务版本 | |||
| description: string; // 描述 | |||
| model: ParameterInputObject; // 模型 | |||
| image: ParameterInputObject; // 镜像 | |||
| code_config: ParameterInputObject; // 代码 | |||
| resource: string; // 资源规格 | |||
| replicas: string; // 副本数量 | |||
| mount_path: string; // 模型路径 | |||
| env_variables: { key: string; value: string }[]; // 环境变量 | |||
| }; | |||
| function CreateAutoML() { | |||
| const navigate = useNavigate(); | |||
| const [form] = Form.useForm(); | |||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); | |||
| const [operationType, setOperationType] = useState(ServiceOperationType.Create); | |||
| const { message } = App.useApp(); | |||
| const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined); | |||
| const [versionInfo, setVersionInfo] = useState<ServiceVersionData | undefined>(undefined); | |||
| const params = useParams(); | |||
| const id = params.id; | |||
| useEffect(() => { | |||
| const res: | |||
| | (ServiceVersionData & { | |||
| operationType: ServiceOperationType; | |||
| }) | |||
| | undefined = SessionStorage.getItem(SessionStorage.serviceVersionInfoKey, true); | |||
| if (res) { | |||
| setOperationType(res.operationType); | |||
| setVersionInfo(res); | |||
| let model, codeConfig, envVariables; | |||
| if (res.model && typeof res.model === 'object') { | |||
| model = changePropertyName(res.model, { show_value: 'showValue' }); | |||
| // 接口返回是数据没有 value 值,但是 form 需要 value | |||
| model.value = model.showValue; | |||
| } | |||
| if (res.code_config && typeof res.code_config === 'object') { | |||
| codeConfig = changePropertyName(res.code_config, { show_value: 'showValue' }); | |||
| // 接口返回是数据没有 value 值,但是 form 需要 value | |||
| codeConfig.value = codeConfig.showValue; | |||
| } | |||
| if (res.env_variables && typeof res.env_variables === 'object') { | |||
| envVariables = Object.entries(res.env_variables).map(([key, value]) => ({ | |||
| key, | |||
| value, | |||
| })); | |||
| } | |||
| const formData = { | |||
| ...omit(res, 'model', 'code_config', 'env_variables'), | |||
| model: model, | |||
| code_config: codeConfig, | |||
| env_variables: envVariables, | |||
| }; | |||
| form.setFieldsValue(formData); | |||
| } | |||
| return () => { | |||
| SessionStorage.removeItem(SessionStorage.serviceVersionInfoKey); | |||
| }; | |||
| }, []); | |||
| // 获取服务详情 | |||
| const getServiceInfo = async () => { | |||
| const [res] = await to(getServiceInfoReq(id)); | |||
| if (res && res.data) { | |||
| setServiceInfo(res.data); | |||
| form.setFieldsValue({ | |||
| service_name: res.data.service_name, | |||
| }); | |||
| } | |||
| }; | |||
| // 创建版本 | |||
| const createServiceVersion = async (formData: FormData) => { | |||
| const envList = formData['env_variables'] ?? []; | |||
| const image = formData['image']; | |||
| const model = formData['model']; | |||
| const codeConfig = formData['code_config']; | |||
| const envVariables = envList.reduce((acc, cur) => { | |||
| acc[cur.key] = cur.value; | |||
| return acc; | |||
| }, {} as Record<string, string>); | |||
| // 根据后台要求,修改表单数据 | |||
| const object = { | |||
| ...omit(formData, ['replicas', 'env_variables', 'image', 'model', 'code_config']), | |||
| replicas: Number(formData.replicas), | |||
| env_variables: envVariables, | |||
| image: image.value, | |||
| model: changePropertyName( | |||
| pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']), | |||
| { showValue: 'show_value' }, | |||
| ), | |||
| code_config: changePropertyName(pick(codeConfig, ['code_path', 'branch', 'showValue']), { | |||
| showValue: 'show_value', | |||
| }), | |||
| service_id: serviceInfo?.id, | |||
| }; | |||
| const params = | |||
| operationType === ServiceOperationType.Create | |||
| ? object | |||
| : { | |||
| id: versionInfo?.id, | |||
| rerun: operationType === ServiceOperationType.Restart ? true : false, | |||
| deployment_name: versionInfo?.deployment_name, | |||
| ...object, | |||
| }; | |||
| const request = | |||
| operationType === ServiceOperationType.Create | |||
| ? createServiceVersionReq | |||
| : updateServiceVersionReq; | |||
| const [res] = await to(request(params)); | |||
| if (res) { | |||
| message.success('操作成功'); | |||
| navigate(-1); | |||
| } | |||
| }; | |||
| // 提交 | |||
| const handleSubmit = (values: FormData) => { | |||
| console.log('values', values); | |||
| }; | |||
| // 取消 | |||
| const cancel = () => { | |||
| navigate(-1); | |||
| }; | |||
| const disabled = operationType !== ServiceOperationType.Create; | |||
| let buttonText = '新建'; | |||
| let title = '新增服务版本'; | |||
| if (operationType === ServiceOperationType.Update) { | |||
| title = '更新服务版本'; | |||
| buttonText = '更新'; | |||
| } else if (operationType === ServiceOperationType.Restart) { | |||
| title = '重启服务版本'; | |||
| buttonText = '重启'; | |||
| } | |||
| return ( | |||
| <div className={styles['create-service-version']}> | |||
| <PageTitle title={title}></PageTitle> | |||
| <div className={styles['create-service-version__content']}> | |||
| <div> | |||
| <Form | |||
| name="create-service-version" | |||
| labelCol={{ flex: '140px' }} | |||
| labelAlign="left" | |||
| form={form} | |||
| onFinish={handleSubmit} | |||
| size="large" | |||
| autoComplete="off" | |||
| initialValues={{ | |||
| execute_type: 'DLC', | |||
| image_type: 3, | |||
| advanced_config: [{ key: '', value: '' }], | |||
| }} | |||
| > | |||
| <BasicConfig /> | |||
| <ExecuteConfig disabled={disabled} /> | |||
| <TrialConfig /> | |||
| <SearchConfig /> | |||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||
| <Button type="primary" htmlType="submit"> | |||
| {buttonText} | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| htmlType="button" | |||
| onClick={cancel} | |||
| style={{ marginLeft: '20px' }} | |||
| > | |||
| 取消 | |||
| </Button> | |||
| </Form.Item> | |||
| </Form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default CreateAutoML; | |||
| @@ -0,0 +1,40 @@ | |||
| .auto-ml-info { | |||
| position: relative; | |||
| height: 100%; | |||
| &__tabs { | |||
| height: 50px; | |||
| padding-left: 25px; | |||
| background-image: url(@/assets/img/page-title-bg.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| } | |||
| &__content { | |||
| height: calc(100% - 60px); | |||
| margin-top: 10px; | |||
| } | |||
| &__tips { | |||
| position: absolute; | |||
| top: 11px; | |||
| left: 256px; | |||
| padding: 3px 12px; | |||
| color: #565658; | |||
| font-size: @font-size-content; | |||
| background: .addAlpha(@primary-color, 0.09) []; | |||
| border-radius: 4px; | |||
| &::before { | |||
| position: absolute; | |||
| top: 10px; | |||
| left: -6px; | |||
| width: 0; | |||
| height: 0; | |||
| border-top: 4px solid transparent; | |||
| border-right: 6px solid .addAlpha(@primary-color, 0.09) []; | |||
| border-bottom: 4px solid transparent; | |||
| content: ''; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,61 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 自主机器学习详情 | |||
| */ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { Tabs } from 'antd'; | |||
| import { useState } from 'react'; | |||
| import AutoMLBasic from '../components/AutoMLBasic'; | |||
| import AutoMLTable from '../components/AutoMLTable'; | |||
| import styles from './index.less'; | |||
| export type MirrorData = { | |||
| id: number; | |||
| name: string; | |||
| description: string; | |||
| create_time: string; | |||
| }; | |||
| function AutoMLInfo() { | |||
| const navigate = useNavigate(); | |||
| const [cacheState, setCacheState] = useCacheState(); | |||
| const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public); | |||
| const tabItems = [ | |||
| { | |||
| key: CommonTabKeys.Public, | |||
| label: '基本信息', | |||
| icon: <KFIcon type="icon-jibenxinxi" />, | |||
| }, | |||
| { | |||
| key: CommonTabKeys.Private, | |||
| label: 'Trial列表', | |||
| icon: <KFIcon type="icon-Trialliebiao" />, | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['auto-ml-info']}> | |||
| <div className={styles['auto-ml-info__tabs']}> | |||
| <Tabs items={tabItems} activeKey={activeTab} onChange={setActiveTab} /> | |||
| </div> | |||
| <div className={styles['auto-ml-info__content']}> | |||
| {activeTab === CommonTabKeys.Public && <AutoMLBasic />} | |||
| {activeTab === CommonTabKeys.Private && <AutoMLTable />} | |||
| </div> | |||
| {activeTab === CommonTabKeys.Private && ( | |||
| <div className={styles['auto-ml-info__tips']}> | |||
| <KFIcon type="icon-tishi" color={themes['warningColor']} font={17} /> | |||
| <span style={{ marginLeft: '4px' }}>Trial是一次独立的尝试,他会使用某组超参来运行</span> | |||
| </div> | |||
| )} | |||
| </div> | |||
| ); | |||
| } | |||
| export default AutoMLInfo; | |||
| @@ -0,0 +1,20 @@ | |||
| .auto-ml-list { | |||
| height: 100%; | |||
| &__content { | |||
| height: calc(100% - 60px); | |||
| margin-top: 10px; | |||
| padding: 20px @content-padding 0; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| &__filter { | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| &__table { | |||
| height: calc(100% - 32px - 28px); | |||
| margin-top: 28px; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,311 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 自主机器学习 | |||
| */ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { | |||
| App, | |||
| Button, | |||
| ConfigProvider, | |||
| Input, | |||
| Table, | |||
| type TablePaginationConfig, | |||
| type TableProps, | |||
| } from 'antd'; | |||
| import { type SearchProps } from 'antd/es/input'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| import ExecuteScheduleCell from '../components/ExecuteScheduleCell'; | |||
| import RunStatusCell from '../components/RunStatusCell'; | |||
| import { ServiceData, ServiceOperationType } from '../types'; | |||
| import styles from './index.less'; | |||
| function AutoMLList() { | |||
| const navigate = useNavigate(); | |||
| const { message } = App.useApp(); | |||
| const [cacheState, setCacheState] = useCacheState(); | |||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | |||
| const [inputText, setInputText] = useState(cacheState?.searchText); | |||
| const [tableData, setTableData] = useState<ServiceData[]>([]); | |||
| const [total, setTotal] = useState(0); | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||
| cacheState?.pagination ?? { | |||
| current: 1, | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| useEffect(() => { | |||
| getServiceList(); | |||
| }, [pagination, searchText]); | |||
| // 获取模型部署服务列表 | |||
| const getServiceList = async () => { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| service_name: searchText, | |||
| }; | |||
| const [res] = await to(getServiceListReq(params)); | |||
| if (res && res.data) { | |||
| const { content = [], totalElements = 0 } = res.data; | |||
| setTableData(content); | |||
| setTotal(totalElements); | |||
| } | |||
| }; | |||
| // 删除模型部署 | |||
| const deleteService = async (record: ServiceData) => { | |||
| const [res] = await to(deleteServiceReq(record.id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getServiceList(); | |||
| } | |||
| } | |||
| }; | |||
| // 搜索 | |||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||
| setSearchText(value); | |||
| }; | |||
| // 处理删除 | |||
| const handleServiceDelete = (record: ServiceData) => { | |||
| modalConfirm({ | |||
| title: '删除后,该服务将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteService(record); | |||
| }, | |||
| }); | |||
| }; | |||
| // 创建、更新服务 | |||
| const createService = (type: ServiceOperationType, record?: ServiceData) => { | |||
| SessionStorage.setItem( | |||
| SessionStorage.serviceInfoKey, | |||
| { | |||
| ...record, | |||
| operationType: type, | |||
| }, | |||
| true, | |||
| ); | |||
| setCacheState({ | |||
| pagination, | |||
| searchText, | |||
| }); | |||
| navigate(`/pipeline/autoML/create`); | |||
| }; | |||
| // 查看详情 | |||
| const toDetail = (record: ServiceData) => { | |||
| setCacheState({ | |||
| pagination, | |||
| searchText, | |||
| }); | |||
| navigate(`/pipeline/autoML/info/${record.id}`); | |||
| }; | |||
| // 分页切换 | |||
| const handleTableChange: TableProps<ServiceData>['onChange'] = ( | |||
| pagination, | |||
| _filters, | |||
| _sorter, | |||
| { action }, | |||
| ) => { | |||
| if (action === 'paginate') { | |||
| setPagination(pagination); | |||
| } | |||
| }; | |||
| const columns: TableProps<ServiceData>['columns'] = [ | |||
| { | |||
| title: '序号', | |||
| dataIndex: 'index', | |||
| key: 'index', | |||
| width: '20%', | |||
| render: tableCellRender(false, TableCellValueType.Index, { | |||
| page: pagination.current! - 1, | |||
| pageSize: pagination.pageSize!, | |||
| }), | |||
| }, | |||
| { | |||
| title: '实验名称', | |||
| dataIndex: 'service_name', | |||
| key: 'service_name', | |||
| width: '20%', | |||
| render: tableCellRender(false, TableCellValueType.Link, { | |||
| onClick: toDetail, | |||
| }), | |||
| }, | |||
| { | |||
| title: '实验描述', | |||
| dataIndex: 'service_type_name', | |||
| key: 'service_type_name', | |||
| width: '20%', | |||
| render: tableCellRender(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '状态', | |||
| dataIndex: 'run_status', | |||
| key: 'run_status', | |||
| width: '20%', | |||
| render: RunStatusCell, | |||
| }, | |||
| { | |||
| title: '实验实例执行进度', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| render: ExecuteScheduleCell, | |||
| width: 180, | |||
| }, | |||
| { | |||
| title: '创建时间', | |||
| dataIndex: 'update_time', | |||
| key: 'update_time', | |||
| width: '20%', | |||
| render: tableCellRender(true, TableCellValueType.Date), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '修改时间', | |||
| dataIndex: 'update_time', | |||
| key: 'update_time', | |||
| width: '20%', | |||
| render: tableCellRender(true, TableCellValueType.Date), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '操作', | |||
| dataIndex: 'operation', | |||
| width: 400, | |||
| key: 'operation', | |||
| render: (_: any, record: ServiceData) => ( | |||
| <div> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon={<KFIcon type="icon-jingxiang" />} | |||
| onClick={() => createService(ServiceOperationType.Update, record)} | |||
| > | |||
| 镜像 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon={<KFIcon type="icon-bianji" />} | |||
| onClick={() => createService(ServiceOperationType.Update, record)} | |||
| > | |||
| 编辑 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="run" | |||
| icon={<KFIcon type="icon-fuzhi" />} | |||
| onClick={() => toDetail(record)} | |||
| > | |||
| 复制 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="run" | |||
| icon={<KFIcon type="icon-tingzhi" />} | |||
| onClick={() => toDetail(record)} | |||
| > | |||
| 停止 | |||
| </Button> | |||
| <ConfigProvider | |||
| theme={{ | |||
| token: { | |||
| colorLink: themes['warningColor'], | |||
| }, | |||
| }} | |||
| > | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="remove" | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| onClick={() => handleServiceDelete(record)} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| </ConfigProvider> | |||
| </div> | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['auto-ml-list']}> | |||
| <PageTitle title="自动机器学习列表"></PageTitle> | |||
| <div className={styles['auto-ml-list__content']}> | |||
| <div className={styles['auto-ml-list__content__filter']}> | |||
| <Input.Search | |||
| placeholder="按实验名称筛选" | |||
| onSearch={onSearch} | |||
| onChange={(e) => setInputText(e.target.value)} | |||
| style={{ width: 300 }} | |||
| value={inputText} | |||
| allowClear | |||
| /> | |||
| <Button | |||
| style={{ marginLeft: '20px' }} | |||
| type="default" | |||
| onClick={() => createService(ServiceOperationType.Create)} | |||
| icon={<KFIcon type="icon-xinjian2" />} | |||
| > | |||
| 新建实验 | |||
| </Button> | |||
| </div> | |||
| <div | |||
| className={classNames('vertical-scroll-table', styles['auto-ml-list__content__table'])} | |||
| > | |||
| <Table | |||
| dataSource={tableData} | |||
| columns={columns} | |||
| scroll={{ y: 'calc(100% - 55px)' }} | |||
| pagination={{ | |||
| ...pagination, | |||
| total: total, | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| showTotal: () => `共${total}条`, | |||
| }} | |||
| onChange={handleTableChange} | |||
| rowKey="id" | |||
| /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default AutoMLList; | |||
| @@ -0,0 +1,7 @@ | |||
| .auto-ml-basic { | |||
| height: 100%; | |||
| padding: 20px @content-padding; | |||
| overflow-y: auto; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| } | |||
| @@ -0,0 +1,81 @@ | |||
| import { Flex } from 'antd'; | |||
| import { useEffect } from 'react'; | |||
| import ConfigInfo, { type BasicInfoData } from '../ConfigInfo'; | |||
| import CopyingText from '../CopyingText'; | |||
| import StatusChart from '../StatusChart'; | |||
| import styles from './index.less'; | |||
| function AutoMLBasic() { | |||
| useEffect(() => {}, []); | |||
| const datas: BasicInfoData[] = [ | |||
| { | |||
| label: '项目名称', | |||
| value: '测试项目名称', | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '项目名称', | |||
| value: '测试项目名称', | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '项目名称', | |||
| value: '测试项目名称', | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '项目名称', | |||
| value: '测试项目名称', | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '项目名称', | |||
| value: '测试项目名称', | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '项目名称', | |||
| value: '测试项目名称', | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '项目名称', | |||
| value: '测试项目名称', | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '项目名称', | |||
| value: <CopyingText text="测试项目名称测试项目名称测试项目名称"></CopyingText>, | |||
| ellipsis: false, | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['auto-ml-basic']}> | |||
| <Flex gap={15} align="stretch"> | |||
| <ConfigInfo title="基本信息" data={datas} labelWidth={70} /> | |||
| <StatusChart | |||
| chartData={{ Failed: 10, Pending: 20, Running: 30, Succeeded: 40, Terminated: 50 }} | |||
| /> | |||
| <ConfigInfo title="Trial 配置" data={datas} labelWidth={70} /> | |||
| </Flex> | |||
| <ConfigInfo | |||
| title="搜索配置" | |||
| data={datas} | |||
| style={{ marginTop: '16px' }} | |||
| labelWidth={70} | |||
| threeColumn | |||
| /> | |||
| <ConfigInfo | |||
| title="执行配置" | |||
| data={datas} | |||
| style={{ marginTop: '16px' }} | |||
| labelWidth={70} | |||
| threeColumn | |||
| /> | |||
| </div> | |||
| ); | |||
| } | |||
| export default AutoMLBasic; | |||
| @@ -0,0 +1,15 @@ | |||
| .auto-ml-table { | |||
| height: 100%; | |||
| padding: 20px @content-padding 0; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| &__filter { | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| &__table { | |||
| height: calc(100% - 32px - 28px); | |||
| margin-top: 28px; | |||
| } | |||
| } | |||
| @@ -0,0 +1,305 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 自主机器学习 | |||
| */ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { | |||
| App, | |||
| Button, | |||
| ConfigProvider, | |||
| Input, | |||
| Table, | |||
| type TablePaginationConfig, | |||
| type TableProps, | |||
| } from 'antd'; | |||
| import { type SearchProps } from 'antd/es/input'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| // import ExecuteScheduleCell from '../components/ExecuteScheduleCell'; | |||
| import { ServiceData, ServiceOperationType } from '@/pages/AutoML/types'; | |||
| import RunStatusCell from '../RunStatusCell'; | |||
| import styles from './index.less'; | |||
| function AutoMLTable() { | |||
| const navigate = useNavigate(); | |||
| const { message } = App.useApp(); | |||
| const [cacheState, setCacheState] = useCacheState(); | |||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | |||
| const [inputText, setInputText] = useState(cacheState?.searchText); | |||
| const [tableData, setTableData] = useState<ServiceData[]>([]); | |||
| const [total, setTotal] = useState(0); | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||
| cacheState?.pagination ?? { | |||
| current: 1, | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| useEffect(() => { | |||
| getServiceList(); | |||
| }, [pagination, searchText]); | |||
| // 获取模型部署服务列表 | |||
| const getServiceList = async () => { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| service_name: searchText, | |||
| }; | |||
| const [res] = await to(getServiceListReq(params)); | |||
| if (res && res.data) { | |||
| const { content = [], totalElements = 0 } = res.data; | |||
| setTableData(content); | |||
| setTotal(totalElements); | |||
| } | |||
| }; | |||
| // 删除模型部署 | |||
| const deleteService = async (record: ServiceData) => { | |||
| const [res] = await to(deleteServiceReq(record.id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getServiceList(); | |||
| } | |||
| } | |||
| }; | |||
| // 搜索 | |||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||
| setSearchText(value); | |||
| }; | |||
| // 处理删除 | |||
| const handleServiceDelete = (record: ServiceData) => { | |||
| modalConfirm({ | |||
| title: '删除后,该服务将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteService(record); | |||
| }, | |||
| }); | |||
| }; | |||
| // 创建、更新服务 | |||
| const createService = (type: ServiceOperationType, record?: ServiceData) => { | |||
| SessionStorage.setItem( | |||
| SessionStorage.serviceInfoKey, | |||
| { | |||
| ...record, | |||
| operationType: type, | |||
| }, | |||
| true, | |||
| ); | |||
| setCacheState({ | |||
| pagination, | |||
| searchText, | |||
| }); | |||
| navigate(`/modelDeployment/createService`); | |||
| }; | |||
| // 查看详情 | |||
| const toDetail = (record: ServiceData) => { | |||
| setCacheState({ | |||
| pagination, | |||
| searchText, | |||
| }); | |||
| navigate(`/modelDeployment/serviceInfo/${record.id}`); | |||
| }; | |||
| // 分页切换 | |||
| const handleTableChange: TableProps<ServiceData>['onChange'] = ( | |||
| pagination, | |||
| _filters, | |||
| _sorter, | |||
| { action }, | |||
| ) => { | |||
| if (action === 'paginate') { | |||
| setPagination(pagination); | |||
| } | |||
| }; | |||
| const columns: TableProps<ServiceData>['columns'] = [ | |||
| { | |||
| title: '序号', | |||
| dataIndex: 'index', | |||
| key: 'index', | |||
| width: '20%', | |||
| render: tableCellRender(false, TableCellValueType.Index, { | |||
| page: pagination.current! - 1, | |||
| pageSize: pagination.pageSize!, | |||
| }), | |||
| }, | |||
| { | |||
| title: 'Trial ID', | |||
| dataIndex: 'service_name', | |||
| key: 'service_name', | |||
| width: '20%', | |||
| render: tableCellRender(false, TableCellValueType.Link, { | |||
| onClick: toDetail, | |||
| }), | |||
| }, | |||
| { | |||
| title: '状态', | |||
| dataIndex: 'run_status', | |||
| key: 'run_status', | |||
| width: '20%', | |||
| render: RunStatusCell, | |||
| }, | |||
| { | |||
| title: '最终指标', | |||
| dataIndex: 'service_type_name', | |||
| key: 'service_type_name', | |||
| width: '20%', | |||
| render: tableCellRender(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '当前指标', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| width: '20%', | |||
| render: tableCellRender(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: 'lr', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| width: '20%', | |||
| render: tableCellRender(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: 'batch_size', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| width: '20%', | |||
| render: tableCellRender(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '修改时间', | |||
| dataIndex: 'update_time', | |||
| key: 'update_time', | |||
| width: '20%', | |||
| render: tableCellRender(true, TableCellValueType.Date), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '执行时长', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| width: '20%', | |||
| render: tableCellRender(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '操作', | |||
| dataIndex: 'operation', | |||
| width: 400, | |||
| key: 'operation', | |||
| render: (_: any, record: ServiceData) => ( | |||
| <div> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon={<KFIcon type="icon-rizhi" />} | |||
| onClick={() => createService(ServiceOperationType.Update, record)} | |||
| > | |||
| 日志 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="run" | |||
| icon={<KFIcon type="icon-tingzhi" />} | |||
| onClick={() => toDetail(record)} | |||
| > | |||
| 停止 | |||
| </Button> | |||
| <ConfigProvider | |||
| theme={{ | |||
| token: { | |||
| colorLink: themes['textColorTertiary'], | |||
| }, | |||
| }} | |||
| > | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="remove" | |||
| icon={<KFIcon type="icon-zhongpao" />} | |||
| onClick={() => handleServiceDelete(record)} | |||
| > | |||
| 重跑 | |||
| </Button> | |||
| </ConfigProvider> | |||
| </div> | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['auto-ml-table']}> | |||
| <div className={styles['auto-ml-table__filter']}> | |||
| <Input.Search | |||
| placeholder="按服务名称筛选" | |||
| onSearch={onSearch} | |||
| onChange={(e) => setInputText(e.target.value)} | |||
| style={{ width: 300 }} | |||
| value={inputText} | |||
| allowClear | |||
| /> | |||
| <Button | |||
| style={{ marginRight: 0, marginLeft: 'auto' }} | |||
| type="default" | |||
| onClick={() => {}} | |||
| icon={<KFIcon type="icon-shuaxin" />} | |||
| > | |||
| 刷新 | |||
| </Button> | |||
| </div> | |||
| <div className={classNames('vertical-scroll-table', styles['auto-ml-table__table'])}> | |||
| <Table | |||
| dataSource={tableData} | |||
| columns={columns} | |||
| scroll={{ y: 'calc(100% - 55px)' }} | |||
| pagination={{ | |||
| ...pagination, | |||
| total: total, | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| showTotal: () => `共${total}条`, | |||
| }} | |||
| onChange={handleTableChange} | |||
| rowKey="id" | |||
| /> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default AutoMLTable; | |||
| @@ -0,0 +1,42 @@ | |||
| .config-info { | |||
| width: 100%; | |||
| border: 1px solid @border-color-base; | |||
| border-radius: 4px; | |||
| &__content { | |||
| padding: 20px; | |||
| padding: 20px @content-padding; | |||
| background-color: white; | |||
| } | |||
| :global { | |||
| .kf-basic-info { | |||
| gap: 15px 40px; | |||
| width: 100%; | |||
| &__item { | |||
| &__label { | |||
| font-size: @font-size; | |||
| text-align: left; | |||
| text-align-last: left; | |||
| &::after { | |||
| display: none; | |||
| } | |||
| } | |||
| &__value { | |||
| font-size: @font-size; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| &--three-column { | |||
| :global { | |||
| .kf-basic-info { | |||
| &__item { | |||
| width: calc((100% - 80px) / 3); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect } from 'react'; | |||
| import ConfigTitle from '../ConfigTitle'; | |||
| import styles from './index.less'; | |||
| export { type BasicInfoData }; | |||
| type ConfigInfoProps = { | |||
| title: string; | |||
| data: BasicInfoData[]; | |||
| className?: string; | |||
| style?: React.CSSProperties; | |||
| children?: React.ReactNode; | |||
| labelWidth: number; | |||
| threeColumn?: boolean; | |||
| }; | |||
| function ConfigInfo({ | |||
| title, | |||
| data, | |||
| className, | |||
| style, | |||
| children, | |||
| labelWidth, | |||
| threeColumn = false, | |||
| }: ConfigInfoProps) { | |||
| useEffect(() => {}, []); | |||
| return ( | |||
| <div | |||
| className={classNames( | |||
| styles['config-info'], | |||
| { [styles['config-info--three-column']]: threeColumn }, | |||
| className, | |||
| )} | |||
| style={style} | |||
| > | |||
| <ConfigTitle title={title} /> | |||
| <div className={styles['config-info__content']}> | |||
| <BasicInfo datas={data} labelWidth={labelWidth} /> | |||
| {children} | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ConfigInfo; | |||
| @@ -0,0 +1,38 @@ | |||
| .config-title { | |||
| width: 100%; | |||
| height: 56px; | |||
| padding-left: @content-padding; | |||
| background: linear-gradient( | |||
| 179.03deg, | |||
| rgba(199, 223, 255, 0.12) 0%, | |||
| rgba(22, 100, 255, 0.04) 100% | |||
| ); | |||
| border: 1px solid #e8effb; | |||
| &__img { | |||
| width: 16px; | |||
| height: 16px; | |||
| margin-right: 10px; | |||
| } | |||
| &__text { | |||
| position: relative; | |||
| color: @text-color; | |||
| font-weight: 500; | |||
| font-size: @font-size-title; | |||
| &::after { | |||
| position: absolute; | |||
| bottom: 6px; | |||
| left: 0; | |||
| width: 100%; | |||
| height: 6px; | |||
| background: linear-gradient( | |||
| to right, | |||
| .addAlpha(@primary-color, 0.4) [] 0, | |||
| .addAlpha(@primary-color, 0) [] 100% | |||
| ); | |||
| content: ''; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| import { Flex } from 'antd'; | |||
| import styles from './index.less'; | |||
| type ConfigTitleProps = { | |||
| title: string; | |||
| }; | |||
| function ConfigTitle({ title }: ConfigTitleProps) { | |||
| return ( | |||
| <Flex align="center" className={styles['config-title']}> | |||
| <img | |||
| src={require('@/assets/img/code-name-icon.png')} | |||
| className={styles['config-title__img']} | |||
| alt="" | |||
| draggable={false} | |||
| /> | |||
| <span className={styles['config-title__text']}>{title}</span> | |||
| </Flex> | |||
| ); | |||
| } | |||
| export default ConfigTitle; | |||
| @@ -0,0 +1,18 @@ | |||
| .copying-text { | |||
| display: flex; | |||
| flex: 1; | |||
| align-items: center; | |||
| min-width: 0; | |||
| margin-left: 16px; | |||
| &__text { | |||
| color: @text-color; | |||
| font-size: 15px; | |||
| } | |||
| &__icon { | |||
| margin-left: 6px; | |||
| font-size: 14px; | |||
| cursor: pointer; | |||
| } | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { Typography } from 'antd'; | |||
| import styles from './index.less'; | |||
| export type CopyingTextProps = { | |||
| text: string; | |||
| onCopySuccess?: () => void; | |||
| onCopyFailed?: () => void; | |||
| }; | |||
| function CopyingText({ text }: CopyingTextProps) { | |||
| return ( | |||
| <div className={styles['copying-text']}> | |||
| <Typography.Text | |||
| ellipsis={{ tooltip: text }} | |||
| style={{ color: 'inherit' }} | |||
| className={styles['copying-text__text']} | |||
| > | |||
| {text} | |||
| </Typography.Text> | |||
| <KFIcon | |||
| id="copying" | |||
| data-clipboard-text={text} | |||
| type="icon-fuzhi2" | |||
| className={styles['copying-text__icon']} | |||
| color="#606b7a" | |||
| /> | |||
| </div> | |||
| ); | |||
| } | |||
| export default CopyingText; | |||
| @@ -0,0 +1,85 @@ | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { Col, Form, Input, Row, Select } from 'antd'; | |||
| function BasicConfig() { | |||
| return ( | |||
| <> | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| image={require('@/assets/img/mirror-basic.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="服务名称" | |||
| name="service_name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入服务名称', | |||
| }, | |||
| { | |||
| pattern: /^(?!\d)[\u4e00-\u9fa5\w-]+$/, | |||
| message: '不能以数字开头,不能存在除了-_以外的特殊字符', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input | |||
| placeholder="不能以数字开头,不能存在除了-_以外的特殊字符" | |||
| maxLength={256} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={20}> | |||
| <Form.Item | |||
| label="描述" | |||
| name="description" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入描述', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| placeholder="请输入描述" | |||
| maxLength={256} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="可见范围" | |||
| name="version" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择可见范围', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择可见范围" | |||
| options={[]} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| optionFilterProp="name" | |||
| showSearch | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </> | |||
| ); | |||
| } | |||
| export default BasicConfig; | |||
| @@ -0,0 +1,134 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | |||
| import { Button, Col, Flex, Form, Input, Radio, Row } from 'antd'; | |||
| import ExecuteConfigDLC from './ExecuteConfigDLC'; | |||
| import ExecuteConfigMC from './ExecuteConfigMC'; | |||
| import styles from './index.less'; | |||
| type ExecuteConfigProps = { | |||
| disabled?: boolean; | |||
| }; | |||
| function ExecuteConfig({ disabled = false }: ExecuteConfigProps) { | |||
| const form = Form.useFormInstance(); | |||
| const image_type = Form.useWatch('image_type', form); | |||
| console.log(image_type); | |||
| return ( | |||
| <> | |||
| <SubAreaTitle | |||
| title="执行配置" | |||
| image={require('@/assets/img/model-deployment.png')} | |||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="任务类型" | |||
| name="execute_type" | |||
| rules={[{ required: true, message: '请选择任务类型' }]} | |||
| > | |||
| <Radio.Group> | |||
| <Radio value={'DLC'}>DLC</Radio> | |||
| <Radio value={'MaxCompute'}>MaxCompute</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Form.Item dependencies={['execute_type']} noStyle> | |||
| {({ getFieldValue }) => { | |||
| return getFieldValue('execute_type') === 'DLC' ? ( | |||
| <ExecuteConfigDLC disabled={disabled}></ExecuteConfigDLC> | |||
| ) : ( | |||
| <ExecuteConfigMC disabled={disabled}></ExecuteConfigMC> | |||
| ); | |||
| }} | |||
| </Form.Item> | |||
| <Form.List name="hyper-parameter"> | |||
| {(fields, { add, remove }) => ( | |||
| <> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="超参数" | |||
| style={{ marginBottom: 0, marginTop: '-14px' }} | |||
| tooltip="超参数" | |||
| ></Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <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__space']}>搜索空间</div> | |||
| <div className={styles['hyper-parameter__header__operation']}>操作</div> | |||
| </Flex> | |||
| {fields.map(({ key, name, ...restField }, index) => ( | |||
| <Flex key={key} align="center" className={styles['hyper-parameter__body']}> | |||
| <Form.Item | |||
| className={styles['hyper-parameter__body__name']} | |||
| {...restField} | |||
| name={[name, 'name']} | |||
| rules={[{ required: true, message: 'Missing first name' }]} | |||
| > | |||
| <Input placeholder="Key" /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| className={styles['hyper-parameter__body__name']} | |||
| {...restField} | |||
| name={[name, 'type']} | |||
| rules={[{ required: true, message: 'Missing last name' }]} | |||
| > | |||
| <Input placeholder="Value" /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| className={styles['hyper-parameter__body__name']} | |||
| {...restField} | |||
| name={[name, 'space']} | |||
| rules={[{ required: true, message: 'Missing last name' }]} | |||
| > | |||
| <Input placeholder="Value" /> | |||
| </Form.Item> | |||
| <div className={styles['hyper-parameter__body__operation']}> | |||
| <Button | |||
| style={{ | |||
| marginRight: '3px', | |||
| }} | |||
| shape="circle" | |||
| disabled={fields.length === 1} | |||
| type="text" | |||
| size="middle" | |||
| onClick={() => remove(name)} | |||
| icon={<MinusCircleOutlined />} | |||
| ></Button> | |||
| {index === fields.length - 1 && ( | |||
| <Button | |||
| shape="circle" | |||
| size="middle" | |||
| type="text" | |||
| onClick={() => add()} | |||
| icon={<PlusCircleOutlined />} | |||
| ></Button> | |||
| )} | |||
| </div> | |||
| </Flex> | |||
| ))} | |||
| {fields.length === 0 && ( | |||
| <div className={styles['hyper-parameter__add']}> | |||
| <Button type="link" onClick={() => add()} icon={<KFIcon type="icon-xinjian2" />}> | |||
| 添加一行 | |||
| </Button> | |||
| </div> | |||
| )} | |||
| </div> | |||
| </> | |||
| )} | |||
| </Form.List> | |||
| </> | |||
| ); | |||
| } | |||
| export default ExecuteConfig; | |||
| @@ -0,0 +1,258 @@ | |||
| import CodeSelect from '@/components/CodeSelect'; | |||
| import ResourceSelect, { ResourceSelectorType } from '@/components/ResourceSelect'; | |||
| import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | |||
| import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select } from 'antd'; | |||
| import styles from './index.less'; | |||
| type ExecuteConfigDLCProps = { | |||
| disabled?: boolean; | |||
| }; | |||
| function ExecuteConfigDLC({ disabled = false }: ExecuteConfigDLCProps) { | |||
| return ( | |||
| <> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="资源组" | |||
| name="version" | |||
| rules={[ | |||
| { | |||
| required: false, | |||
| message: '请选择资源组', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择资源组" | |||
| options={[]} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| optionFilterProp="name" | |||
| showSearch | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="框架" | |||
| name="version" | |||
| rules={[ | |||
| { | |||
| required: false, | |||
| message: '请选择框架', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择框架" | |||
| options={[]} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| optionFilterProp="name" | |||
| showSearch | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="数据集" | |||
| name="model" | |||
| // rules={[ | |||
| // { | |||
| // validator: requiredValidator, | |||
| // message: '请选择数据集', | |||
| // }, | |||
| // ]} | |||
| required | |||
| > | |||
| <ResourceSelect | |||
| type={ResourceSelectorType.Dataset} | |||
| placeholder="请选择数据集" | |||
| canInput={false} | |||
| size="large" | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="代码配置" | |||
| name="code_config" | |||
| // rules={[ | |||
| // { | |||
| // validator: requiredValidator, | |||
| // message: '请选择代码配置', | |||
| // }, | |||
| // ]} | |||
| required | |||
| > | |||
| <CodeSelect | |||
| placeholder="请选择代码配置" | |||
| canInput={false} | |||
| size="large" | |||
| disabled={disabled} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="节点镜像" | |||
| name="image_type" | |||
| rules={[{ required: true, message: '请选择节点镜像' }]} | |||
| tooltip="节点镜像" | |||
| > | |||
| <Radio.Group> | |||
| <Radio value={1}>官方镜像</Radio> | |||
| <Radio value={2}>自定义镜像</Radio> | |||
| <Radio value={3}>镜像地址</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item dependencies={['image_type']} noStyle> | |||
| {({ getFieldValue }) => { | |||
| const imageType = getFieldValue('image_type'); | |||
| if (imageType === 1 || imageType === 2) { | |||
| return ( | |||
| <Form.Item name="image_url" label=" " className="image-url"> | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择框架" | |||
| options={[]} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| optionFilterProp="name" | |||
| showSearch | |||
| /> | |||
| </Form.Item> | |||
| ); | |||
| } else { | |||
| return ( | |||
| <Form.Item name="image_url" label=" " className="image-url"> | |||
| <Input placeholder="请输入" disabled={disabled} /> | |||
| </Form.Item> | |||
| ); | |||
| } | |||
| }} | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="节点数量" | |||
| name="replicas" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入节点数量', | |||
| }, | |||
| { | |||
| pattern: /^[1-9]\d*$/, | |||
| message: '节点数量必须是正整数', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入节点数量" min={1} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="高级配置" tooltip="高级配置"> | |||
| <Form.List name="advanced_config"> | |||
| {(fields, { add, remove }) => ( | |||
| <> | |||
| {fields.map(({ key, name, ...restField }, index) => ( | |||
| <Flex key={key} align="center" className={styles['advanced-config']}> | |||
| <Form.Item | |||
| style={{ flex: 1, marginBottom: 0 }} | |||
| {...restField} | |||
| name={[name, 'key']} | |||
| rules={[{ required: true, message: 'Missing first name' }]} | |||
| > | |||
| <Input placeholder="Key" /> | |||
| </Form.Item> | |||
| <span style={{ margin: '0 8px' }}>:</span> | |||
| <Form.Item | |||
| style={{ flex: 1, marginBottom: 0 }} | |||
| {...restField} | |||
| name={[name, 'value']} | |||
| rules={[{ required: true, message: 'Missing last name' }]} | |||
| > | |||
| <Input placeholder="Value" /> | |||
| </Form.Item> | |||
| <div style={{ width: '76px', marginLeft: '18px' }}> | |||
| <Button | |||
| style={{ | |||
| marginRight: '3px', | |||
| }} | |||
| size="middle" | |||
| shape="circle" | |||
| disabled={fields.length === 1} | |||
| type="text" | |||
| onClick={() => remove(name)} | |||
| icon={<MinusCircleOutlined />} | |||
| ></Button> | |||
| {index === fields.length - 1 && ( | |||
| <Button | |||
| size="middle" | |||
| shape="circle" | |||
| type="text" | |||
| onClick={() => add()} | |||
| icon={<PlusCircleOutlined />} | |||
| ></Button> | |||
| )} | |||
| </div> | |||
| </Flex> | |||
| ))} | |||
| {fields.length === 0 && ( | |||
| <Form.Item style={{ marginBottom: 0 }}> | |||
| <Button | |||
| style={{ background: 'white' }} | |||
| type="dashed" | |||
| onClick={() => add()} | |||
| block | |||
| icon={<PlusCircleOutlined />} | |||
| > | |||
| 添加高级配置 | |||
| </Button> | |||
| </Form.Item> | |||
| )} | |||
| </> | |||
| )} | |||
| </Form.List> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="节点启动命令" | |||
| name="mount_path" | |||
| // rules={[ | |||
| // { | |||
| // required: true, | |||
| // message: '请输入节点启动命令', | |||
| // }, | |||
| // ]} | |||
| tooltip="节点启动命令" | |||
| > | |||
| <Input placeholder="请输入节点启动命令" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </> | |||
| ); | |||
| } | |||
| export default ExecuteConfigDLC; | |||
| @@ -0,0 +1,82 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | |||
| import { Button, Col, Flex, Form, Input, Row } from 'antd'; | |||
| import styles from './index.less'; | |||
| type ExecuteConfigMCProps = { | |||
| disabled?: boolean; | |||
| }; | |||
| function ExecuteConfigMC({ disabled = false }: ExecuteConfigMCProps) { | |||
| return ( | |||
| <> | |||
| <Form.List name="command"> | |||
| {(fields, { add, remove }) => ( | |||
| <> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="命令" | |||
| style={{ marginBottom: 0, marginTop: '-14px' }} | |||
| tooltip="命令" | |||
| ></Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <div className={styles['command']}> | |||
| <Flex align="center" className={styles['command__header']}> | |||
| <div className={styles['command__header__name']}>Key</div> | |||
| <div className={styles['command__header__command']}>命令</div> | |||
| <div className={styles['command__header__operation']}>操作</div> | |||
| </Flex> | |||
| {fields.map(({ key, name, ...restField }, index) => ( | |||
| <Flex key={key} align="center" className={styles['command__body']}> | |||
| <span className={styles['command__body__name']}>cmd{index + 1}</span> | |||
| <Form.Item | |||
| className={styles['command__body__command']} | |||
| {...restField} | |||
| name={[name, 'command']} | |||
| rules={[{ required: true, message: 'Missing last name' }]} | |||
| > | |||
| <Input.TextArea placeholder="Value" autoSize={{ minRows: 2, maxRows: 3 }} /> | |||
| </Form.Item> | |||
| <div className={styles['command__body__operation']}> | |||
| <Button | |||
| style={{ | |||
| marginRight: '3px', | |||
| }} | |||
| shape="circle" | |||
| disabled={fields.length === 1} | |||
| type="text" | |||
| size="middle" | |||
| onClick={() => remove(name)} | |||
| icon={<MinusCircleOutlined />} | |||
| ></Button> | |||
| {index === fields.length - 1 && ( | |||
| <Button | |||
| shape="circle" | |||
| size="middle" | |||
| type="text" | |||
| onClick={() => add()} | |||
| icon={<PlusCircleOutlined />} | |||
| ></Button> | |||
| )} | |||
| </div> | |||
| </Flex> | |||
| ))} | |||
| {fields.length === 0 && ( | |||
| <div className={styles['hyper-parameter__add']}> | |||
| <Button type="link" onClick={() => add()} icon={<KFIcon type="icon-xinjian2" />}> | |||
| 添加一行 | |||
| </Button> | |||
| </div> | |||
| )} | |||
| </div> | |||
| </> | |||
| )} | |||
| </Form.List> | |||
| </> | |||
| ); | |||
| } | |||
| export default ExecuteConfigMC; | |||
| @@ -0,0 +1,119 @@ | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { Col, Form, InputNumber, Row, Select, Switch } from 'antd'; | |||
| function SearchConfig() { | |||
| return ( | |||
| <> | |||
| <SubAreaTitle | |||
| title="搜索配置" | |||
| image={require('@/assets/img/search-config-icon.png')} | |||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="搜索算法" | |||
| name="version" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择搜索算法', | |||
| }, | |||
| ]} | |||
| tooltip="搜索算法" | |||
| > | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择搜索算法" | |||
| options={[]} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| optionFilterProp="name" | |||
| showSearch | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="最大搜素次数" | |||
| name="replicas" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入最大搜素次数', | |||
| }, | |||
| { | |||
| pattern: /^[1-9]\d*$/, | |||
| message: '最大搜素次数必须是正整数', | |||
| }, | |||
| ]} | |||
| tooltip="最大搜素次数" | |||
| > | |||
| <InputNumber placeholder="请输入最大搜素次数" min={1} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="最大并发量" | |||
| name="replicas" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入最大并发量', | |||
| }, | |||
| { | |||
| pattern: /^[1-9]\d*$/, | |||
| message: '最大并发量必须是正整数', | |||
| }, | |||
| ]} | |||
| tooltip="最大并发量" | |||
| > | |||
| <InputNumber placeholder="请输入最大并发量" min={1} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="开启EarlyStop" | |||
| name="replicas" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择是否开启EarlyStop', | |||
| }, | |||
| ]} | |||
| tooltip="开启EarlyStop" | |||
| > | |||
| <Switch checkedChildren="开启" unCheckedChildren="关闭" defaultChecked /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="Start step" | |||
| name="replicas" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入Start step', | |||
| }, | |||
| { | |||
| pattern: /^[1-9]\d*$/, | |||
| message: 'Start step必须是正整数', | |||
| }, | |||
| ]} | |||
| tooltip="Start step" | |||
| > | |||
| <InputNumber placeholder="请输入Start step" min={1} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </> | |||
| ); | |||
| } | |||
| export default SearchConfig; | |||
| @@ -0,0 +1,193 @@ | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | |||
| import { Button, Col, Flex, Form, Input, Radio, Row, Select } from 'antd'; | |||
| import styles from './index.less'; | |||
| function TrialConfig() { | |||
| return ( | |||
| <> | |||
| <SubAreaTitle | |||
| title="Trial配置" | |||
| image={require('@/assets/img/trial-config-icon.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| ></SubAreaTitle> | |||
| <div className={styles['trial-metrics']}> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="优化指标"></Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={24}> | |||
| <Form.Item | |||
| label="指标类型" | |||
| name="version" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择指标类型', | |||
| }, | |||
| ]} | |||
| labelCol={{ flex: '120px' }} | |||
| > | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择指标类型" | |||
| options={[]} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| optionFilterProp="name" | |||
| showSearch | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={24}> | |||
| <Form.Item | |||
| label="计算方式" | |||
| name="version" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择计算方式', | |||
| }, | |||
| ]} | |||
| labelCol={{ flex: '120px' }} | |||
| > | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择计算方式" | |||
| options={[]} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| optionFilterProp="name" | |||
| showSearch | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={24}> | |||
| <Form.Item label="指标权重" tooltip="指标权重" labelCol={{ flex: '120px' }}> | |||
| <Form.List name="metrics_weight"> | |||
| {(fields, { add, remove }) => ( | |||
| <> | |||
| {fields.map(({ key, name, ...restField }, index) => ( | |||
| <Flex key={key} align="center" className={styles['advanced-config']}> | |||
| <Form.Item | |||
| style={{ flex: 1, marginBottom: 0 }} | |||
| {...restField} | |||
| name={[name, 'key']} | |||
| rules={[{ required: true, message: 'Missing first name' }]} | |||
| > | |||
| <Input placeholder="Key" /> | |||
| </Form.Item> | |||
| <span style={{ margin: '0 8px' }}>:</span> | |||
| <Form.Item | |||
| style={{ flex: 1, marginBottom: 0 }} | |||
| {...restField} | |||
| name={[name, 'value']} | |||
| rules={[{ required: true, message: 'Missing last name' }]} | |||
| > | |||
| <Input placeholder="Value" /> | |||
| </Form.Item> | |||
| <div style={{ width: '76px', marginLeft: '18px' }}> | |||
| <Button | |||
| style={{ | |||
| marginRight: '3px', | |||
| }} | |||
| shape="circle" | |||
| size="middle" | |||
| disabled={fields.length === 1} | |||
| type="text" | |||
| onClick={() => remove(name)} | |||
| icon={<MinusCircleOutlined />} | |||
| ></Button> | |||
| {index === fields.length - 1 && ( | |||
| <Button | |||
| shape="circle" | |||
| size="middle" | |||
| type="text" | |||
| onClick={() => add()} | |||
| icon={<PlusCircleOutlined />} | |||
| ></Button> | |||
| )} | |||
| </div> | |||
| </Flex> | |||
| ))} | |||
| {fields.length === 0 && ( | |||
| <Form.Item style={{ marginBottom: 0 }}> | |||
| <Button | |||
| style={{ background: 'white' }} | |||
| type="dashed" | |||
| onClick={() => add()} | |||
| block | |||
| icon={<PlusCircleOutlined />} | |||
| > | |||
| 添加指标权重 | |||
| </Button> | |||
| </Form.Item> | |||
| )} | |||
| </> | |||
| )} | |||
| </Form.List> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={0}> | |||
| <Col span={24}> | |||
| <Form.Item | |||
| label="指标来源" | |||
| name="service_name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入指标来源', | |||
| }, | |||
| ]} | |||
| tooltip="指标来源" | |||
| labelCol={{ flex: '120px' }} | |||
| > | |||
| <Input placeholder="请输入指标来源" maxLength={256} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={0}> | |||
| <Col span={24}> | |||
| <Form.Item | |||
| label="优化方向" | |||
| name="execute_type" | |||
| rules={[{ required: true, message: '请选择优化方向' }]} | |||
| labelCol={{ flex: '120px' }} | |||
| > | |||
| <Radio.Group> | |||
| <Radio value={1}>越大越好</Radio> | |||
| <Radio value={2}>越小越好</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </div> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="模型名称" | |||
| name="service_name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入模型名称', | |||
| }, | |||
| ]} | |||
| tooltip="模型名称" | |||
| > | |||
| <Input placeholder="请输入模型名称" maxLength={256} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </> | |||
| ); | |||
| } | |||
| export default TrialConfig; | |||
| @@ -0,0 +1,130 @@ | |||
| .advanced-config { | |||
| margin-bottom: 20px; | |||
| &:last-child { | |||
| margin-bottom: 0; | |||
| } | |||
| } | |||
| .command { | |||
| width: 83.33%; | |||
| margin-bottom: 20px; | |||
| border: 1px solid rgba(234, 234, 234, 0.8); | |||
| border-radius: 4px; | |||
| &__header { | |||
| height: 50px; | |||
| padding-left: 8px; | |||
| color: @text-color; | |||
| font-size: @font-size; | |||
| background: #f8f8f9; | |||
| border-radius: 4px 4px 0px 0px; | |||
| &__name { | |||
| flex: none; | |||
| width: 100px; | |||
| } | |||
| &__command { | |||
| flex: 1; | |||
| margin-right: 15px; | |||
| } | |||
| &__operation { | |||
| flex: none; | |||
| width: 100px; | |||
| } | |||
| } | |||
| &__body { | |||
| padding: 8px; | |||
| border-bottom: 1px solid rgba(234, 234, 234, 0.8); | |||
| &:last-child { | |||
| border-bottom: none; | |||
| } | |||
| &__name { | |||
| flex: none; | |||
| width: 100px; | |||
| } | |||
| &__command { | |||
| flex: 1; | |||
| margin-right: 15px; | |||
| margin-bottom: 0 !important; | |||
| } | |||
| &__operation { | |||
| flex: none; | |||
| width: 100px; | |||
| } | |||
| } | |||
| &__add { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| padding: 15px 0; | |||
| } | |||
| } | |||
| .hyper-parameter { | |||
| width: 83.33%; | |||
| margin-bottom: 20px; | |||
| border: 1px solid rgba(234, 234, 234, 0.8); | |||
| border-radius: 4px; | |||
| &__header { | |||
| height: 50px; | |||
| padding-left: 8px; | |||
| color: @text-color; | |||
| font-size: @font-size; | |||
| background: #f8f8f9; | |||
| border-radius: 4px 4px 0px 0px; | |||
| &__name, | |||
| &__type, | |||
| &__space { | |||
| flex: 1; | |||
| margin-right: 15px; | |||
| } | |||
| &__operation { | |||
| flex: none; | |||
| width: 100px; | |||
| } | |||
| } | |||
| &__body { | |||
| padding: 8px; | |||
| border-bottom: 1px solid rgba(234, 234, 234, 0.8); | |||
| &:last-child { | |||
| border-bottom: none; | |||
| } | |||
| &__name, | |||
| &__type, | |||
| &__space { | |||
| flex: 1; | |||
| margin-right: 15px; | |||
| margin-bottom: 0 !important; | |||
| } | |||
| &__operation { | |||
| flex: none; | |||
| width: 100px; | |||
| } | |||
| } | |||
| &__add { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| padding: 15px 0; | |||
| } | |||
| } | |||
| .trial-metrics { | |||
| width: calc(41.67% - 6px); | |||
| margin-bottom: 20px; | |||
| padding: 0 20px; | |||
| border: 1px dashed #e0e0e0; | |||
| border-radius: 8px; | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| .execute-schedule-cell { | |||
| display: flex; | |||
| flex-direction: row; | |||
| align-items: center; | |||
| &__progress { | |||
| width: 112px; | |||
| height: 16px; | |||
| padding: 4px 5px; | |||
| background: rgba(96, 107, 122, 0.15); | |||
| border-radius: 8px; | |||
| &__bar { | |||
| height: 7px; | |||
| background: linear-gradient(270deg, #1664ff 0%, rgba(22, 100, 255, 0.1) 100%); | |||
| border-radius: 9px; | |||
| } | |||
| } | |||
| &__text { | |||
| margin-left: 6px; | |||
| color: @text-color-tertiary; | |||
| font-size: 12px; | |||
| letter-spacing: 2px; | |||
| strong { | |||
| color: @text-color; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-10-29 18:35:41 | |||
| * @Description: 实验实例执行进度 | |||
| */ | |||
| import styles from './index.less'; | |||
| function ExecuteScheduleCell(status?: any) { | |||
| return ( | |||
| <div className={styles['execute-schedule-cell']}> | |||
| <div className={styles['execute-schedule-cell__progress']}> | |||
| <div | |||
| className={styles['execute-schedule-cell__progress__bar']} | |||
| style={{ width: '80%' }} | |||
| ></div> | |||
| </div> | |||
| <span className={styles['execute-schedule-cell__text']}> | |||
| 1/<strong>2</strong> | |||
| </span> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExecuteScheduleCell; | |||
| @@ -0,0 +1,19 @@ | |||
| .run-status-cell { | |||
| color: @text-color; | |||
| &--running { | |||
| color: @primary-color; | |||
| } | |||
| &--stopped { | |||
| color: @abort-color; | |||
| } | |||
| &--error { | |||
| color: @error-color; | |||
| } | |||
| &--pending { | |||
| color: @warning-color; | |||
| } | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-18 18:35:41 | |||
| * @Description: 实验运行状态 | |||
| */ | |||
| import { ServiceRunStatus } from '@/enums'; | |||
| import styles from './index.less'; | |||
| export type ServiceRunStatusInfo = { | |||
| text: string; | |||
| classname: string; | |||
| }; | |||
| export const statusInfo: Record<ServiceRunStatus, ServiceRunStatusInfo> = { | |||
| [ServiceRunStatus.Init]: { | |||
| text: '启动中', | |||
| classname: styles['run-status-cell'], | |||
| }, | |||
| [ServiceRunStatus.Running]: { | |||
| classname: styles['run-status-cell--running'], | |||
| text: '运行中', | |||
| }, | |||
| [ServiceRunStatus.Stopped]: { | |||
| classname: styles['run-status-cell--stopped'], | |||
| text: '已停止', | |||
| }, | |||
| [ServiceRunStatus.Failed]: { | |||
| classname: styles['run-status-cell--error'], | |||
| text: '失败', | |||
| }, | |||
| [ServiceRunStatus.Pending]: { | |||
| classname: styles['run-status-cell--pending'], | |||
| text: '挂起中', | |||
| }, | |||
| }; | |||
| function RunStatusCell(status?: ServiceRunStatus | null) { | |||
| if (status === null || status === undefined || !statusInfo[status]) { | |||
| return <span>--</span>; | |||
| } | |||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | |||
| } | |||
| export default RunStatusCell; | |||
| @@ -0,0 +1,8 @@ | |||
| .status-chart { | |||
| flex: none; | |||
| width: 380px; | |||
| min-width: 380px; | |||
| background: #ffffff; | |||
| border: 1px solid @border-color-base; | |||
| border-radius: 4px; | |||
| } | |||
| @@ -0,0 +1,217 @@ | |||
| import themes from '@/styles/theme.less'; | |||
| import * as echarts from 'echarts'; | |||
| import React, { useEffect, useRef } from 'react'; | |||
| import ConfigTitle from '../ConfigTitle'; | |||
| import styles from './index.less'; | |||
| const color1 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: '#c73131' }, | |||
| { offset: 1, color: '#ff7e96' }, | |||
| ], | |||
| false, | |||
| ); | |||
| const color2 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: '#6ac21d' }, | |||
| { offset: 1, color: '#96e850' }, | |||
| ], | |||
| false, | |||
| ); | |||
| const color3 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: '#8c8c8c' }, | |||
| { offset: 1, color: '#c8c6c6' }, | |||
| ], | |||
| false, | |||
| ); | |||
| const color4 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: '#ecb934' }, | |||
| { offset: 1, color: '#f0864d' }, | |||
| ], | |||
| false, | |||
| ); | |||
| const color5 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: '#7ea9fe' }, | |||
| { offset: 1, color: '#1664ff' }, | |||
| ], | |||
| false, | |||
| ); | |||
| const color6 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: 'rgba(255, 255, 255, 0.62)' }, | |||
| { offset: 1, color: '#ebf2ff ' }, | |||
| ], | |||
| false, | |||
| ); | |||
| export type ExperimentStatistics = { | |||
| Failed: number; | |||
| Pending: number; | |||
| Running: number; | |||
| Succeeded: number; | |||
| Terminated: number; | |||
| }; | |||
| type ExperimentChartProps = { | |||
| style?: React.CSSProperties; | |||
| chartData: ExperimentStatistics; | |||
| }; | |||
| function StatusChart({ chartData, style }: ExperimentChartProps) { | |||
| const chartRef = useRef<HTMLDivElement>(null); | |||
| const total = | |||
| chartData.Failed + | |||
| chartData.Pending + | |||
| chartData.Running + | |||
| chartData.Succeeded + | |||
| chartData.Terminated; | |||
| const options: echarts.EChartsOption = { | |||
| title: { | |||
| show: true, | |||
| left: '29%', | |||
| top: 'center', | |||
| textAlign: 'center', | |||
| text: [`{a|${total}}`, '{b|Trials}'].join('\n'), | |||
| textStyle: { | |||
| rich: { | |||
| a: { | |||
| color: themes['textColor'], | |||
| fontSize: 20, | |||
| fontWeight: 700, | |||
| lineHeight: 28, | |||
| }, | |||
| b: { | |||
| color: themes['textColorSecondary'], | |||
| fontSize: 10, | |||
| fontWeight: 'normal', | |||
| }, | |||
| }, | |||
| }, | |||
| }, | |||
| tooltip: { | |||
| trigger: 'item', | |||
| }, | |||
| legend: { | |||
| top: 'center', | |||
| right: '5%', | |||
| orient: 'vertical', | |||
| icon: 'circle', | |||
| itemWidth: 6, | |||
| itemGap: 20, | |||
| height: 100, | |||
| }, | |||
| color: [color1, color2, color3, color4, color5], | |||
| series: [ | |||
| { | |||
| type: 'pie', | |||
| radius: ['70%', '80%'], | |||
| center: ['30%', '50%'], | |||
| avoidLabelOverlap: false, | |||
| padAngle: 3, | |||
| itemStyle: { | |||
| borderRadius: 3, | |||
| }, | |||
| minAngle: 5, | |||
| label: { | |||
| show: false, | |||
| }, | |||
| emphasis: { | |||
| label: { | |||
| show: false, | |||
| }, | |||
| }, | |||
| labelLine: { | |||
| show: false, | |||
| }, | |||
| data: [ | |||
| { value: chartData.Failed > 0 ? chartData.Failed : undefined, name: '失败' }, | |||
| { value: chartData.Succeeded > 0 ? chartData.Succeeded : undefined, name: '成功' }, | |||
| { value: chartData.Terminated > 0 ? chartData.Terminated : undefined, name: '中止' }, | |||
| { value: chartData.Pending > 0 ? chartData.Pending : undefined, name: '等待' }, | |||
| { value: chartData.Running > 0 ? chartData.Running : undefined, name: '运行中' }, | |||
| ], | |||
| }, | |||
| { | |||
| type: 'pie', | |||
| radius: '60%', | |||
| center: ['30%', '50%'], | |||
| avoidLabelOverlap: false, | |||
| label: { | |||
| show: false, | |||
| }, | |||
| tooltip: { | |||
| show: false, | |||
| }, | |||
| emphasis: { | |||
| label: { | |||
| show: false, | |||
| }, | |||
| disabled: true, | |||
| }, | |||
| animation: false, | |||
| labelLine: { | |||
| show: false, | |||
| }, | |||
| data: [ | |||
| { | |||
| value: 100, | |||
| itemStyle: { color: color6, borderColor: 'rgba(22, 100, 255, 0.08)', borderWidth: 1 }, | |||
| }, | |||
| ], | |||
| }, | |||
| ], | |||
| }; | |||
| useEffect(() => { | |||
| // 创建一个echarts实例,返回echarts实例 | |||
| const chart = echarts.init(chartRef.current); | |||
| // 设置图表实例的配置项和数据 | |||
| chart.setOption(options); | |||
| // 组件卸载 | |||
| return () => { | |||
| // myChart.dispose() 销毁实例 | |||
| chart.dispose(); | |||
| }; | |||
| }, []); | |||
| return ( | |||
| <div className={styles['status-chart']} style={style}> | |||
| <ConfigTitle title="Trial 状态统计" /> | |||
| <div style={{ width: '100%', height: '185px' }} ref={chartRef}></div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default StatusChart; | |||
| @@ -0,0 +1,6 @@ | |||
| // 操作类型 | |||
| export enum ServiceOperationType { | |||
| Create = 'Create', // 创建 | |||
| Update = 'Update', // 更新 | |||
| Restart = 'Restart', // 重启 | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| import ClipboardJS from 'clipboard'; | |||
| import { message } from "antd"; | |||
| const clipboard = new ClipboardJS('#copying'); | |||
| clipboard.on('success', () => { | |||
| message.success('复制成功'); | |||
| }); | |||
| clipboard.on('error', () => { | |||
| message.error('复制失败'); | |||
| }); | |||