| @@ -0,0 +1,113 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-11-29 09:27:19 | |||
| * @Description: 用于 BasicInfo 和 BasicTableInfo 组件的子组件 | |||
| */ | |||
| import { Link } from '@umijs/max'; | |||
| import { Typography } from 'antd'; | |||
| import React from 'react'; | |||
| import { type BasicInfoData, type BasicInfoLink } from './types'; | |||
| type BasicInfoItemProps = { | |||
| data: BasicInfoData; | |||
| labelWidth: number; | |||
| classPrefix: string; | |||
| }; | |||
| export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) { | |||
| const { label, value, format, ellipsis } = data; | |||
| const formatValue = format ? format(value) : value; | |||
| const myClassName = `${classPrefix}__item`; | |||
| let valueComponent = undefined; | |||
| if (Array.isArray(formatValue)) { | |||
| valueComponent = ( | |||
| <div className={`${myClassName}__value-container`}> | |||
| {formatValue.map((item: BasicInfoLink) => ( | |||
| <BasicInfoItemValue | |||
| key={item.value} | |||
| value={item.value} | |||
| link={item.link} | |||
| url={item.url} | |||
| ellipsis={ellipsis} | |||
| classPrefix={classPrefix} | |||
| /> | |||
| ))} | |||
| </div> | |||
| ); | |||
| } else if (React.isValidElement(formatValue)) { | |||
| // 这个判断必须在下面的判断之前 | |||
| valueComponent = ( | |||
| <BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} /> | |||
| ); | |||
| } else if (typeof formatValue === 'object' && formatValue) { | |||
| valueComponent = ( | |||
| <BasicInfoItemValue | |||
| value={formatValue.value} | |||
| link={formatValue.link} | |||
| url={formatValue.url} | |||
| ellipsis={ellipsis} | |||
| classPrefix={classPrefix} | |||
| /> | |||
| ); | |||
| } else { | |||
| valueComponent = ( | |||
| <BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} /> | |||
| ); | |||
| } | |||
| return ( | |||
| <div className={myClassName} key={label}> | |||
| <div className={`${myClassName}__label`} style={{ width: labelWidth }}> | |||
| {label} | |||
| </div> | |||
| {valueComponent} | |||
| </div> | |||
| ); | |||
| } | |||
| type BasicInfoItemValueProps = { | |||
| ellipsis?: boolean; | |||
| classPrefix: string; | |||
| value: string | React.ReactNode; | |||
| link?: string; | |||
| url?: string; | |||
| }; | |||
| export function BasicInfoItemValue({ | |||
| value, | |||
| link, | |||
| url, | |||
| ellipsis, | |||
| classPrefix, | |||
| }: BasicInfoItemValueProps) { | |||
| const myClassName = `${classPrefix}__item__value`; | |||
| let component = undefined; | |||
| if (url && value) { | |||
| component = ( | |||
| <a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer"> | |||
| {value} | |||
| </a> | |||
| ); | |||
| } else if (link && value) { | |||
| component = ( | |||
| <Link to={link} className={`${myClassName}__link`}> | |||
| {value} | |||
| </Link> | |||
| ); | |||
| } else if (React.isValidElement(value)) { | |||
| return value; | |||
| } else { | |||
| component = <span className={`${myClassName}__text`}>{value ?? '--'}</span>; | |||
| } | |||
| return ( | |||
| <div className={myClassName}> | |||
| <Typography.Text | |||
| ellipsis={ellipsis ? { tooltip: value } : false} | |||
| style={{ fontSize: 'inherit' }} | |||
| > | |||
| {component} | |||
| </Typography.Text> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,48 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-11-29 09:27:19 | |||
| * @Description: 用于 BasicInfo 和 BasicTableInfo 组件的常用转化格式 | |||
| */ | |||
| // 格式化日期 | |||
| export { formatDate } from '@/utils/date'; | |||
| /** | |||
| * 格式化字符串数组 | |||
| * @param value - 字符串数组 | |||
| * @returns 逗号分隔的字符串 | |||
| */ | |||
| export const formatList = (value: string[] | null | undefined): string => { | |||
| if ( | |||
| value === undefined || | |||
| value === null || | |||
| Array.isArray(value) === false || | |||
| value.length === 0 | |||
| ) { | |||
| return '--'; | |||
| } | |||
| return value.join(','); | |||
| }; | |||
| /** | |||
| * 格式化布尔值 | |||
| * @param value - 布尔值 | |||
| * @returns "是" 或 "否" | |||
| */ | |||
| export const formatBoolean = (value: boolean): string => { | |||
| return value ? '是' : '否'; | |||
| }; | |||
| type FormatEnum = (value: string | number) => string; | |||
| /** | |||
| * 格式化枚举 | |||
| * @param options - 枚举选项 | |||
| * @returns 格式化枚举函数 | |||
| */ | |||
| export const formatEnum = (options: { value: string | number; label: string }[]): FormatEnum => { | |||
| return (value: string | number) => { | |||
| const option = options.find((item) => item.value === value); | |||
| return option ? option.label : '--'; | |||
| }; | |||
| }; | |||
| @@ -1,21 +1,10 @@ | |||
| import { Link } from '@umijs/max'; | |||
| import { Typography } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import React from 'react'; | |||
| import { BasicInfoItem } from './components'; | |||
| import './index.less'; | |||
| export type BasicInfoLink = { | |||
| value: string; | |||
| link?: string; | |||
| url?: string; | |||
| }; | |||
| export type BasicInfoData = { | |||
| label: string; | |||
| value?: any; | |||
| ellipsis?: boolean; | |||
| format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; | |||
| }; | |||
| import type { BasicInfoData, BasicInfoLink } from './types'; | |||
| export * from './format'; | |||
| export type { BasicInfoData, BasicInfoLink }; | |||
| type BasicInfoProps = { | |||
| datas: BasicInfoData[]; | |||
| @@ -24,20 +13,6 @@ type BasicInfoProps = { | |||
| labelWidth: number; | |||
| }; | |||
| type BasicInfoItemProps = { | |||
| data: BasicInfoData; | |||
| labelWidth: number; | |||
| classPrefix: string; | |||
| }; | |||
| type BasicInfoItemValueProps = { | |||
| ellipsis?: boolean; | |||
| classPrefix: string; | |||
| value: string | React.ReactNode; | |||
| link?: string; | |||
| url?: string; | |||
| }; | |||
| export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) { | |||
| return ( | |||
| <div className={classNames('kf-basic-info', className)} style={style}> | |||
| @@ -52,92 +27,3 @@ export default function BasicInfo({ datas, className, style, labelWidth }: Basic | |||
| </div> | |||
| ); | |||
| } | |||
| export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) { | |||
| const { label, value, format, ellipsis } = data; | |||
| const formatValue = format ? format(value) : value; | |||
| const myClassName = `${classPrefix}__item`; | |||
| let valueComponent = undefined; | |||
| if (Array.isArray(formatValue)) { | |||
| valueComponent = ( | |||
| <div className={`${myClassName}__value-container`}> | |||
| {formatValue.map((item: BasicInfoLink) => ( | |||
| <BasicInfoItemValue | |||
| key={item.value} | |||
| value={item.value} | |||
| link={item.link} | |||
| url={item.url} | |||
| ellipsis={ellipsis} | |||
| classPrefix={classPrefix} | |||
| /> | |||
| ))} | |||
| </div> | |||
| ); | |||
| } else if (React.isValidElement(formatValue)) { | |||
| // 这个判断必须在下面的判断之前 | |||
| valueComponent = ( | |||
| <BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} /> | |||
| ); | |||
| } else if (typeof formatValue === 'object' && formatValue) { | |||
| valueComponent = ( | |||
| <BasicInfoItemValue | |||
| value={formatValue.value} | |||
| link={formatValue.link} | |||
| url={formatValue.url} | |||
| ellipsis={ellipsis} | |||
| classPrefix={classPrefix} | |||
| /> | |||
| ); | |||
| } else { | |||
| valueComponent = ( | |||
| <BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} /> | |||
| ); | |||
| } | |||
| return ( | |||
| <div className={myClassName} key={label}> | |||
| <div className={`${myClassName}__label`} style={{ width: labelWidth }}> | |||
| {label} | |||
| </div> | |||
| {valueComponent} | |||
| </div> | |||
| ); | |||
| } | |||
| export function BasicInfoItemValue({ | |||
| value, | |||
| link, | |||
| url, | |||
| ellipsis, | |||
| classPrefix, | |||
| }: BasicInfoItemValueProps) { | |||
| const myClassName = `${classPrefix}__item__value`; | |||
| let component = undefined; | |||
| if (url && value) { | |||
| component = ( | |||
| <a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer"> | |||
| {value} | |||
| </a> | |||
| ); | |||
| } else if (link && value) { | |||
| component = ( | |||
| <Link to={link} className={`${myClassName}__link`}> | |||
| {value} | |||
| </Link> | |||
| ); | |||
| } else if (React.isValidElement(value)) { | |||
| return value; | |||
| } else { | |||
| component = <span className={`${myClassName}__text`}>{value ?? '--'}</span>; | |||
| } | |||
| return ( | |||
| <div className={myClassName}> | |||
| <Typography.Text | |||
| ellipsis={ellipsis ? { tooltip: value } : false} | |||
| style={{ fontSize: 'inherit' }} | |||
| > | |||
| {component} | |||
| </Typography.Text> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| // 基础信息 | |||
| export type BasicInfoData = { | |||
| label: string; | |||
| value?: any; | |||
| ellipsis?: boolean; | |||
| format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; | |||
| }; | |||
| // 值为链接的类型 | |||
| export type BasicInfoLink = { | |||
| value: string; | |||
| link?: string; | |||
| url?: string; | |||
| }; | |||
| @@ -1,6 +1,8 @@ | |||
| import classNames from 'classnames'; | |||
| import { BasicInfoItem, type BasicInfoData, type BasicInfoLink } from '../BasicInfo'; | |||
| import { BasicInfoItem } from '../BasicInfo/components'; | |||
| import { type BasicInfoData, type BasicInfoLink } from '../BasicInfo/types'; | |||
| import './index.less'; | |||
| export * from '../BasicInfo/format'; | |||
| export type { BasicInfoData, BasicInfoLink }; | |||
| type BasicTableInfoProps = { | |||
| @@ -92,19 +92,29 @@ export enum AutoMLTaskType { | |||
| Regression = 'regression', | |||
| } | |||
| export const autoMLTaskTypeOptions = [ | |||
| { label: '分类', value: AutoMLTaskType.Classification }, | |||
| { label: '回归', value: AutoMLTaskType.Regression }, | |||
| ]; | |||
| // 自动化任务集成策略 | |||
| export enum AutoMLEnsembleClass { | |||
| Default = 'default', | |||
| SingleBest = 'SingleBest', | |||
| } | |||
| export const autoMLEnsembleClassOptions = [ | |||
| { label: '集成模型', value: AutoMLEnsembleClass.Default }, | |||
| { label: '单一最佳模型', value: AutoMLEnsembleClass.SingleBest }, | |||
| ]; | |||
| // 自动化任务重采样策略 | |||
| export enum AutoMLResamplingStrategy { | |||
| Holdout = 'holdout', | |||
| CrossValid = 'crossValid', | |||
| } | |||
| export const resamplingStrategyOptions = [ | |||
| export const autoMLResamplingStrategyOptions = [ | |||
| { label: 'holdout', value: AutoMLResamplingStrategy.Holdout }, | |||
| { label: 'crossValid', value: AutoMLResamplingStrategy.CrossValid }, | |||
| ]; | |||
| @@ -1,4 +1,4 @@ | |||
| .create-service-version { | |||
| .create-automl { | |||
| height: 100%; | |||
| &__content { | |||
| @@ -5,9 +5,9 @@ | |||
| */ | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { AutoMLTaskType } from '@/enums'; | |||
| import { addAutoMLReq, getDatasetInfoReq, updateAutoMLReq } from '@/services/autoML'; | |||
| import { parseJsonText, trimCharacter } from '@/utils'; | |||
| import { AutoMLEnsembleClass, AutoMLTaskType } from '@/enums'; | |||
| import { addAutoMLReq, getAutoMLInfoReq, updateAutoMLReq } from '@/services/autoML'; | |||
| import { convertEmptyStringToUndefined, parseJsonText, trimCharacter } from '@/utils'; | |||
| import { safeInvoke } from '@/utils/functional'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| @@ -49,7 +49,7 @@ function CreateAutoML() { | |||
| // 获取服务详情 | |||
| const getAutoMLInfo = async (id: number, isCopy = false) => { | |||
| const [res] = await to(getDatasetInfoReq({ id })); | |||
| const [res] = await to(getAutoMLInfoReq({ id })); | |||
| if (res && res.data) { | |||
| const autoMLInfo: AutoMLData = res.data; | |||
| const { | |||
| @@ -96,7 +96,7 @@ function CreateAutoML() { | |||
| } | |||
| }; | |||
| // 创建版本 | |||
| // 创建、更新、复制实验 | |||
| const createExperiment = async (formData: FormData) => { | |||
| const include_classifier = formData['include_classifier']?.join(','); | |||
| const include_feature_preprocessor = formData['include_feature_preprocessor']?.join(','); | |||
| @@ -113,12 +113,12 @@ function CreateAutoML() { | |||
| // 根据后台要求,修改表单数据 | |||
| const object = { | |||
| ...omit(formData), | |||
| include_classifier, | |||
| include_feature_preprocessor, | |||
| include_regressor, | |||
| exclude_classifier, | |||
| exclude_feature_preprocessor, | |||
| exclude_regressor, | |||
| include_classifier: convertEmptyStringToUndefined(include_classifier), | |||
| include_feature_preprocessor: convertEmptyStringToUndefined(include_feature_preprocessor), | |||
| include_regressor: convertEmptyStringToUndefined(include_regressor), | |||
| exclude_classifier: convertEmptyStringToUndefined(exclude_classifier), | |||
| exclude_feature_preprocessor: convertEmptyStringToUndefined(exclude_feature_preprocessor), | |||
| exclude_regressor: convertEmptyStringToUndefined(exclude_regressor), | |||
| metrics: metrics ? JSON.stringify(metrics) : undefined, | |||
| target_columns, | |||
| }; | |||
| @@ -148,7 +148,6 @@ function CreateAutoML() { | |||
| navigate(-1); | |||
| }; | |||
| const disabled = id !== null || id !== undefined; | |||
| let buttonText = '新建'; | |||
| let title = '新增实验'; | |||
| if (id) { | |||
| @@ -157,26 +156,28 @@ function CreateAutoML() { | |||
| } | |||
| return ( | |||
| <div className={styles['create-service-version']}> | |||
| <div className={styles['create-automl']}> | |||
| <PageTitle title={title}></PageTitle> | |||
| <div className={styles['create-service-version__content']}> | |||
| <div className={styles['create-automl__content']}> | |||
| <div> | |||
| <Form | |||
| name="create-service-version" | |||
| name="create-automl" | |||
| labelCol={{ flex: '160px' }} | |||
| labelAlign="left" | |||
| form={form} | |||
| onFinish={handleSubmit} | |||
| size="large" | |||
| autoComplete="off" | |||
| scrollToFirstError | |||
| initialValues={{ | |||
| task_type: AutoMLTaskType.Classification, | |||
| shuffle: false, | |||
| ensemble_class: AutoMLEnsembleClass.Default, | |||
| greater_is_better: true, | |||
| }} | |||
| > | |||
| <BasicConfig /> | |||
| <ExecuteConfig disabled={disabled} /> | |||
| <ExecuteConfig /> | |||
| <TrialConfig /> | |||
| <DatasetConfig /> | |||
| @@ -6,7 +6,7 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { deleteAutoMLReq, getAutoMLListReq } from '@/services/autoML'; | |||
| import { deleteAutoMLReq, getAutoMLListReq, runAutoMLReq } from '@/services/autoML'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| @@ -129,6 +129,24 @@ function AutoMLList() { | |||
| navigate(`/pipeline/autoML/info/${record.id}`); | |||
| }; | |||
| // 启动 | |||
| const startAutoML = async (record: AutoMLData) => { | |||
| const [res] = await to(runAutoMLReq(record.id)); | |||
| if (res) { | |||
| message.success('操作成功'); | |||
| getServiceList(); | |||
| } | |||
| }; | |||
| // 停止 | |||
| const stopAutoML = async (record: AutoMLData) => { | |||
| const [res] = await to(runAutoMLReq(record.id)); | |||
| if (res) { | |||
| message.success('操作成功'); | |||
| getServiceList(); | |||
| } | |||
| }; | |||
| // 分页切换 | |||
| const handleTableChange: TableProps<AutoMLData>['onChange'] = ( | |||
| pagination, | |||
| @@ -202,13 +220,10 @@ function AutoMLList() { | |||
| { | |||
| title: '操作', | |||
| dataIndex: 'operation', | |||
| width: 400, | |||
| width: 320, | |||
| key: 'operation', | |||
| render: (_: any, record: AutoMLData) => ( | |||
| <div> | |||
| <Button type="link" size="small" key="mirror" icon={<KFIcon type="icon-jingxiang" />}> | |||
| 镜像 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| @@ -227,9 +242,27 @@ function AutoMLList() { | |||
| > | |||
| 复制 | |||
| </Button> | |||
| <Button type="link" size="small" key="stop" icon={<KFIcon type="icon-tingzhi" />}> | |||
| 停止 | |||
| </Button> | |||
| {record.run_state === 'Running' ? ( | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="stop" | |||
| icon={<KFIcon type="icon-tingzhi" />} | |||
| onClick={() => startAutoML(record)} | |||
| > | |||
| 停止 | |||
| </Button> | |||
| ) : ( | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="start" | |||
| icon={<KFIcon type="icon-yunhang" />} | |||
| onClick={() => stopAutoML(record)} | |||
| > | |||
| 运行 | |||
| </Button> | |||
| )} | |||
| <ConfigProvider | |||
| theme={{ | |||
| token: { | |||
| @@ -4,4 +4,10 @@ | |||
| overflow-y: auto; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| :global { | |||
| .kf-basic-info__item__value__text { | |||
| white-space: pre; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,78 +1,303 @@ | |||
| import { AutoMLData } from '@/pages/AutoML/types'; | |||
| import { getAutoMLInfoReq } from '@/services/autoML'; | |||
| import { safeInvoke } from '@/utils/functional'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useParams } from '@umijs/max'; | |||
| import { Flex } from 'antd'; | |||
| import { useEffect } from 'react'; | |||
| import ConfigInfo, { type BasicInfoData } from '../ConfigInfo'; | |||
| import CopyingText from '../CopyingText'; | |||
| import StatusChart from '../StatusChart'; | |||
| import { useEffect, useState } from 'react'; | |||
| import ConfigInfo, { | |||
| formatBoolean, | |||
| formatDate, | |||
| formatEnum, | |||
| type BasicInfoData, | |||
| } from '../ConfigInfo'; | |||
| // import CopyingText from '../CopyingText'; | |||
| import { AutoMLTaskType, autoMLEnsembleClassOptions, autoMLTaskTypeOptions } from '@/enums'; | |||
| import { parseJsonText } from '@/utils'; | |||
| import styles from './index.less'; | |||
| // 格式化数据集 | |||
| const formatDataset = (dataset: { name: string; version: string }) => { | |||
| if (!dataset || !dataset.name || !dataset.version) { | |||
| return '--'; | |||
| } | |||
| return `${dataset.name}:${dataset.version}`; | |||
| }; | |||
| // 格式化优化方向 | |||
| const formatOptimizeMode = (value: boolean) => { | |||
| return value ? '越大越好' : '越小越好'; | |||
| }; | |||
| const formatMetricsWeight = (value: string) => { | |||
| if (!value) { | |||
| return '--'; | |||
| } | |||
| const json = parseJsonText(value); | |||
| if (!json) { | |||
| return '--'; | |||
| } | |||
| return Object.entries(json) | |||
| .map(([key, value]) => `${key}:${value}`) | |||
| .join('\n'); | |||
| }; | |||
| function AutoMLBasic() { | |||
| useEffect(() => {}, []); | |||
| const params = useParams(); | |||
| const id = safeInvoke(Number)(params.id); | |||
| const [basicDatas, setBasicDatas] = useState<BasicInfoData[]>([]); | |||
| const [configDatas, setConfigDatas] = useState<BasicInfoData[]>([]); | |||
| useEffect(() => { | |||
| if (id) { | |||
| getAutoMLInfo(id); | |||
| } | |||
| }, []); | |||
| 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, | |||
| }, | |||
| ]; | |||
| // const basicDatas: 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, | |||
| // }, | |||
| // ]; | |||
| // 获取服务详情 | |||
| const getAutoMLInfo = async (id: number) => { | |||
| const [res] = await to(getAutoMLInfoReq({ id })); | |||
| if (res && res.data) { | |||
| const info: AutoMLData = res.data; | |||
| const basicDatas: BasicInfoData[] = [ | |||
| { | |||
| label: '实验名称', | |||
| value: info.ml_name, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '实验描述', | |||
| value: info.ml_description, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '创建人', | |||
| value: info.create_by, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '创建时间', | |||
| value: info.create_time, | |||
| ellipsis: true, | |||
| format: formatDate, | |||
| }, | |||
| { | |||
| label: '更新时间', | |||
| value: info.update_time, | |||
| ellipsis: true, | |||
| format: formatDate, | |||
| }, | |||
| { | |||
| label: '状态', | |||
| value: info.run_state, | |||
| ellipsis: true, | |||
| }, | |||
| ]; | |||
| setBasicDatas(basicDatas); | |||
| const configDatas: BasicInfoData[] = [ | |||
| { | |||
| label: '任务类型', | |||
| value: info.task_type, | |||
| ellipsis: true, | |||
| format: formatEnum(autoMLTaskTypeOptions), | |||
| }, | |||
| { | |||
| label: '特征预处理算法', | |||
| value: info.include_feature_preprocessor, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '排除的特征预处理算法', | |||
| value: info.exclude_feature_preprocessor, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', | |||
| value: | |||
| info.task_type === AutoMLTaskType.Regression | |||
| ? info.include_regressor | |||
| : info.include_classifier, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', | |||
| value: | |||
| info.task_type === AutoMLTaskType.Regression | |||
| ? info.exclude_regressor | |||
| : info.exclude_classifier, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '集成方式', | |||
| value: info.ensemble_class, | |||
| ellipsis: true, | |||
| format: formatEnum(autoMLEnsembleClassOptions), | |||
| }, | |||
| { | |||
| label: '集成模型数量', | |||
| value: info.ensemble_size, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '集成最佳模型数量', | |||
| value: info.ensemble_nbest, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '最大数量', | |||
| value: info.max_models_on_disc, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '内存限制(MB)', | |||
| value: info.memory_limit, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '时间限制(秒)', | |||
| value: info.per_run_time_limit, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '搜索时间限制(秒)', | |||
| value: info.time_left_for_this_task, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '重采样策略', | |||
| value: info.resampling_strategy, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '交叉验证折数', | |||
| value: info.folds, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '是否打乱', | |||
| value: info.shuffle, | |||
| ellipsis: true, | |||
| format: formatBoolean, | |||
| }, | |||
| { | |||
| label: '训练集比率', | |||
| value: info.train_size, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '测试集比率', | |||
| value: info.test_size, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '计算指标', | |||
| value: info.scoring_functions, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '随机种子', | |||
| value: info.seed, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '数据集', | |||
| value: info.dataset, | |||
| ellipsis: true, | |||
| format: formatDataset, | |||
| }, | |||
| { | |||
| label: '预测目标列', | |||
| value: info.target_columns, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '指标名称', | |||
| value: info.metric_name, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '优化方向', | |||
| value: info.greater_is_better, | |||
| ellipsis: true, | |||
| format: formatOptimizeMode, | |||
| }, | |||
| { | |||
| label: '指标权重', | |||
| value: info.metrics, | |||
| ellipsis: true, | |||
| format: formatMetricsWeight, | |||
| }, | |||
| ]; | |||
| setConfigDatas(configDatas); | |||
| } | |||
| }; | |||
| return ( | |||
| <div className={styles['auto-ml-basic']}> | |||
| <Flex gap={15} align="stretch"> | |||
| <ConfigInfo title="基本信息" data={datas} labelWidth={70} /> | |||
| <StatusChart | |||
| <ConfigInfo title="基本信息" data={basicDatas} labelWidth={70} threeColumn /> | |||
| {/* <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} | |||
| title="配置信息" | |||
| data={configDatas.slice(0, -3)} | |||
| labelWidth={150} | |||
| threeColumn | |||
| style={{ marginTop: '20px' }} | |||
| /> | |||
| <ConfigInfo | |||
| title="执行配置" | |||
| data={datas} | |||
| style={{ marginTop: '16px' }} | |||
| title="优化指标" | |||
| data={configDatas.slice(-3)} | |||
| labelWidth={70} | |||
| threeColumn | |||
| style={{ marginTop: '20px' }} | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -3,7 +3,8 @@ import classNames from 'classnames'; | |||
| import { useEffect } from 'react'; | |||
| import ConfigTitle from '../ConfigTitle'; | |||
| import styles from './index.less'; | |||
| export { type BasicInfoData }; | |||
| export * from '@/components/BasicInfo/format'; | |||
| export type { BasicInfoData }; | |||
| type ConfigInfoProps = { | |||
| title: string; | |||
| @@ -10,7 +10,7 @@ function DatasetConfig() { | |||
| <> | |||
| <SubAreaTitle | |||
| title="数据集配置" | |||
| image={require('@/assets/img/search-config-icon.png')} | |||
| image={require('@/assets/img/dataset-config-icon.png')} | |||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| @@ -3,9 +3,11 @@ import { | |||
| AutoMLEnsembleClass, | |||
| AutoMLResamplingStrategy, | |||
| AutoMLTaskType, | |||
| resamplingStrategyOptions, | |||
| autoMLEnsembleClassOptions, | |||
| autoMLResamplingStrategyOptions, | |||
| autoMLTaskTypeOptions, | |||
| } from '@/enums'; | |||
| import { Col, Form, Input, InputNumber, Radio, Row, Select, Switch } from 'antd'; | |||
| import { Col, Form, InputNumber, Radio, Row, Select, Switch } from 'antd'; | |||
| // 分类算法 | |||
| const classificationAlgorithms = [ | |||
| @@ -120,10 +122,10 @@ function ExecuteConfig() { | |||
| name="task_type" | |||
| rules={[{ required: true, message: '请选择任务类型' }]} | |||
| > | |||
| <Radio.Group onChange={() => form.resetFields(['metrics'])}> | |||
| <Radio value={AutoMLTaskType.Classification}>分类</Radio> | |||
| <Radio value={AutoMLTaskType.Regression}>回归</Radio> | |||
| </Radio.Group> | |||
| <Radio.Group | |||
| options={autoMLTaskTypeOptions} | |||
| onChange={() => form.resetFields(['metrics'])} | |||
| ></Radio.Group> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| @@ -259,10 +261,7 @@ function ExecuteConfig() { | |||
| name="ensemble_class" | |||
| tooltip="仅使用单个最佳模型还是集成模型" | |||
| > | |||
| <Radio.Group> | |||
| <Radio value={AutoMLEnsembleClass.Default}>集成模型</Radio> | |||
| <Radio value={AutoMLEnsembleClass.SingleBest}>单一最佳模型</Radio> | |||
| </Radio.Group> | |||
| <Radio.Group options={autoMLEnsembleClassOptions}></Radio.Group> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| @@ -357,7 +356,7 @@ function ExecuteConfig() { | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择重采样策略" | |||
| options={resamplingStrategyOptions} | |||
| options={autoMLResamplingStrategyOptions} | |||
| showSearch | |||
| /> | |||
| </Form.Item> | |||
| @@ -389,7 +388,7 @@ function ExecuteConfig() { | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="shuffle" name="shuffle" tooltip="拆分数据前是否进行 shuffle"> | |||
| <Form.Item label="是否打乱" name="shuffle" tooltip="拆分数据前是否打乱顺序"> | |||
| <Switch /> | |||
| </Form.Item> | |||
| </Col> | |||
| @@ -444,7 +443,7 @@ function ExecuteConfig() { | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| {/* <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="文件夹路径" | |||
| @@ -461,7 +460,7 @@ function ExecuteConfig() { | |||
| <Input placeholder="请输入文件夹路径" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </Row> */} | |||
| {/* <Form.List name="hyper-parameter"> | |||
| {(fields, { add, remove }) => ( | |||
| @@ -21,7 +21,7 @@ function TrialConfig() { | |||
| <SubAreaTitle | |||
| title="优化指标" | |||
| image={require('@/assets/img/trial-config-icon.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| @@ -37,7 +37,7 @@ function TrialConfig() { | |||
| {(fields, { add, remove }) => ( | |||
| <> | |||
| {fields.map(({ key, name, ...restField }, index) => ( | |||
| <Flex key={key} align="flex-start" className={styles['advanced-config']}> | |||
| <Flex key={key} align="flex-start" className={styles['metrics-weight']}> | |||
| <Form.Item | |||
| style={{ flex: 1, marginBottom: 0, minWidth: 0 }} | |||
| {...restField} | |||
| @@ -88,10 +88,11 @@ function TrialConfig() { | |||
| </Flex> | |||
| ))} | |||
| {fields.length === 0 && ( | |||
| <Form.Item style={{ marginBottom: 0 }}> | |||
| <Form.Item className={styles['add-weight']}> | |||
| <Button | |||
| style={{ background: 'white' }} | |||
| type="dashed" | |||
| className={styles['add-weight__button']} | |||
| color="primary" | |||
| variant="dashed" | |||
| onClick={() => add()} | |||
| block | |||
| icon={<PlusCircleOutlined />} | |||
| @@ -1,4 +1,4 @@ | |||
| .advanced-config { | |||
| .metrics-weight { | |||
| margin-bottom: 20px; | |||
| &:last-child { | |||
| @@ -6,6 +6,19 @@ | |||
| } | |||
| } | |||
| .add-weight { | |||
| margin-bottom: 0; | |||
| // 增加样式权重 | |||
| & &__button { | |||
| border-color: .addAlpha(@primary-color, 0.5) []; | |||
| box-shadow: none !important; | |||
| &:hover { | |||
| border-style: solid; | |||
| } | |||
| } | |||
| } | |||
| // .command { | |||
| // width: 83.33%; | |||
| // margin-bottom: 20px; | |||
| @@ -10,32 +10,32 @@ export enum OperationType { | |||
| export type FormData = { | |||
| ml_name: string; // 实验名称 | |||
| ml_description: string; // 实验描述 | |||
| ensemble_class?: string; // 集成构建 | |||
| ensemble_nbest?: string; | |||
| ensemble_size?: number; | |||
| include_classifier?: string[]; | |||
| include_feature_preprocessor?: string[]; | |||
| include_regressor?: string[]; | |||
| ensemble_class?: string; // 集成方式 | |||
| ensemble_nbest?: string; // 集成最佳模型数量 | |||
| ensemble_size?: number; // 集成模型数量 | |||
| include_classifier?: string[]; // 分类算法 | |||
| include_feature_preprocessor?: string[]; // 特征预处理算法 | |||
| include_regressor?: string[]; // 回归算法 | |||
| exclude_classifier?: string[]; | |||
| exclude_feature_preprocessor?: string[]; | |||
| exclude_regressor?: string[]; | |||
| max_models_on_disc?: number; | |||
| memory_limit?: number; | |||
| metric_name?: string; | |||
| greater_is_better: boolean; | |||
| per_run_time_limit?: number; | |||
| resampling_strategy?: string; | |||
| scoring_functions?: string; | |||
| shuffle?: boolean; | |||
| seed?: number; | |||
| target_columns: string; | |||
| task_type: string; | |||
| test_size?: number; | |||
| train_size?: number; | |||
| time_left_for_this_task: number; | |||
| tmp_folder?: string; | |||
| metrics?: { name: string; value: number }[]; | |||
| dataset: ParameterInputObject; // 模型 | |||
| max_models_on_disc?: number; // 最大数量 | |||
| memory_limit?: number; // 内存限制(MB) | |||
| per_run_time_limit?: number; // 时间限制(秒) | |||
| resampling_strategy?: string; // 重采样策略 | |||
| folds?: number; // 交叉验证折数 | |||
| scoring_functions?: string; // 计算指标 | |||
| shuffle?: boolean; // 是否打乱 | |||
| seed?: number; // 随机种子 | |||
| task_type: string; // 任务类型 | |||
| test_size?: number; // 测试集比率 | |||
| train_size?: number; // 训练集比率 | |||
| time_left_for_this_task: number; // 搜索时间限制(秒) | |||
| metric_name?: string; // 指标名称 | |||
| greater_is_better: boolean; // 指标优化方向 | |||
| metrics?: { name: string; value: number }[]; // 指标权重 | |||
| dataset: ParameterInputObject; // 数据集 | |||
| target_columns: string; // 预测目标列 | |||
| }; | |||
| export type AutoMLData = { | |||
| @@ -51,6 +51,10 @@ export type AutoMLData = { | |||
| exclude_feature_preprocessor?: string; | |||
| exclude_regressor?: string; | |||
| dataset?: string; | |||
| create_by?: string; | |||
| create_time?: string; | |||
| update_by?: string; | |||
| update_time?: string; | |||
| } & Omit< | |||
| FormData, | |||
| 'metrics|dataset|include_classifier|include_feature_preprocessor|include_regressor|exclude_classifier|exclude_feature_preprocessor|exclude_regressor' | |||
| @@ -16,7 +16,7 @@ export function getAutoMLListReq(params) { | |||
| } | |||
| // 查询自动学习详情 | |||
| export function getDatasetInfoReq(params) { | |||
| export function getAutoMLInfoReq(params) { | |||
| return request(`/api/mmp/autoML/getAutoMlDetail`, { | |||
| method: 'GET', | |||
| params, | |||
| @@ -198,7 +198,7 @@ export const fittingString = (str: string, maxWidth: number, fontSize: number): | |||
| * @param {any} str - the string to be checked | |||
| * @return {boolean} true if the string is empty, undefined, or null, false otherwise | |||
| */ | |||
| export const isEmptyString = (str: any): boolean => { | |||
| export const isEmpty = (str: any): boolean => { | |||
| return str === '' || str === undefined || str === null; | |||
| }; | |||
| @@ -256,3 +256,13 @@ export const trimCharacter = (str: string, ch: string): string => { | |||
| const reg = new RegExp(`^${ch}|${ch}$`, 'g'); | |||
| return str.trim().replace(reg, ''); | |||
| }; | |||
| /** | |||
| * Converts an empty string to undefined. | |||
| * | |||
| * @param {string} [value] - The string to convert. | |||
| * @return {string | undefined} The converted string or undefined. | |||
| */ | |||
| export const convertEmptyStringToUndefined = (value?: string): string | undefined => { | |||
| return value === '' ? undefined : value; | |||
| }; | |||