| @@ -10,6 +10,7 @@ import { addAutoMLReq, getDatasetInfoReq, updateAutoMLReq } from '@/services/aut | |||
| import { parseJsonText, trimCharacter } from '@/utils'; | |||
| import { safeInvoke } from '@/utils/functional'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import { useNavigate, useParams } from '@umijs/max'; | |||
| import { App, Button, Form } from 'antd'; | |||
| import { omit } from 'lodash'; | |||
| @@ -29,13 +30,25 @@ function CreateAutoML() { | |||
| const id = safeInvoke(Number)(params.id); | |||
| useEffect(() => { | |||
| if (id) { | |||
| getAutoMLInfo(); | |||
| // 复制和新建 | |||
| const recordId = SessionStorage.getItem(SessionStorage.autoMLRecordIDKey); | |||
| if (recordId && !Number.isNaN(Number(recordId))) { | |||
| getAutoMLInfo(Number(recordId), true); | |||
| } | |||
| return () => { | |||
| SessionStorage.removeItem(SessionStorage.autoMLRecordIDKey); | |||
| }; | |||
| }, []); | |||
| useEffect(() => { | |||
| // 编辑 | |||
| if (id && !Number.isNaN(id)) { | |||
| getAutoMLInfo(id, false); | |||
| } | |||
| }, [id]); | |||
| // 获取服务详情 | |||
| const getAutoMLInfo = async () => { | |||
| const getAutoMLInfo = async (id: number, isCopy = false) => { | |||
| const [res] = await to(getDatasetInfoReq({ id })); | |||
| if (res && res.data) { | |||
| const autoMLInfo: AutoMLData = res.data; | |||
| @@ -47,6 +60,8 @@ function CreateAutoML() { | |||
| exclude_feature_preprocessor: exclude_feature_preprocessor_str, | |||
| exclude_regressor: exclude_regressor_str, | |||
| metrics: metrics_str, | |||
| ml_name: ml_name_str, | |||
| ...rest | |||
| } = autoMLInfo; | |||
| const include_classifier = include_classifier_str?.split(',').filter(Boolean); | |||
| const include_feature_preprocessor = include_feature_preprocessor_str | |||
| @@ -63,9 +78,10 @@ function CreateAutoML() { | |||
| name: key, | |||
| value, | |||
| })); | |||
| const ml_name = isCopy ? `${ml_name_str}-copy` : ml_name_str; | |||
| const formData = { | |||
| ...autoMLInfo, | |||
| ...rest, | |||
| include_classifier, | |||
| include_feature_preprocessor, | |||
| include_regressor, | |||
| @@ -73,6 +89,7 @@ function CreateAutoML() { | |||
| exclude_feature_preprocessor, | |||
| exclude_regressor, | |||
| metrics, | |||
| ml_name, | |||
| }; | |||
| form.setFieldsValue(formData); | |||
| @@ -135,7 +152,7 @@ function CreateAutoML() { | |||
| let buttonText = '新建'; | |||
| let title = '新增实验'; | |||
| if (id) { | |||
| title = '更新实验'; | |||
| title = '编辑实验'; | |||
| buttonText = '更新'; | |||
| } | |||
| @@ -9,6 +9,7 @@ import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { deleteAutoMLReq, getAutoMLListReq } from '@/services/autoML'; | |||
| 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'; | |||
| @@ -90,7 +91,7 @@ function AutoMLList() { | |||
| // 处理删除 | |||
| const handleAutoMLDelete = (record: AutoMLData) => { | |||
| modalConfirm({ | |||
| title: '删除后,该服务将不可恢复', | |||
| title: '删除后,该实验将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteService(record); | |||
| @@ -99,15 +100,21 @@ function AutoMLList() { | |||
| }; | |||
| // 创建、编辑 | |||
| const createService = (record?: AutoMLData) => { | |||
| const createService = (record?: AutoMLData, isCopy: boolean = false) => { | |||
| setCacheState({ | |||
| pagination, | |||
| searchText, | |||
| }); | |||
| if (record) { | |||
| navigate(`/pipeline/autoML/edit/${record.id}`); | |||
| if (isCopy) { | |||
| SessionStorage.setItem(SessionStorage.autoMLRecordIDKey, record.id, false); | |||
| navigate(`/pipeline/autoML/create`); | |||
| } else { | |||
| navigate(`/pipeline/autoML/edit/${record.id}`); | |||
| } | |||
| } else { | |||
| SessionStorage.setItem(SessionStorage.autoMLRecordIDKey, '', false); | |||
| navigate(`/pipeline/autoML/create`); | |||
| } | |||
| }; | |||
| @@ -139,7 +146,7 @@ function AutoMLList() { | |||
| title: '序号', | |||
| dataIndex: 'index', | |||
| key: 'index', | |||
| width: '20%', | |||
| width: 80, | |||
| render: tableCellRender(false, TableCellValueType.Index, { | |||
| page: pagination.current! - 1, | |||
| pageSize: pagination.pageSize!, | |||
| @@ -166,7 +173,7 @@ function AutoMLList() { | |||
| title: '状态', | |||
| dataIndex: 'run_state', | |||
| key: 'run_state', | |||
| width: '20%', | |||
| width: 100, | |||
| render: RunStatusCell, | |||
| }, | |||
| { | |||
| @@ -207,7 +214,7 @@ function AutoMLList() { | |||
| size="small" | |||
| key="edit" | |||
| icon={<KFIcon type="icon-bianji" />} | |||
| onClick={() => createService(record)} | |||
| onClick={() => createService(record, false)} | |||
| > | |||
| 编辑 | |||
| </Button> | |||
| @@ -216,17 +223,11 @@ function AutoMLList() { | |||
| size="small" | |||
| key="copy" | |||
| icon={<KFIcon type="icon-fuzhi" />} | |||
| onClick={() => toDetail(record)} | |||
| onClick={() => createService(record, true)} | |||
| > | |||
| 复制 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="stop" | |||
| icon={<KFIcon type="icon-tingzhi" />} | |||
| onClick={() => toDetail(record)} | |||
| > | |||
| <Button type="link" size="small" key="stop" icon={<KFIcon type="icon-tingzhi" />}> | |||
| 停止 | |||
| </Button> | |||
| <ConfigProvider | |||
| @@ -120,7 +120,7 @@ function ExecuteConfig() { | |||
| name="task_type" | |||
| rules={[{ required: true, message: '请选择任务类型' }]} | |||
| > | |||
| <Radio.Group> | |||
| <Radio.Group onChange={() => form.resetFields(['metrics'])}> | |||
| <Radio value={AutoMLTaskType.Classification}>分类</Radio> | |||
| <Radio value={AutoMLTaskType.Regression}>回归</Radio> | |||
| </Radio.Group> | |||
| @@ -8,6 +8,14 @@ import styles from './index.less'; | |||
| function TrialConfig() { | |||
| const form = Form.useFormInstance(); | |||
| const task_type = Form.useWatch('task_type', form); | |||
| const metrics = Form.useWatch('metrics', form) || []; | |||
| const selectedMetrics = metrics | |||
| .map((item: { name: string; value: number }) => item?.name) | |||
| .filter(Boolean); | |||
| const allMetricsOptions = | |||
| task_type === AutoMLTaskType.Classification ? classificationMetrics : regressionMetrics; | |||
| const metricsOptions = allMetricsOptions.filter((item) => !selectedMetrics.includes(item.label)); | |||
| return ( | |||
| <> | |||
| <SubAreaTitle | |||
| @@ -22,7 +30,6 @@ function TrialConfig() { | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="指标权重" tooltip="用户可自定义优化指标的组合"> | |||
| @@ -30,9 +37,9 @@ function TrialConfig() { | |||
| {(fields, { add, remove }) => ( | |||
| <> | |||
| {fields.map(({ key, name, ...restField }, index) => ( | |||
| <Flex key={key} align="center" className={styles['advanced-config']}> | |||
| <Flex key={key} align="flex-start" className={styles['advanced-config']}> | |||
| <Form.Item | |||
| style={{ flex: 1, marginBottom: 0 }} | |||
| style={{ flex: 1, marginBottom: 0, minWidth: 0 }} | |||
| {...restField} | |||
| name={[name, 'name']} | |||
| rules={[{ required: true, message: '请选择指标' }]} | |||
| @@ -41,31 +48,29 @@ function TrialConfig() { | |||
| allowClear | |||
| placeholder="请选择指标" | |||
| popupMatchSelectWidth={false} | |||
| options={ | |||
| task_type === AutoMLTaskType.Classification | |||
| ? classificationMetrics | |||
| : regressionMetrics | |||
| } | |||
| options={metricsOptions} | |||
| showSearch | |||
| /> | |||
| </Form.Item> | |||
| <span style={{ margin: '0 8px' }}>:</span> | |||
| <span style={{ margin: '0 8px', lineHeight: '46px' }}>:</span> | |||
| <Form.Item | |||
| style={{ flex: 1, marginBottom: 0 }} | |||
| style={{ flex: 1, marginBottom: 0, minWidth: 0 }} | |||
| {...restField} | |||
| name={[name, 'value']} | |||
| rules={[{ required: true, message: '请输入指标权重' }]} | |||
| > | |||
| <InputNumber placeholder="请输入指标权重" min={0} precision={0} /> | |||
| </Form.Item> | |||
| <div style={{ width: '76px', marginLeft: '18px' }}> | |||
| <Flex | |||
| style={{ width: '76px', marginLeft: '18px', height: '46px' }} | |||
| align="center" | |||
| > | |||
| <Button | |||
| style={{ | |||
| marginRight: '3px', | |||
| }} | |||
| shape="circle" | |||
| size="middle" | |||
| // disabled={fields.length === 1} | |||
| type="text" | |||
| onClick={() => remove(name)} | |||
| icon={<MinusCircleOutlined />} | |||
| @@ -79,7 +84,7 @@ function TrialConfig() { | |||
| icon={<PlusCircleOutlined />} | |||
| ></Button> | |||
| )} | |||
| </div> | |||
| </Flex> | |||
| </Flex> | |||
| ))} | |||
| {fields.length === 0 && ( | |||
| @@ -6,136 +6,136 @@ | |||
| } | |||
| } | |||
| .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; | |||
| } | |||
| .upload-tip { | |||
| margin-top: 5px; | |||
| color: @text-color-secondary; | |||
| font-size: 14px; | |||
| } | |||
| .upload-button { | |||
| height: 46px; | |||
| font-size: 15px; | |||
| } | |||
| // .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; | |||
| // } | |||
| // .upload-tip { | |||
| // margin-top: 5px; | |||
| // color: @text-color-secondary; | |||
| // font-size: 14px; | |||
| // } | |||
| // .upload-button { | |||
| // height: 46px; | |||
| // font-size: 15px; | |||
| // } | |||
| @@ -6,18 +6,19 @@ | |||
| import styles from './index.less'; | |||
| function ExecuteScheduleCell(status?: any) { | |||
| function ExecuteScheduleCell(progress?: number) { | |||
| const width = (progress || 0) * 100 + '%'; | |||
| 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%' }} | |||
| style={{ width: width }} | |||
| ></div> | |||
| </div> | |||
| <span className={styles['execute-schedule-cell__text']}> | |||
| {/* <span className={styles['execute-schedule-cell__text']}> | |||
| 1/<strong>2</strong> | |||
| </span> | |||
| </span> */} | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -51,4 +51,7 @@ export type AutoMLData = { | |||
| exclude_feature_preprocessor?: string; | |||
| exclude_regressor?: string; | |||
| dataset?: string; | |||
| }; | |||
| } & Omit< | |||
| FormData, | |||
| 'metrics|dataset|include_classifier|include_feature_preprocessor|include_regressor|exclude_classifier|exclude_feature_preprocessor|exclude_regressor' | |||
| >; | |||
| @@ -11,6 +11,8 @@ export default class SessionStorage { | |||
| static readonly editorUrlKey = 'editor-url'; | |||
| // 客户端信息 | |||
| static readonly clientInfoKey = 'client-info'; | |||
| // 自动机器学习记录ID | |||
| static readonly autoMLRecordIDKey = 'auto-ml-record-id'; | |||
| static getItem(key: string, isObject: boolean = false) { | |||
| const jsonStr = sessionStorage.getItem(key); | |||