| @@ -5,48 +5,54 @@ | |||||
| gap: 20px 40px; | gap: 20px 40px; | ||||
| align-items: flex-start; | align-items: flex-start; | ||||
| width: 80%; | width: 80%; | ||||
| } | |||||
| .kf-basic-info-item { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| width: calc(50% - 20px); | |||||
| font-size: 16px; | |||||
| line-height: 1.6; | |||||
| &__label { | |||||
| position: relative; | |||||
| flex: none; | |||||
| color: @text-color-secondary; | |||||
| text-align: justify; | |||||
| text-align-last: justify; | |||||
| &::after { | |||||
| position: absolute; | |||||
| content: ':'; | |||||
| &__item { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| width: calc(50% - 20px); | |||||
| &__label { | |||||
| position: relative; | |||||
| flex: none; | |||||
| color: @text-color-secondary; | |||||
| font-size: @font-size-content; | |||||
| line-height: 1.6; | |||||
| text-align: justify; | |||||
| text-align-last: justify; | |||||
| &::after { | |||||
| position: absolute; | |||||
| content: ':'; | |||||
| } | |||||
| } | } | ||||
| } | |||||
| &__list-value { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| gap: 5px 0; | |||||
| } | |||||
| &__value-container { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| gap: 5px 0; | |||||
| } | |||||
| &__value { | |||||
| flex: 1; | |||||
| margin-left: 16px; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| } | |||||
| &__value { | |||||
| flex: 1; | |||||
| margin-left: 16px; | |||||
| font-size: @font-size-content; | |||||
| line-height: 1.6; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| &__text { | |||||
| color: @text-color; | |||||
| } | |||||
| &--ellipsis { | |||||
| .singleLine(); | |||||
| } | |||||
| &__text { | |||||
| color: @text-color; | |||||
| } | |||||
| &__link:hover { | |||||
| text-decoration: underline @underline-color; | |||||
| text-underline-offset: 3px; | |||||
| &__link:hover { | |||||
| text-decoration: underline @underline-color; | |||||
| text-underline-offset: 3px; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| import { Link } from '@umijs/max'; | import { Link } from '@umijs/max'; | ||||
| import { Typography } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import './index.less'; | import './index.less'; | ||||
| @@ -11,6 +12,7 @@ export type BasicInfoLink = { | |||||
| export type BasicInfoData = { | export type BasicInfoData = { | ||||
| label: string; | label: string; | ||||
| value?: any; | value?: any; | ||||
| ellipsis?: boolean; | |||||
| format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; | format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; | ||||
| }; | }; | ||||
| @@ -18,45 +20,73 @@ type BasicInfoProps = { | |||||
| datas: BasicInfoData[]; | datas: BasicInfoData[]; | ||||
| className?: string; | className?: string; | ||||
| style?: React.CSSProperties; | style?: React.CSSProperties; | ||||
| labelWidth?: number; | |||||
| labelWidth: number; | |||||
| }; | }; | ||||
| function BasicInfo({ datas, className, style, labelWidth = 100 }: BasicInfoProps) { | |||||
| type BasicInfoItemProps = { | |||||
| data: BasicInfoData; | |||||
| labelWidth: number; | |||||
| classPrefix: string; | |||||
| }; | |||||
| type BasicInfoItemValueProps = BasicInfoLink & { | |||||
| ellipsis?: boolean; | |||||
| classPrefix: string; | |||||
| }; | |||||
| export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) { | |||||
| return ( | return ( | ||||
| <div className={classNames('kf-basic-info', className)} style={style}> | <div className={classNames('kf-basic-info', className)} style={style}> | ||||
| {datas.map((item) => ( | {datas.map((item) => ( | ||||
| <BasicInfoItem key={item.label} data={item} labelWidth={labelWidth} /> | |||||
| <BasicInfoItem | |||||
| key={item.label} | |||||
| data={item} | |||||
| labelWidth={labelWidth} | |||||
| classPrefix="kf-basic-info" | |||||
| /> | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| type BasicInfoItemProps = { | |||||
| data: BasicInfoData; | |||||
| labelWidth?: number; | |||||
| }; | |||||
| function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) { | |||||
| const { label, value, format } = data; | |||||
| export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) { | |||||
| const { label, value, format, ellipsis } = data; | |||||
| const formatValue = format ? format(value) : value; | const formatValue = format ? format(value) : value; | ||||
| const myClassName = `${classPrefix}__item`; | |||||
| let valueComponent = undefined; | let valueComponent = undefined; | ||||
| if (Array.isArray(formatValue)) { | if (Array.isArray(formatValue)) { | ||||
| valueComponent = ( | valueComponent = ( | ||||
| <div className="kf-basic-info-item__list-value"> | |||||
| <div className={`${myClassName}__value-container`}> | |||||
| {formatValue.map((item: BasicInfoLink) => ( | {formatValue.map((item: BasicInfoLink) => ( | ||||
| <BasicInfoItemValue key={item.value} value={item.value} link={item.link} url={item.url} /> | |||||
| <BasicInfoItemValue | |||||
| key={item.value} | |||||
| value={item.value} | |||||
| link={item.link} | |||||
| url={item.url} | |||||
| ellipsis={ellipsis} | |||||
| classPrefix={classPrefix} | |||||
| /> | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| } else if (typeof formatValue === 'object' && formatValue) { | } else if (typeof formatValue === 'object' && formatValue) { | ||||
| valueComponent = ( | valueComponent = ( | ||||
| <BasicInfoItemValue value={formatValue.value} link={formatValue.link} url={formatValue.url} /> | |||||
| <BasicInfoItemValue | |||||
| value={formatValue.value} | |||||
| link={formatValue.link} | |||||
| url={formatValue.url} | |||||
| ellipsis={ellipsis} | |||||
| classPrefix={classPrefix} | |||||
| /> | |||||
| ); | ); | ||||
| } else { | } else { | ||||
| valueComponent = <BasicInfoItemValue value={formatValue} />; | |||||
| valueComponent = ( | |||||
| <BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} /> | |||||
| ); | |||||
| } | } | ||||
| return ( | return ( | ||||
| <div className="kf-basic-info-item" key={label}> | |||||
| <div className="kf-basic-info-item__label" style={{ width: labelWidth }}> | |||||
| <div className={myClassName} key={label}> | |||||
| <div className={`${myClassName}__label`} style={{ width: labelWidth }}> | |||||
| {label} | {label} | ||||
| </div> | </div> | ||||
| {valueComponent} | {valueComponent} | ||||
| @@ -64,35 +94,39 @@ function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) { | |||||
| ); | ); | ||||
| } | } | ||||
| type BasicInfoItemValueProps = { | |||||
| value: string; | |||||
| link?: string; | |||||
| url?: string; | |||||
| }; | |||||
| function BasicInfoItemValue({ value, link, url }: BasicInfoItemValueProps) { | |||||
| export function BasicInfoItemValue({ | |||||
| value, | |||||
| link, | |||||
| url, | |||||
| ellipsis, | |||||
| classPrefix, | |||||
| }: BasicInfoItemValueProps) { | |||||
| const myClassName = `${classPrefix}__item__value`; | |||||
| let component = undefined; | |||||
| if (url && value) { | if (url && value) { | ||||
| return ( | |||||
| <a | |||||
| className="kf-basic-info-item__value kf-basic-info-item__link" | |||||
| href={url} | |||||
| target="_blank" | |||||
| rel="noopener noreferrer" | |||||
| > | |||||
| component = ( | |||||
| <a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer"> | |||||
| {value} | {value} | ||||
| </a> | </a> | ||||
| ); | ); | ||||
| } else if (link && value) { | } else if (link && value) { | ||||
| return ( | |||||
| <Link to={link} className="kf-basic-info-item__value kf-basic-info-item__link"> | |||||
| component = ( | |||||
| <Link to={link} className={`${myClassName}__link`}> | |||||
| {value} | {value} | ||||
| </Link> | </Link> | ||||
| ); | ); | ||||
| } else { | } else { | ||||
| return ( | |||||
| <div className="kf-basic-info-item__value kf-basic-info-item__text">{value ?? '--'}</div> | |||||
| ); | |||||
| component = <span className={`${myClassName}__text`}>{value ?? '--'}</span>; | |||||
| } | } | ||||
| } | |||||
| export default BasicInfo; | |||||
| return ( | |||||
| <Typography.Text | |||||
| className={classNames(myClassName, { | |||||
| [`${myClassName}--ellipsis`]: ellipsis, | |||||
| })} | |||||
| ellipsis={{ tooltip: value }} | |||||
| > | |||||
| {component} | |||||
| </Typography.Text> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| .kf-basic-table-info { | |||||
| display: flex; | |||||
| flex-direction: row; | |||||
| flex-wrap: wrap; | |||||
| align-items: stretch; | |||||
| width: 100%; | |||||
| border: 1px solid @border-color-base; | |||||
| border-bottom: none; | |||||
| border-radius: 4px; | |||||
| &__item { | |||||
| display: flex; | |||||
| align-items: stretch; | |||||
| width: 25%; | |||||
| border-bottom: 1px solid @border-color-base; | |||||
| &__label { | |||||
| flex: none; | |||||
| padding: 12px 20px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| text-align: left; | |||||
| background-color: .addAlpha(#606b7a, 0.05) []; | |||||
| } | |||||
| &__value-container { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| align-items: flex-start; | |||||
| min-width: 0; | |||||
| } | |||||
| &__value { | |||||
| flex: 1; | |||||
| margin: 0 !important; | |||||
| padding: 12px 20px 4px; | |||||
| font-size: @font-size; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| & + & { | |||||
| padding-top: 0; | |||||
| } | |||||
| &:last-child { | |||||
| padding-bottom: 12px; | |||||
| } | |||||
| &--ellipsis { | |||||
| .singleLine(); | |||||
| } | |||||
| &__text { | |||||
| color: @text-color; | |||||
| } | |||||
| &__link:hover { | |||||
| text-decoration: underline @underline-color; | |||||
| text-underline-offset: 3px; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| import classNames from 'classnames'; | |||||
| import { BasicInfoItem, type BasicInfoData, type BasicInfoLink } from '../BasicInfo'; | |||||
| import './index.less'; | |||||
| export type { BasicInfoData, BasicInfoLink }; | |||||
| type BasicTableInfoProps = { | |||||
| datas: BasicInfoData[]; | |||||
| className?: string; | |||||
| style?: React.CSSProperties; | |||||
| labelWidth: number; | |||||
| }; | |||||
| export default function BasicTableInfo({ | |||||
| datas, | |||||
| className, | |||||
| style, | |||||
| labelWidth, | |||||
| }: BasicTableInfoProps) { | |||||
| const remainder = datas.length % 4; | |||||
| const array = []; | |||||
| if (remainder > 0) { | |||||
| for (let i = 0; i < 4 - remainder; i++) { | |||||
| array.push({ | |||||
| label: '', | |||||
| value: '', | |||||
| }); | |||||
| } | |||||
| } | |||||
| const showDatas = [...datas, ...array]; | |||||
| return ( | |||||
| <div className={classNames('kf-basic-table-info', className)} style={style}> | |||||
| {showDatas.map((item) => ( | |||||
| <BasicInfoItem | |||||
| key={item.label} | |||||
| data={item} | |||||
| labelWidth={labelWidth} | |||||
| classPrefix="kf-basic-table-info" | |||||
| /> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| @@ -38,10 +38,6 @@ | |||||
| &__bottom { | &__bottom { | ||||
| position: relative; | position: relative; | ||||
| height: calc(100% - 135px); | height: calc(100% - 135px); | ||||
| padding: 8px 30px 20px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| &__legend { | &__legend { | ||||
| position: absolute; | position: absolute; | ||||
| @@ -52,6 +48,12 @@ | |||||
| :global { | :global { | ||||
| .ant-tabs { | .ant-tabs { | ||||
| height: 100%; | height: 100%; | ||||
| .ant-tabs-nav-wrap { | |||||
| padding-top: 8px; | |||||
| padding-left: 30px; | |||||
| background-color: white; | |||||
| border-radius: 10px 10px 0 0; | |||||
| } | |||||
| .ant-tabs-content-holder { | .ant-tabs-content-holder { | ||||
| height: 100%; | height: 100%; | ||||
| .ant-tabs-content { | .ant-tabs-content { | ||||
| @@ -164,7 +164,16 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| key: ResourceInfoTabKeys.Introduction, | key: ResourceInfoTabKeys.Introduction, | ||||
| label: `${typeName}简介`, | label: `${typeName}简介`, | ||||
| icon: <KFIcon type="icon-moxingjianjie" />, | icon: <KFIcon type="icon-moxingjianjie" />, | ||||
| children: <ResourceIntro resourceType={resourceType} info={info}></ResourceIntro>, | |||||
| children: ( | |||||
| <ResourceIntro | |||||
| resourceType={resourceType} | |||||
| info={info} | |||||
| resourceId={resourceId} | |||||
| identifier={identifier} | |||||
| owner={owner} | |||||
| version={version} | |||||
| ></ResourceIntro> | |||||
| ), | |||||
| }, | }, | ||||
| { | { | ||||
| key: ResourceInfoTabKeys.Version, | key: ResourceInfoTabKeys.Version, | ||||
| @@ -1,10 +1,25 @@ | |||||
| .resource-intro { | .resource-intro { | ||||
| width: 100%; | width: 100%; | ||||
| margin-top: 24px; | |||||
| &__basic { | |||||
| width: 100%; | |||||
| } | |||||
| &__usage { | |||||
| width: 100%; | |||||
| &__top { | |||||
| padding: 20px 30px; | |||||
| background: white; | |||||
| border-radius: 0 0 10px 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| pre { | |||||
| margin-bottom: 0 !important; | |||||
| } | |||||
| &__title { | |||||
| margin: 15px 0; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| } | |||||
| &__desc { | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| import BasicInfo, { BasicInfoData } from '@/components/BasicInfo'; | |||||
| import BasicTableInfo, { BasicInfoData } from '@/components/BasicTableInfo'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | ||||
| import { | import { | ||||
| @@ -8,13 +8,19 @@ import { | |||||
| ProjectDependency, | ProjectDependency, | ||||
| ResourceType, | ResourceType, | ||||
| TrainTask, | TrainTask, | ||||
| resourceConfig, | |||||
| } from '@/pages/Dataset/config'; | } from '@/pages/Dataset/config'; | ||||
| import ModelMetrics from '@/pages/Model/components/ModelMetrics'; | |||||
| import { getGitUrl } from '@/utils'; | import { getGitUrl } from '@/utils'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ResourceIntroProps = { | type ResourceIntroProps = { | ||||
| resourceType: ResourceType; | resourceType: ResourceType; | ||||
| info: DatasetData | ModelData; | info: DatasetData | ModelData; | ||||
| resourceId: number; | |||||
| identifier: string; | |||||
| owner: string; | |||||
| version?: string; | |||||
| }; | }; | ||||
| const formatDataset = (datasets?: DatasetData[]) => { | const formatDataset = (datasets?: DatasetData[]) => { | ||||
| @@ -27,29 +33,6 @@ const formatDataset = (datasets?: DatasetData[]) => { | |||||
| })); | })); | ||||
| }; | }; | ||||
| const formatParams = (map?: Record<string, string>, space: string = '') => { | |||||
| if (!map || Object.keys(map).length === 0) { | |||||
| return undefined; | |||||
| } | |||||
| return Object.entries(map) | |||||
| .map(([key, value]) => `${space}${key} : ${value}`) | |||||
| .join('\n'); | |||||
| }; | |||||
| const formatMetrics = (map?: Record<string, string>) => { | |||||
| if (!map || Object.keys(map).length === 0) { | |||||
| return undefined; | |||||
| } | |||||
| return Object.entries(map) | |||||
| .map(([key, value]) => { | |||||
| if (typeof value === 'object' && value !== null) { | |||||
| return `${key} : \n${formatParams(value, ' ')}`; | |||||
| } | |||||
| return `${key} : ${value}`; | |||||
| }) | |||||
| .join('\n'); | |||||
| }; | |||||
| const getProjectUrl = (project?: ProjectDependency) => { | const getProjectUrl = (project?: ProjectDependency) => { | ||||
| if (!project || !project.url || !project.branch) { | if (!project || !project.url || !project.branch) { | ||||
| return undefined; | return undefined; | ||||
| @@ -93,49 +76,50 @@ const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [ | |||||
| { | { | ||||
| label: '数据集名称', | label: '数据集名称', | ||||
| value: data.name, | value: data.name, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '版本', | label: '版本', | ||||
| value: data.version, | value: data.version, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '创建人', | label: '创建人', | ||||
| value: data.create_by, | value: data.create_by, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '更新时间', | label: '更新时间', | ||||
| value: data.update_time, | value: data.update_time, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '数据来源', | label: '数据来源', | ||||
| value: data.dataset_source, | value: data.dataset_source, | ||||
| format: formatSource, | format: formatSource, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '训练任务', | label: '训练任务', | ||||
| value: data.train_task, | value: data.train_task, | ||||
| format: formatTrainTask, | format: formatTrainTask, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '处理代码', | label: '处理代码', | ||||
| value: data.processing_code, | value: data.processing_code, | ||||
| format: formatProject, | format: formatProject, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '数据集分类', | label: '数据集分类', | ||||
| value: data.data_type, | value: data.data_type, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '研究方向', | label: '研究方向', | ||||
| value: data.data_tag, | value: data.data_tag, | ||||
| }, | |||||
| { | |||||
| label: '数据集描述', | |||||
| value: data.description, | |||||
| }, | |||||
| { | |||||
| label: '版本描述', | |||||
| value: data.version_desc, | |||||
| ellipsis: true, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -143,77 +127,79 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [ | |||||
| { | { | ||||
| label: '模型名称', | label: '模型名称', | ||||
| value: data.name, | value: data.name, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '版本', | label: '版本', | ||||
| value: data.version, | value: data.version, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '创建人', | label: '创建人', | ||||
| value: data.create_by, | value: data.create_by, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '更新时间', | label: '更新时间', | ||||
| value: data.update_time, | value: data.update_time, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '训练镜像', | label: '训练镜像', | ||||
| value: data.image, | value: data.image, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '训练代码', | label: '训练代码', | ||||
| value: data.project_depency, | value: data.project_depency, | ||||
| format: formatProject, | format: formatProject, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '训练数据集', | label: '训练数据集', | ||||
| value: data.train_datasets, | value: data.train_datasets, | ||||
| format: formatDataset, | format: formatDataset, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '测试数据集', | label: '测试数据集', | ||||
| value: data.test_datasets, | value: data.test_datasets, | ||||
| format: formatDataset, | format: formatDataset, | ||||
| }, | |||||
| { | |||||
| label: '参数', | |||||
| value: data.params, | |||||
| format: formatParams, | |||||
| }, | |||||
| { | |||||
| label: '指标', | |||||
| value: data.metrics, | |||||
| format: formatMetrics, | |||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '模型来源', | label: '模型来源', | ||||
| value: data.model_source, | value: data.model_source, | ||||
| format: formatSource, | format: formatSource, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '训练任务', | label: '训练任务', | ||||
| value: data.train_task, | value: data.train_task, | ||||
| format: formatTrainTask, | format: formatTrainTask, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '模型框架', | label: '模型框架', | ||||
| value: data.model_type, | value: data.model_type, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '模型能力', | label: '模型能力', | ||||
| value: data.model_tag, | value: data.model_tag, | ||||
| }, | |||||
| { | |||||
| label: '模型描述', | |||||
| value: data.description, | |||||
| }, | |||||
| { | |||||
| label: '版本描述', | |||||
| value: data.version_desc, | |||||
| ellipsis: true, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| function ResourceIntro({ resourceType, info }: ResourceIntroProps) { | |||||
| function ResourceIntro({ | |||||
| resourceType, | |||||
| info, | |||||
| resourceId, | |||||
| identifier, | |||||
| owner, | |||||
| version, | |||||
| }: ResourceIntroProps) { | |||||
| const config = resourceConfig[resourceType]; | |||||
| const basicDatas: BasicInfoData[] = | const basicDatas: BasicInfoData[] = | ||||
| resourceType === ResourceType.Dataset | resourceType === ResourceType.Dataset | ||||
| ? getDatasetDatas(info as DatasetData) | ? getDatasetDatas(info as DatasetData) | ||||
| @@ -221,23 +207,37 @@ function ResourceIntro({ resourceType, info }: ResourceIntroProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['resource-intro']}> | <div className={styles['resource-intro']}> | ||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['resource-intro__basic']}> | |||||
| <BasicInfo datas={basicDatas} labelWidth={86}></BasicInfo> | |||||
| <div className={styles['resource-intro__top']}> | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '15px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['resource-intro__top__basic']}> | |||||
| <BasicTableInfo datas={basicDatas} labelWidth={135}></BasicTableInfo> | |||||
| </div> | |||||
| <div className={styles['resource-intro__top__title']}>{`${config.name}描述`}</div> | |||||
| <div className={styles['resource-intro__top__desc']}>{info.description ?? '暂无描述'}</div> | |||||
| <div className={styles['resource-intro__top__title']}>版本描述</div> | |||||
| <div className={styles['resource-intro__top__desc']}>{info.version_desc ?? '暂无描述'}</div> | |||||
| <SubAreaTitle | |||||
| title="实例用法" | |||||
| image={require('@/assets/img/usage-icon.png')} | |||||
| style={{ margin: '25px 0 15px' }} | |||||
| ></SubAreaTitle> | |||||
| <div | |||||
| className={styles['resource-intro__top__usage']} | |||||
| dangerouslySetInnerHTML={{ __html: info.usage ?? '暂无实例用法' }} | |||||
| ></div> | |||||
| </div> | </div> | ||||
| <SubAreaTitle | |||||
| title="实例用法" | |||||
| image={require('@/assets/img/usage-icon.png')} | |||||
| style={{ margin: '40px 0 24px' }} | |||||
| ></SubAreaTitle> | |||||
| <div | |||||
| className={styles['resource-intro__usage']} | |||||
| dangerouslySetInnerHTML={{ __html: info.usage ?? '暂无实例用法' }} | |||||
| ></div> | |||||
| {resourceType === ResourceType.Model && version && ( | |||||
| <ModelMetrics | |||||
| resourceId={resourceId} | |||||
| identifier={identifier} | |||||
| owner={owner} | |||||
| version={version} | |||||
| ></ModelMetrics> | |||||
| )} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,4 +1,9 @@ | |||||
| .resource-version { | .resource-version { | ||||
| min-height: 100%; | |||||
| padding: 20px 30px; | |||||
| color: @text-color; | color: @text-color; | ||||
| font-size: @font-size-content; | font-size: @font-size-content; | ||||
| background: white; | |||||
| border-radius: 0 0 10px 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | } | ||||
| @@ -86,7 +86,7 @@ function ResourceVersion({ resourceType, info }: ResourceVersionProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['resource-version']}> | <div className={styles['resource-version']}> | ||||
| <Flex justify="space-between" align="center" style={{ margin: '30px 0' }}> | |||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '20px' }}> | |||||
| <Flex align="center"> | <Flex align="center"> | ||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| @@ -53,7 +53,7 @@ function ExperimentComparison() { | |||||
| // setLoading(true); | // setLoading(true); | ||||
| const request = | const request = | ||||
| comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq; | comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq; | ||||
| const [res] = await to(request(experimentId)); | |||||
| const [res] = await to(request(experimentId, { offset: '', limit: 50 })); | |||||
| // setLoading(false); | // setLoading(false); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| // const { content = [], totalElements = 0 } = res.data; | // const { content = [], totalElements = 0 } = res.data; | ||||
| @@ -202,9 +202,11 @@ function ExperimentComparison() { | |||||
| dataSource={tableData} | dataSource={tableData} | ||||
| columns={columns} | columns={columns} | ||||
| rowSelection={rowSelection} | rowSelection={rowSelection} | ||||
| scroll={{ y: 'calc(100% - 55px)', x: '100%' }} | |||||
| scroll={{ y: 'calc(100% - 110px)', x: '100%' }} | |||||
| pagination={false} | pagination={false} | ||||
| bordered={true} | bordered={true} | ||||
| virtual | |||||
| // onScroll={handleTableScroll} | |||||
| // loading={loading} | // loading={loading} | ||||
| // pagination={{ | // pagination={{ | ||||
| // ...pagination, | // ...pagination, | ||||
| @@ -52,7 +52,7 @@ function LogGroup({ | |||||
| const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); | const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); | ||||
| const preStatusRef = useRef<ExperimentStatus | undefined>(undefined); | const preStatusRef = useRef<ExperimentStatus | undefined>(undefined); | ||||
| const socketRef = useRef<WebSocket | undefined>(undefined); | const socketRef = useRef<WebSocket | undefined>(undefined); | ||||
| const retryRef = useRef(2); | |||||
| const retryRef = useRef(2); // 等待 2 秒,重试 2 次 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| scrollToBottom(false); | scrollToBottom(false); | ||||
| @@ -142,11 +142,12 @@ function LogGroup({ | |||||
| ); | ); | ||||
| socket.addEventListener('open', () => { | socket.addEventListener('open', () => { | ||||
| // console.log('WebSocket is open now.'); | |||||
| console.log('WebSocket is open now.'); | |||||
| }); | }); | ||||
| socket.addEventListener('close', (event) => { | socket.addEventListener('close', (event) => { | ||||
| // console.log('WebSocket is closed:', event); | |||||
| console.log('WebSocket is closed:', event); | |||||
| // 有时候会出现连接失败,重试 2 次 | |||||
| if (event.code !== 1000 && retryRef.current > 0) { | if (event.code !== 1000 && retryRef.current > 0) { | ||||
| retryRef.current -= 1; | retryRef.current -= 1; | ||||
| setTimeout(() => { | setTimeout(() => { | ||||
| @@ -160,6 +161,7 @@ function LogGroup({ | |||||
| }); | }); | ||||
| socket.addEventListener('message', (event) => { | socket.addEventListener('message', (event) => { | ||||
| console.log('message received.', event); | |||||
| if (!event.data) { | if (!event.data) { | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -32,8 +32,9 @@ function LogList({ | |||||
| }: LogListProps) { | }: LogListProps) { | ||||
| const [logList, setLogList] = useState<ExperimentLog[]>([]); | const [logList, setLogList] = useState<ExperimentLog[]>([]); | ||||
| const preStatusRef = useRef<ExperimentStatus | undefined>(undefined); | const preStatusRef = useRef<ExperimentStatus | undefined>(undefined); | ||||
| const retryRef = useRef(3); | |||||
| const retryRef = useRef(3); // 等待 2 秒,重试 3 次 | |||||
| // 当实例节点运行状态不是 Pending,而上一个运行状态不存在或者是 Pending 时,获取实验日志 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if ( | if ( | ||||
| instanceNodeStatus && | instanceNodeStatus && | ||||
| @@ -0,0 +1,29 @@ | |||||
| .metrics-chart { | |||||
| width: calc((100% - 30px) / 3); | |||||
| background-color: white; | |||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 36px; | |||||
| padding-left: 15px; | |||||
| color: @text-color; | |||||
| font-size: 14px; | |||||
| background-color: #ebf2ff; | |||||
| img { | |||||
| width: 13px; | |||||
| height: 13px; | |||||
| margin-right: 12px; | |||||
| } | |||||
| } | |||||
| &__chart { | |||||
| width: 100%; | |||||
| height: 280px; | |||||
| background: linear-gradient(180deg, #ffffff 0%, #fdfeff 100%); | |||||
| border: 1px solid white; | |||||
| border-radius: 0 0 10px 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,174 @@ | |||||
| import * as echarts from 'echarts'; | |||||
| import { useEffect, useRef } from 'react'; | |||||
| import styles from './index.less'; | |||||
| import './tooltip.css'; | |||||
| const colors = [ | |||||
| '#0D5EF8', | |||||
| '#6AC21D', | |||||
| '#F98E1B', | |||||
| '#ECB934', | |||||
| '#8A34EC', | |||||
| '#FF1493', | |||||
| '#FFFF00', | |||||
| '#DAA520', | |||||
| '#CD853F', | |||||
| '#FF6347', | |||||
| '#808080', | |||||
| '#00BFFF', | |||||
| '#008000', | |||||
| '#00FFFF', | |||||
| '#FFFACD', | |||||
| '#FFA500', | |||||
| '#FF4500', | |||||
| '#800080', | |||||
| '#FF1493', | |||||
| '#000080', | |||||
| ]; | |||||
| const backgroundColor = new echarts.graphic.LinearGradient( | |||||
| 0, | |||||
| 0, | |||||
| 0, | |||||
| 1, | |||||
| [ | |||||
| { offset: 0, color: '#ffffff' }, | |||||
| { offset: 1, color: '#fdfeff' }, | |||||
| ], | |||||
| false, | |||||
| ); | |||||
| function getTooltip(xTitle: string, xValue: number, yTitle: string, yValue: number) { | |||||
| const str = `<div class="metrics-tooltip"> | |||||
| <span class="y-text">Y:</span> | |||||
| <span class="x-text">X:</span> | |||||
| <div class="title">${yTitle}</div> | |||||
| <div class="value">${yValue}</div> | |||||
| <div class="title" style="margin-top: 10px">${xTitle}</div> | |||||
| <div class="value">${xValue}</div> | |||||
| <div>`; | |||||
| return str; | |||||
| } | |||||
| export type MetricsChatData = { | |||||
| name: string; | |||||
| values: number[]; | |||||
| version: string; | |||||
| iters: number[]; | |||||
| }; | |||||
| export type MetricsChartProps = { | |||||
| name: string; | |||||
| chartData: MetricsChatData[]; | |||||
| }; | |||||
| function MetricsChart({ name, chartData }: MetricsChartProps) { | |||||
| const chartRef = useRef<HTMLDivElement>(null); | |||||
| const xAxisData = chartData[0]?.iters; | |||||
| const seriesData = chartData.map((item) => { | |||||
| return { | |||||
| name: item.version, | |||||
| type: 'line' as const, | |||||
| smooth: true, | |||||
| data: item.values, | |||||
| }; | |||||
| }); | |||||
| const options: echarts.EChartsOption = { | |||||
| backgroundColor: backgroundColor, | |||||
| title: { | |||||
| show: false, | |||||
| }, | |||||
| tooltip: { | |||||
| trigger: 'item', | |||||
| padding: 10, | |||||
| formatter: (params: any) => { | |||||
| const { name: xTitle, data } = params; | |||||
| return getTooltip('step', xTitle, name, data); | |||||
| }, | |||||
| }, | |||||
| legend: { | |||||
| bottom: 10, | |||||
| icon: 'rect', | |||||
| itemWidth: 10, | |||||
| itemHeight: 10, | |||||
| itemGap: 20, | |||||
| textStyle: { | |||||
| color: 'rgba(29, 29, 32, 0.75)', | |||||
| fontSize: 12, | |||||
| }, | |||||
| }, | |||||
| color: colors, | |||||
| grid: { | |||||
| left: '15', | |||||
| right: '15', | |||||
| top: '20', | |||||
| bottom: '60', | |||||
| containLabel: true, | |||||
| }, | |||||
| xAxis: { | |||||
| type: 'category', | |||||
| boundaryGap: true, | |||||
| offset: 10, | |||||
| data: xAxisData, | |||||
| axisLabel: { | |||||
| color: 'rgba(29, 29, 32, 0.75)', | |||||
| fontSize: 12, | |||||
| }, | |||||
| axisTick: { | |||||
| show: false, | |||||
| }, | |||||
| axisLine: { | |||||
| lineStyle: { | |||||
| color: '#eaeaea', | |||||
| width: 1, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| yAxis: { | |||||
| type: 'value', | |||||
| axisLabel: { | |||||
| color: 'rgba(29, 29, 32, 0.75)', | |||||
| fontSize: 12, | |||||
| margin: 15, | |||||
| }, | |||||
| axisLine: { | |||||
| show: false, | |||||
| }, | |||||
| splitLine: { | |||||
| lineStyle: { | |||||
| color: '#e4e4e4', | |||||
| width: 1, | |||||
| type: 'dashed', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| series: seriesData, | |||||
| }; | |||||
| useEffect(() => { | |||||
| // 创建一个echarts实例,返回echarts实例 | |||||
| const chart = echarts.init(chartRef.current); | |||||
| // 设置图表实例的配置项和数据 | |||||
| chart.setOption(options); | |||||
| // 组件卸载 | |||||
| return () => { | |||||
| // myChart.dispose() 销毁实例 | |||||
| chart.dispose(); | |||||
| }; | |||||
| }, []); | |||||
| return ( | |||||
| <div className={styles['metrics-chart']}> | |||||
| <div className={styles['metrics-chart__title']}> | |||||
| <img src={require('@/assets/img/metrics-title-icon.png')}></img> | |||||
| <span>{name}</span> | |||||
| </div> | |||||
| <div className={styles['metrics-chart__chart']} ref={chartRef}></div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default MetricsChart; | |||||
| @@ -0,0 +1,33 @@ | |||||
| .metrics-tooltip { | |||||
| width: 172px; | |||||
| padding-left: 20px; | |||||
| background-color: white; | |||||
| font-size: 12px; | |||||
| } | |||||
| .metrics-tooltip .y-text { | |||||
| position: absolute; | |||||
| left: 10px; | |||||
| top: 10px; | |||||
| } | |||||
| .metrics-tooltip .x-text { | |||||
| position: absolute; | |||||
| left: 10px; | |||||
| top: 66px; | |||||
| } | |||||
| .metrics-tooltip .title { | |||||
| color: #575757; | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| white-space: nowrap; | |||||
| margin-bottom: 3px; | |||||
| } | |||||
| .metrics-tooltip .value { | |||||
| color: #1d1d20; | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| white-space: nowrap; | |||||
| } | |||||
| @@ -1,11 +1,14 @@ | |||||
| .model-evolution { | .model-evolution { | ||||
| width: 100%; | width: 100%; | ||||
| height: 100%; | height: 100%; | ||||
| padding: 0 30px 20px; | |||||
| overflow-x: hidden; | overflow-x: hidden; | ||||
| background-color: white; | |||||
| background: white; | |||||
| border-radius: 0 0 10px 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| &__graph { | &__graph { | ||||
| height: calc(100%); | |||||
| height: 100%; | |||||
| background-color: @background-color; | background-color: @background-color; | ||||
| background-image: url(@/assets/img/pipeline-canvas-bg.png); | background-image: url(@/assets/img/pipeline-canvas-bg.png); | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| @@ -0,0 +1,35 @@ | |||||
| .model-metrics { | |||||
| &__table { | |||||
| margin-top: 10px; | |||||
| padding: 20px 30px 0; | |||||
| background: white; | |||||
| border-radius: 10px; | |||||
| :global { | |||||
| .ant-table-container { | |||||
| border: none !important; | |||||
| } | |||||
| .ant-table-thead { | |||||
| .ant-table-cell { | |||||
| background-color: rgb(247, 247, 247); | |||||
| border-color: @border-color-base !important; | |||||
| } | |||||
| } | |||||
| .ant-table-tbody { | |||||
| .ant-table-cell { | |||||
| border-right: none !important; | |||||
| border-left: none !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__chart { | |||||
| display: flex; | |||||
| flex-wrap: wrap; | |||||
| gap: 15px; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| margin-top: 10px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,259 @@ | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { useCheck } from '@/hooks'; | |||||
| import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import tableCellRender from '@/utils/table'; | |||||
| import { Checkbox, Table, Tooltip, type TablePaginationConfig, type TableProps } from 'antd'; | |||||
| import { useEffect, useMemo, useState } from 'react'; | |||||
| import MetricsChart, { MetricsChatData } from '../MetricsChart'; | |||||
| import styles from './index.less'; | |||||
| enum MetricsType { | |||||
| Train = 'train', // 训练 | |||||
| Evaluate = 'evaluate', // 评估 | |||||
| } | |||||
| type TableData = { | |||||
| name: string; | |||||
| metrics_names?: string[]; | |||||
| metrics?: Record<string, number>; | |||||
| params_names?: string[]; | |||||
| params?: Record<string, string>; | |||||
| }; | |||||
| type ModelMetricsProps = { | |||||
| resourceId: number; | |||||
| identifier: string; | |||||
| owner: string; | |||||
| version: string; | |||||
| }; | |||||
| function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsProps) { | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [tableData, setTableData] = useState<TableData[]>([]); | |||||
| const [chartData, setChartData] = useState<Record<string, MetricsChatData[]> | undefined>( | |||||
| undefined, | |||||
| ); | |||||
| const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | |||||
| // 获取所有的指标名称 | |||||
| const allMetricsNames = useMemo(() => { | |||||
| const first: TableData | undefined = tableData.find( | |||||
| (item) => item.metrics_names && item.metrics_names.length > 0, | |||||
| ); | |||||
| return first?.metrics_names ?? []; | |||||
| }, [tableData]); | |||||
| const [ | |||||
| selectedMetrics, | |||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |||||
| _setSelectedMetrics, | |||||
| metricsChecked, | |||||
| metricsIndeterminate, | |||||
| checkAllMetrics, | |||||
| isSingleMetricsChecked, | |||||
| checkSingleMetrics, | |||||
| ] = useCheck(allMetricsNames); | |||||
| useEffect(() => { | |||||
| getModelPageVersions(); | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| if (selectedMetrics.length !== 0 && selectedRowKeys.length !== 0) { | |||||
| getModelVersionsMetrics(); | |||||
| } else { | |||||
| setChartData(undefined); | |||||
| } | |||||
| }, [selectedMetrics, selectedRowKeys]); | |||||
| useEffect(() => { | |||||
| const curRow = tableData.find((item) => item.name === version); | |||||
| if ( | |||||
| curRow && | |||||
| curRow.metrics_names && | |||||
| curRow.metrics_names.length > 0 && | |||||
| !selectedRowKeys.includes(version) | |||||
| ) { | |||||
| setSelectedRowKeys([version, ...selectedRowKeys]); | |||||
| } | |||||
| }, [version]); | |||||
| // 获取模型版本列表,带有参数和指标数据 | |||||
| const getModelPageVersions = async () => { | |||||
| const params = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| identifier: identifier, | |||||
| owner: owner, | |||||
| type: MetricsType.Train, | |||||
| }; | |||||
| const [res] = await to(getModelPageVersionsReq(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }; | |||||
| const getModelVersionsMetrics = async () => { | |||||
| const params = { | |||||
| versions: selectedRowKeys, | |||||
| metrics: selectedMetrics, | |||||
| type: MetricsType.Train, | |||||
| identifier: identifier, | |||||
| repo_id: resourceId, | |||||
| }; | |||||
| const [res] = await to(getModelVersionsMetricsReq(params)); | |||||
| if (res && res.data) { | |||||
| setChartData(res.data); | |||||
| } | |||||
| }; | |||||
| // 分页切换 | |||||
| const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| // console.log(pagination, filters, sorter, action); | |||||
| }; | |||||
| const rowSelection: TableProps['rowSelection'] = { | |||||
| type: 'checkbox', | |||||
| fixed: 'left', | |||||
| selectedRowKeys, | |||||
| onChange: (selectedRowKeys: React.Key[]) => { | |||||
| setSelectedRowKeys(selectedRowKeys); | |||||
| }, | |||||
| getCheckboxProps: (record: TableData) => ({ | |||||
| disabled: !record.metrics_names || record.metrics_names.length === 0, | |||||
| }), | |||||
| }; | |||||
| const showTableData = useMemo(() => { | |||||
| const index = tableData.findIndex((item) => item.name === version); | |||||
| if (index !== -1) { | |||||
| const rowData = tableData[index]; | |||||
| const newTableData = tableData.filter((_, idx) => idx !== index); | |||||
| return [rowData, ...newTableData]; | |||||
| } | |||||
| }, [version, tableData]); | |||||
| // 表头 | |||||
| const columns: TableProps['columns'] = useMemo(() => { | |||||
| const first: TableData | undefined = tableData.find( | |||||
| (item) => item.metrics_names && item.metrics_names.length > 0, | |||||
| ); | |||||
| return [ | |||||
| { | |||||
| title: '基本信息', | |||||
| align: 'center', | |||||
| children: [ | |||||
| { | |||||
| title: '版本号', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| width: 180, | |||||
| fixed: 'left', | |||||
| align: 'center', | |||||
| render: tableCellRender(false), | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| title: `训练参数`, | |||||
| align: 'center', | |||||
| children: first?.params_names?.map((name) => ({ | |||||
| title: ( | |||||
| <Tooltip title={name}> | |||||
| <span>{name}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| dataIndex: ['params', name], | |||||
| key: name, | |||||
| width: 120, | |||||
| align: 'center', | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| sorter: (a, b) => a.params?.[name] ?? 0 - b.params?.[name] ?? 0, | |||||
| showSorterTooltip: false, | |||||
| })), | |||||
| }, | |||||
| { | |||||
| title: () => ( | |||||
| <div> | |||||
| <Checkbox | |||||
| checked={metricsChecked} | |||||
| indeterminate={metricsIndeterminate} | |||||
| onChange={checkAllMetrics} | |||||
| ></Checkbox>{' '} | |||||
| <span>训练指标</span> | |||||
| </div> | |||||
| ), | |||||
| align: 'center', | |||||
| children: first?.metrics_names?.map((name) => ({ | |||||
| title: ( | |||||
| <div> | |||||
| <Checkbox | |||||
| checked={isSingleMetricsChecked(name)} | |||||
| onChange={(e) => { | |||||
| e.stopPropagation(); | |||||
| checkSingleMetrics(name); | |||||
| }} | |||||
| onClick={(e) => e.stopPropagation()} | |||||
| ></Checkbox>{' '} | |||||
| <Tooltip title={name}> | |||||
| <span>{name}</span> | |||||
| </Tooltip> | |||||
| </div> | |||||
| ), | |||||
| dataIndex: ['metrics', name], | |||||
| key: name, | |||||
| width: 120, | |||||
| align: 'center', | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| sorter: (a, b) => a.metrics?.[name] ?? 0 - b.metrics?.[name] ?? 0, | |||||
| showSorterTooltip: false, | |||||
| })), | |||||
| }, | |||||
| ]; | |||||
| }, [tableData, selectedMetrics]); | |||||
| return ( | |||||
| <div className={styles['model-metrics']}> | |||||
| <div className={styles['model-metrics__table']}> | |||||
| <SubAreaTitle | |||||
| title="指标参数差异对比" | |||||
| image={require('@/assets/img/model-metrics.png')} | |||||
| style={{ marginBottom: '15px' }} | |||||
| ></SubAreaTitle> | |||||
| <Table | |||||
| dataSource={showTableData} | |||||
| columns={columns} | |||||
| rowSelection={rowSelection} | |||||
| bordered={true} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| showTotal: () => `共${total}条`, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="name" | |||||
| /> | |||||
| </div> | |||||
| <div className={styles['model-metrics__chart']}> | |||||
| {chartData && | |||||
| Object.keys(chartData).map((key) => ( | |||||
| <MetricsChart key={key} name={key} chartData={chartData[key]}></MetricsChart> | |||||
| ))} | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ModelMetrics; | |||||
| @@ -149,4 +149,20 @@ export function exportModelReq(data) { | |||||
| method: 'POST', | method: 'POST', | ||||
| data | data | ||||
| }); | }); | ||||
| } | |||||
| // 分页查询模型所有版本,带有参数和指标数据 | |||||
| export function getModelPageVersionsReq(params) { | |||||
| return request(`/api/mmp/newmodel/queryVersions`, { | |||||
| method: 'GET', | |||||
| params | |||||
| }); | |||||
| } | |||||
| // 获取模型版本指标对比 | |||||
| export function getModelVersionsMetricsReq(data) { | |||||
| return request(`/api/mmp/newmodel/queryVersionsMetrics`, { | |||||
| method: 'POST', | |||||
| data | |||||
| }); | |||||
| } | } | ||||
| @@ -59,6 +59,7 @@ export function getQueryByExperimentLog(data) { | |||||
| method: 'POST', | method: 'POST', | ||||
| data, | data, | ||||
| skipErrorHandler: true, | skipErrorHandler: true, | ||||
| skipLoading: true, | |||||
| }); | }); | ||||
| } | } | ||||
| // 查询实例节点结果 | // 查询实例节点结果 | ||||
| @@ -128,16 +129,18 @@ export function getTensorBoardStatusReq(data) { | |||||
| } | } | ||||
| // 获取当前实验的模型推理指标信息 | // 获取当前实验的模型推理指标信息 | ||||
| export function getExpEvaluateInfosReq(experimentId) { | |||||
| export function getExpEvaluateInfosReq(experimentId, params) { | |||||
| return request(`/api/mmp/aim/getExpEvaluateInfos/${experimentId}`, { | return request(`/api/mmp/aim/getExpEvaluateInfos/${experimentId}`, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params | |||||
| }); | }); | ||||
| } | } | ||||
| // 获取当前实验的模型训练指标信息 | // 获取当前实验的模型训练指标信息 | ||||
| export function getExpTrainInfosReq(experimentId) { | |||||
| export function getExpTrainInfosReq(experimentId, params) { | |||||
| return request(`/api/mmp/aim/getExpTrainInfos/${experimentId}`, { | return request(`/api/mmp/aim/getExpTrainInfos/${experimentId}`, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params | |||||
| }); | }); | ||||
| } | } | ||||