| @@ -12,12 +12,21 @@ import './index.less'; | |||||
| export interface KFModalProps extends ModalProps { | export interface KFModalProps extends ModalProps { | ||||
| image?: string; | image?: string; | ||||
| } | } | ||||
| function KFModal({ title, image, children, className = '', centered, ...rest }: KFModalProps) { | |||||
| function KFModal({ | |||||
| title, | |||||
| image, | |||||
| children, | |||||
| className = '', | |||||
| centered, | |||||
| maskClosable, | |||||
| ...rest | |||||
| }: KFModalProps) { | |||||
| return ( | return ( | ||||
| <Modal | <Modal | ||||
| className={classNames(['kf-modal', className])} | className={classNames(['kf-modal', className])} | ||||
| {...rest} | {...rest} | ||||
| centered={centered ?? true} | centered={centered ?? true} | ||||
| maskClosable={maskClosable ?? false} | |||||
| title={<ModalTitle title={title} image={image}></ModalTitle>} | title={<ModalTitle title={title} image={image}></ModalTitle>} | ||||
| > | > | ||||
| {children} | {children} | ||||
| @@ -29,7 +29,6 @@ function ParameterInput({ | |||||
| onClick, | onClick, | ||||
| canInput = true, | canInput = true, | ||||
| textArea = false, | textArea = false, | ||||
| placeholder, | |||||
| allowClear, | allowClear, | ||||
| className, | className, | ||||
| style, | style, | ||||
| @@ -37,8 +36,6 @@ function ParameterInput({ | |||||
| disabled = false, | disabled = false, | ||||
| ...rest | ...rest | ||||
| }: ParameterInputProps) { | }: ParameterInputProps) { | ||||
| // console.log('ParameterInput', value); | |||||
| const valueObj = | const valueObj = | ||||
| typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | ||||
| if (valueObj && !valueObj.showValue) { | if (valueObj && !valueObj.showValue) { | ||||
| @@ -46,6 +43,7 @@ function ParameterInput({ | |||||
| } | } | ||||
| const isSelect = valueObj?.fromSelect; | const isSelect = valueObj?.fromSelect; | ||||
| const InputComponent = textArea ? Input.TextArea : Input; | const InputComponent = textArea ? Input.TextArea : Input; | ||||
| const placeholder = valueObj?.placeholder; | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -68,9 +66,12 @@ function ParameterInput({ | |||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| onChange?.({ | onChange?.({ | ||||
| ...valueObj, | ...valueObj, | ||||
| fromSelect: false, | |||||
| value: undefined, | value: undefined, | ||||
| showValue: undefined, | showValue: undefined, | ||||
| fromSelect: false, | |||||
| activeTab: undefined, | |||||
| expandedKeys: undefined, | |||||
| checkedKeys: undefined, | |||||
| }); | }); | ||||
| }} | }} | ||||
| /> | /> | ||||
| @@ -0,0 +1,72 @@ | |||||
| import { getDatasetList, getModelList } from '@/services/dataset/index.js'; | |||||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||||
| import { ComputingResource } from '@/types'; | |||||
| import { type SelectProps } from 'antd'; | |||||
| // 过滤资源规格 | |||||
| const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | |||||
| input: string, | |||||
| option?: ComputingResource, | |||||
| ) => { | |||||
| return ( | |||||
| option?.computing_resource?.toLocaleLowerCase()?.includes(input.toLocaleLowerCase()) ?? false | |||||
| ); | |||||
| }; | |||||
| // id 从 number 转换为 string | |||||
| const convertId = (item: any) => ({ ...item, id: String(item.id) }); | |||||
| export type SelectPropsConfig = { | |||||
| getOptions: () => Promise<any>; | |||||
| fieldNames?: SelectProps['fieldNames']; | |||||
| optionFilterProp?: SelectProps['optionFilterProp']; | |||||
| filterOption?: SelectProps['filterOption']; | |||||
| }; | |||||
| export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| dataset: { | |||||
| getOptions: async () => { | |||||
| const res = await getDatasetList({ | |||||
| page: 0, | |||||
| size: 1000, | |||||
| available_range: 0, | |||||
| }); | |||||
| return res?.data?.content?.map(convertId) ?? []; | |||||
| }, | |||||
| fieldNames: { | |||||
| label: 'name', | |||||
| value: 'id', | |||||
| }, | |||||
| optionFilterProp: 'name', | |||||
| }, | |||||
| model: { | |||||
| getOptions: async () => { | |||||
| const res = await getModelList({ | |||||
| page: 0, | |||||
| size: 1000, | |||||
| available_range: 0, | |||||
| }); | |||||
| return res?.data?.content?.map(convertId) ?? []; | |||||
| }, | |||||
| fieldNames: { | |||||
| label: 'name', | |||||
| value: 'id', | |||||
| }, | |||||
| optionFilterProp: 'name', | |||||
| }, | |||||
| resource: { | |||||
| getOptions: async () => { | |||||
| const res = await getComputingResourceReq({ | |||||
| page: 0, | |||||
| size: 1000, | |||||
| resource_type: '', | |||||
| }); | |||||
| return res?.data?.content ?? []; | |||||
| }, | |||||
| fieldNames: { | |||||
| label: 'description', | |||||
| value: 'standard', | |||||
| }, | |||||
| filterOption: filterResourceStandard as SelectProps['filterOption'], | |||||
| }, | |||||
| }; | |||||
| @@ -0,0 +1,58 @@ | |||||
| import { PipelineNodeModelParameter } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Select } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { paramSelectConfig } from './config'; | |||||
| type ParameterSelectProps = { | |||||
| value?: PipelineNodeModelParameter; | |||||
| onChange?: (value: PipelineNodeModelParameter) => void; | |||||
| disabled?: boolean; | |||||
| }; | |||||
| function ParameterSelect({ value, onChange, disabled = false }: ParameterSelectProps) { | |||||
| const [options, setOptions] = useState([]); | |||||
| const valueNonNullable = value ?? ({} as PipelineNodeModelParameter); | |||||
| const { item_type } = valueNonNullable; | |||||
| const propsConfig = paramSelectConfig[item_type]; | |||||
| useEffect(() => { | |||||
| getSelectOptions(); | |||||
| }, []); | |||||
| const hangleChange = (e: string) => { | |||||
| onChange?.({ | |||||
| ...valueNonNullable, | |||||
| value: e, | |||||
| }); | |||||
| }; | |||||
| // 获取下拉数据 | |||||
| const getSelectOptions = async () => { | |||||
| if (!propsConfig) { | |||||
| return; | |||||
| } | |||||
| const getOptions = propsConfig.getOptions; | |||||
| const [res] = await to(getOptions()); | |||||
| if (res) { | |||||
| setOptions(res); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <Select | |||||
| placeholder={valueNonNullable.placeholder} | |||||
| filterOption={propsConfig?.filterOption} | |||||
| options={options} | |||||
| fieldNames={propsConfig?.fieldNames} | |||||
| value={valueNonNullable.value} | |||||
| optionFilterProp={propsConfig.optionFilterProp} | |||||
| onChange={hangleChange} | |||||
| disabled={disabled} | |||||
| showSearch | |||||
| allowClear | |||||
| /> | |||||
| ); | |||||
| } | |||||
| export default ParameterSelect; | |||||
| @@ -7,7 +7,6 @@ import { getDictSelectOption } from '@/services/system/dict'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| import { | import { | ||||
| App, | |||||
| Button, | Button, | ||||
| Form, | Form, | ||||
| Input, | Input, | ||||
| @@ -15,12 +14,13 @@ import { | |||||
| Select, | Select, | ||||
| Upload, | Upload, | ||||
| UploadFile, | UploadFile, | ||||
| message, | |||||
| type ModalProps, | type ModalProps, | ||||
| type UploadProps, | type UploadProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import { omit } from 'lodash'; | import { omit } from 'lodash'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { CategoryData } from '../../types'; | |||||
| import { CategoryData } from '../../config'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| @@ -32,7 +32,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | ||||
| const [uuid] = useState(Date.now()); | const [uuid] = useState(Date.now()); | ||||
| const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | ||||
| const { message } = App.useApp(); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getClusterOptions(); | getClusterOptions(); | ||||
| @@ -1,18 +1,18 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { CategoryData } from '@/pages/Dataset/types'; | |||||
| import { CategoryData } from '@/pages/Dataset/config'; | |||||
| import { addModel } from '@/services/dataset/index.js'; | import { addModel } from '@/services/dataset/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| import { | import { | ||||
| App, | |||||
| Button, | Button, | ||||
| Form, | Form, | ||||
| Input, | Input, | ||||
| Select, | Select, | ||||
| Upload, | Upload, | ||||
| UploadFile, | UploadFile, | ||||
| message, | |||||
| type ModalProps, | type ModalProps, | ||||
| type UploadProps, | type UploadProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| @@ -28,7 +28,6 @@ interface AddModelModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) { | function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) { | ||||
| const [uuid] = useState(Date.now()); | const [uuid] = useState(Date.now()); | ||||
| const { message } = App.useApp(); | |||||
| // 上传组件参数 | // 上传组件参数 | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| @@ -1,16 +1,16 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { ResourceType, resourceConfig } from '@/pages/Dataset/types'; | |||||
| import { ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| import { | import { | ||||
| App, | |||||
| Button, | Button, | ||||
| Form, | Form, | ||||
| Input, | Input, | ||||
| Upload, | Upload, | ||||
| UploadFile, | UploadFile, | ||||
| message, | |||||
| type ModalProps, | type ModalProps, | ||||
| type UploadProps, | type UploadProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| @@ -33,7 +33,6 @@ function AddVersionModal({ | |||||
| ...rest | ...rest | ||||
| }: AddVersionModalProps) { | }: AddVersionModalProps) { | ||||
| const [uuid] = useState(Date.now()); | const [uuid] = useState(Date.now()); | ||||
| const { message } = App.useApp(); | |||||
| // 上传组件参数 | // 上传组件参数 | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| @@ -46,7 +45,7 @@ function AddVersionModal({ | |||||
| // 上传请求 | // 上传请求 | ||||
| const createDatasetVersion = async (params: any) => { | const createDatasetVersion = async (params: any) => { | ||||
| const request = resourceConfig[resourceType].addVersionReq; | |||||
| const request = resourceConfig[resourceType].addVersion; | |||||
| const [res] = await to(request(params)); | const [res] = await to(request(params)); | ||||
| if (res) { | if (res) { | ||||
| message.success('创建成功'); | message.success('创建成功'); | ||||
| @@ -1,5 +1,5 @@ | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../config'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type CategoryItemProps = { | type CategoryItemProps = { | ||||
| @@ -20,13 +20,13 @@ function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemP | |||||
| <img | <img | ||||
| className={styles['category-item__icon']} | className={styles['category-item__icon']} | ||||
| style={{ width: '22px' }} | style={{ width: '22px' }} | ||||
| src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}.png`} | |||||
| src={`/assets/images/${resourceConfig[resourceType].prefix}/${item.path}.png`} | |||||
| alt="" | alt="" | ||||
| /> | /> | ||||
| <img | <img | ||||
| className={styles['category-item__active-icon']} | className={styles['category-item__active-icon']} | ||||
| style={{ width: '22px' }} | style={{ width: '22px' }} | ||||
| src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}-hover.png`} | |||||
| src={`/assets/images/${resourceConfig[resourceType].prefix}/${item.path}-hover.png`} | |||||
| alt="" | alt="" | ||||
| /> | /> | ||||
| <span className={styles['category-item__name']}>{item.name}</span> | <span className={styles['category-item__name']}>{item.name}</span> | ||||
| @@ -1,5 +1,5 @@ | |||||
| import { Flex, Input } from 'antd'; | import { Flex, Input } from 'antd'; | ||||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../config'; | |||||
| import CategoryItem from '../CategoryItem'; | import CategoryItem from '../CategoryItem'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -0,0 +1,52 @@ | |||||
| .resource-intro { | |||||
| height: 100%; | |||||
| &__top { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 25px 30px; | |||||
| background-image: url(/assets/images/dataset-back.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| &__name { | |||||
| margin-bottom: 12px; | |||||
| color: @text-color; | |||||
| font-size: 20; | |||||
| } | |||||
| &__tag { | |||||
| margin-right: 10px; | |||||
| padding: 4px 10px; | |||||
| color: @primary-color; | |||||
| font-size: 14px; | |||||
| background: rgba(22, 100, 255, 0.1); | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| &__bottom { | |||||
| height: calc(100% - 120px); | |||||
| padding: 8px 30px 20px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| &__title { | |||||
| margin: 30px 0 10px; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: @font-size; | |||||
| } | |||||
| &__intro { | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,86 @@ | |||||
| import { to } from '@/utils/promise'; | |||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { Flex, Tabs } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { ResourceData, ResourceType, resourceConfig } from '../../config'; | |||||
| import ResourceVersion from '../ResourceVersion'; | |||||
| import styles from './index.less'; | |||||
| type ResourceIntroProps = { | |||||
| resourceType: ResourceType; | |||||
| }; | |||||
| const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||||
| const [info, setInfo] = useState<ResourceData>({} as ResourceData); | |||||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||||
| const [searchParams] = useSearchParams(); | |||||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||||
| const resourceId = Number(locationParams.id); | |||||
| const name = resourceConfig[resourceType].name; | |||||
| useEffect(() => { | |||||
| getModelByDetail(); | |||||
| }, []); | |||||
| // 获取详情 | |||||
| const getModelByDetail = async () => { | |||||
| const request = resourceConfig[resourceType].getInfo; | |||||
| const [res] = await to(request(resourceId)); | |||||
| if (res) { | |||||
| setInfo(res.data); | |||||
| } | |||||
| }; | |||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: `${name}简介`, | |||||
| children: ( | |||||
| <> | |||||
| <div className={styles['resource-intro__title']}>简介</div> | |||||
| <div className={styles['resource-intro__intro']}>{info.description}</div> | |||||
| </> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: `${name}文件/版本`, | |||||
| children: ( | |||||
| <ResourceVersion | |||||
| resourceType={resourceType} | |||||
| resourceId={resourceId} | |||||
| resourceName={info.name} | |||||
| isPublic={isPublic} | |||||
| ></ResourceVersion> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| const infoTypePropertyName = resourceConfig[resourceType] | |||||
| .infoTypePropertyName as keyof ResourceData; | |||||
| const infoTagPropertyName = resourceConfig[resourceType] | |||||
| .infoTagPropertyName as keyof ResourceData; | |||||
| return ( | |||||
| <div className={styles['resource-intro']}> | |||||
| <div className={styles['resource-intro__top']}> | |||||
| <span className={styles['resource-intro__top__name']}>{info.name}</span> | |||||
| <Flex align="center"> | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {name} id:{info.id} | |||||
| </div> | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {info[infoTypePropertyName] || '--'} | |||||
| </div> | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {info[infoTagPropertyName] || '--'} | |||||
| </div> | |||||
| </Flex> | |||||
| </div> | |||||
| <div className={styles['resource-intro__bottom']}> | |||||
| <Tabs defaultActiveKey="1" items={items}></Tabs> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default ResourceIntro; | |||||
| @@ -7,7 +7,7 @@ import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | ||||
| import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | ||||
| import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../types'; | |||||
| import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config'; | |||||
| import AddDatasetModal from '../AddDatasetModal'; | import AddDatasetModal from '../AddDatasetModal'; | ||||
| import ResourceItem from '../Resourcetem'; | import ResourceItem from '../Resourcetem'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -129,11 +129,8 @@ function ResourceList( | |||||
| activeType: dataType, | activeType: dataType, | ||||
| activeTag: dataTag, | activeTag: dataTag, | ||||
| }); | }); | ||||
| if (resourceType === ResourceType.Dataset) { | |||||
| navigate(`/dataset/dataset/${record.id}?isPublic=${isPublic}`); | |||||
| } else { | |||||
| navigate(`/dataset/model/${record.id}?isPublic=${isPublic}`); | |||||
| } | |||||
| const prefix = resourceConfig[resourceType].prefix; | |||||
| navigate(`/dataset/${prefix}/${record.id}?isPublic=${isPublic}`); | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -4,7 +4,7 @@ import { getAssetIcon } from '@/services/dataset/index.js'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Flex, Tabs, type TabsProps } from 'antd'; | import { Flex, Tabs, type TabsProps } from 'antd'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../config'; | |||||
| import CategoryList from '../CategoryList'; | import CategoryList from '../CategoryList'; | ||||
| import ResourceList, { ResourceListRef } from '../ResourceList'; | import ResourceList, { ResourceListRef } from '../ResourceList'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -0,0 +1,4 @@ | |||||
| .resource-version { | |||||
| color: @text-color; | |||||
| font-size: @font-size-content; | |||||
| } | |||||
| @@ -0,0 +1,236 @@ | |||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | |||||
| import { ResourceType } from '@/pages/Dataset/config'; | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { App, Button, Flex, Select, Table } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { ResourceFileData, resourceConfig } from '../../config'; | |||||
| import styles from './index.less'; | |||||
| type ResourceVersionProps = { | |||||
| resourceType: ResourceType; | |||||
| resourceId: number; | |||||
| resourceName: string; | |||||
| isPublic: boolean; | |||||
| }; | |||||
| function ResourceVersion({ | |||||
| resourceType, | |||||
| resourceId, | |||||
| resourceName, | |||||
| isPublic, | |||||
| }: ResourceVersionProps) { | |||||
| const [versionList, setVersionList] = useState([]); | |||||
| const [version, setVersion] = useState<string | undefined>(undefined); | |||||
| const [fileList, setFileList] = useState<ResourceFileData[]>([]); | |||||
| const { message } = App.useApp(); | |||||
| useEffect(() => { | |||||
| getVersionList(); | |||||
| }, []); | |||||
| // 获取版本列表 | |||||
| const getVersionList = async () => { | |||||
| const request = resourceConfig[resourceType].getVersions; | |||||
| const [res] = await to(request(resourceId)); | |||||
| if (res && res.data && res.data.length > 0) { | |||||
| setVersionList( | |||||
| res.data.map((item: string) => { | |||||
| return { | |||||
| label: item, | |||||
| value: item, | |||||
| }; | |||||
| }), | |||||
| ); | |||||
| setVersion(res.data[0]); | |||||
| getFileList(res.data[0]); | |||||
| } else { | |||||
| setVersion(undefined); | |||||
| setFileList([]); | |||||
| } | |||||
| }; | |||||
| // 获取版本下的文件列表 | |||||
| const getFileList = async (version: string) => { | |||||
| const params = { | |||||
| version, | |||||
| [resourceConfig[resourceType].fileReqParamKey]: resourceId, | |||||
| }; | |||||
| const request = resourceConfig[resourceType].getFiles; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| setFileList(res?.data?.content ?? []); | |||||
| } | |||||
| }; | |||||
| // 删除版本 | |||||
| const deleteVersion = async () => { | |||||
| const request = resourceConfig[resourceType].deleteVersion; | |||||
| const params = { | |||||
| [resourceConfig[resourceType].idParamKey]: resourceId, | |||||
| version, | |||||
| }; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| getVersionList(); | |||||
| message.success('删除成功'); | |||||
| } | |||||
| }; | |||||
| // 新建版本 | |||||
| const showModal = () => { | |||||
| const { close } = openAntdModal(AddVersionModal, { | |||||
| resourceType: resourceType, | |||||
| resourceId: resourceId, | |||||
| initialName: resourceName, | |||||
| onOk: () => { | |||||
| getVersionList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 处理删除 | |||||
| const hanldeDelete = () => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| deleteVersion(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 全部导出 | |||||
| const handleExport = async () => { | |||||
| const url = resourceConfig[resourceType].downloadAllAction; | |||||
| downLoadZip(url, { models_id: resourceId, version }); | |||||
| }; | |||||
| // 单个导出 | |||||
| const downloadAlone = (record: ResourceFileData) => { | |||||
| const url = resourceConfig[resourceType].downloadSingleAction; | |||||
| downLoadZip(`${url}/${record.id}`); | |||||
| }; | |||||
| // 版本变化 | |||||
| const handleChange = (value: string) => { | |||||
| if (value) { | |||||
| getFileList(value); | |||||
| setVersion(value); | |||||
| } else { | |||||
| setVersion(undefined); | |||||
| } | |||||
| }; | |||||
| const columns = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: 80, | |||||
| render(text: string, record: ResourceFileData, index: number) { | |||||
| return <span>{index + 1}</span>; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| title: '文件名称', | |||||
| dataIndex: 'file_name', | |||||
| key: 'file_name', | |||||
| render: (text: string, record: ResourceFileData) => ( | |||||
| <a onClick={() => downloadAlone(record)}>{text}</a> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| title: '版本号', | |||||
| dataIndex: 'version', | |||||
| key: 'version', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '文件大小', | |||||
| dataIndex: 'file_size', | |||||
| key: 'file_size', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '更新时间', | |||||
| dataIndex: 'update_time', | |||||
| key: 'update_time', | |||||
| render: DateTableCell, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'option', | |||||
| width: '100px', | |||||
| key: 'option', | |||||
| render: (_: any, record: ResourceFileData) => [ | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="download" | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| onClick={() => downloadAlone(record)} | |||||
| > | |||||
| 下载 | |||||
| </Button>, | |||||
| ], | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['resource-version']}> | |||||
| <Flex justify="space-between" align="center" style={{ margin: '30px 0' }}> | |||||
| <Flex align="center"> | |||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ width: '160px', marginRight: '20px' }} | |||||
| value={version} | |||||
| allowClear | |||||
| onChange={handleChange} | |||||
| options={versionList} | |||||
| /> | |||||
| <Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 创建新版本 | |||||
| </Button> | |||||
| </Flex> | |||||
| <Flex align="center"> | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| style={{ marginRight: '20px' }} | |||||
| onClick={hanldeDelete} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| )} | |||||
| <Button | |||||
| type="default" | |||||
| disabled={!version} | |||||
| onClick={handleExport} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| </Flex> | |||||
| </Flex> | |||||
| <div style={{ marginBottom: '30px', fontSize: '15px' }}> | |||||
| {fileList.length > 0 && fileList[0].description | |||||
| ? '版本描述:' + fileList[0].description | |||||
| : null} | |||||
| </div> | |||||
| <Table columns={columns} dataSource={fileList} pagination={false} rowKey="id" /> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ResourceVersion; | |||||
| @@ -3,7 +3,7 @@ import creatByImg from '@/assets/img/creatBy.png'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { Button, Flex, Typography } from 'antd'; | import { Button, Flex, Typography } from 'antd'; | ||||
| import { ResourceData } from '../../types'; | |||||
| import { ResourceData } from '../../config'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ResourceItemProps = { | type ResourceItemProps = { | ||||
| @@ -4,10 +4,14 @@ import { | |||||
| addDatasetVersionDetail, | addDatasetVersionDetail, | ||||
| addModelsVersionDetail, | addModelsVersionDetail, | ||||
| deleteDataset, | deleteDataset, | ||||
| deleteDatasetVersion, | |||||
| deleteModel, | deleteModel, | ||||
| deleteModelVersion, | |||||
| getDatasetById, | |||||
| getDatasetList, | getDatasetList, | ||||
| getDatasetVersionIdList, | getDatasetVersionIdList, | ||||
| getDatasetVersionsById, | getDatasetVersionsById, | ||||
| getModelById, | |||||
| getModelList, | getModelList, | ||||
| getModelVersionIdList, | getModelVersionIdList, | ||||
| getModelVersionsById, | getModelVersionsById, | ||||
| @@ -24,6 +28,9 @@ type ResourceTypeInfo = { | |||||
| getVersions: (params: any) => Promise<any>; | getVersions: (params: any) => Promise<any>; | ||||
| getFiles: (params: any) => Promise<any>; | getFiles: (params: any) => Promise<any>; | ||||
| deleteRecord: (params: any) => Promise<any>; | deleteRecord: (params: any) => Promise<any>; | ||||
| addVersion: (params: any) => Promise<any>; | |||||
| deleteVersion: (params: any) => Promise<any>; | |||||
| getInfo: (params: any) => Promise<any>; | |||||
| name: string; | name: string; | ||||
| typeParamKey: string; | typeParamKey: string; | ||||
| tagParamKey: string; | tagParamKey: string; | ||||
| @@ -33,13 +40,16 @@ type ResourceTypeInfo = { | |||||
| tagTitle: string; | tagTitle: string; | ||||
| typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue) | typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue) | ||||
| tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue) | tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue) | ||||
| iconPathPrefix: string; // 图标路径前缀 | |||||
| prefix: string; // 前缀 | |||||
| deleteModalTitle: string; // 删除弹框的title | deleteModalTitle: string; // 删除弹框的title | ||||
| addBtnTitle: string; // 新增按钮的title | addBtnTitle: string; // 新增按钮的title | ||||
| addVersionReq: (params: any) => Promise<any>; | |||||
| idParamKey: string; | |||||
| idParamKey: 'models_id' | 'dataset_id'; | |||||
| uploadAction: string; | uploadAction: string; | ||||
| uploadAccept?: string; | uploadAccept?: string; | ||||
| downloadAllAction: string; | |||||
| downloadSingleAction: string; | |||||
| infoTypePropertyName: string; | |||||
| infoTagPropertyName: string; | |||||
| }; | }; | ||||
| export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | ||||
| @@ -48,6 +58,9 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| getVersions: getDatasetVersionsById, | getVersions: getDatasetVersionsById, | ||||
| getFiles: getDatasetVersionIdList, | getFiles: getDatasetVersionIdList, | ||||
| deleteRecord: deleteDataset, | deleteRecord: deleteDataset, | ||||
| addVersion: addDatasetVersionDetail, | |||||
| deleteVersion: deleteDatasetVersion, | |||||
| getInfo: getDatasetById, | |||||
| name: '数据集', | name: '数据集', | ||||
| typeParamKey: 'data_type', | typeParamKey: 'data_type', | ||||
| tagParamKey: 'data_tag', | tagParamKey: 'data_tag', | ||||
| @@ -68,19 +81,25 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| tagTitle: '研究方向/应用领域', | tagTitle: '研究方向/应用领域', | ||||
| typeValue: 1, | typeValue: 1, | ||||
| tagValue: 2, | tagValue: 2, | ||||
| iconPathPrefix: 'dataset', | |||||
| prefix: 'dataset', | |||||
| deleteModalTitle: '确定删除该条数据集实例吗?', | deleteModalTitle: '确定删除该条数据集实例吗?', | ||||
| addBtnTitle: '新建数据集', | addBtnTitle: '新建数据集', | ||||
| addVersionReq: addDatasetVersionDetail, | |||||
| idParamKey: 'dataset_id', | idParamKey: 'dataset_id', | ||||
| uploadAction: '/api/mmp/dataset/upload', | uploadAction: '/api/mmp/dataset/upload', | ||||
| uploadAccept: '.zip,.tgz', | uploadAccept: '.zip,.tgz', | ||||
| downloadAllAction: '/api/mmp/dataset/downloadAllFilesl', | |||||
| downloadSingleAction: '/api/mmp/dataset/download', | |||||
| infoTypePropertyName: 'dataset_type_name', | |||||
| infoTagPropertyName: 'dataset_tag_name', | |||||
| }, | }, | ||||
| [ResourceType.Model]: { | [ResourceType.Model]: { | ||||
| getList: getModelList, | getList: getModelList, | ||||
| getVersions: getModelVersionsById, | getVersions: getModelVersionsById, | ||||
| getFiles: getModelVersionIdList, | getFiles: getModelVersionIdList, | ||||
| deleteRecord: deleteModel, | deleteRecord: deleteModel, | ||||
| addVersion: addModelsVersionDetail, | |||||
| deleteVersion: deleteModelVersion, | |||||
| getInfo: getModelById, | |||||
| name: '模型', | name: '模型', | ||||
| typeParamKey: 'model_type', | typeParamKey: 'model_type', | ||||
| tagParamKey: 'model_tag', | tagParamKey: 'model_tag', | ||||
| @@ -101,13 +120,16 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| tagTitle: '模型能力', | tagTitle: '模型能力', | ||||
| typeValue: 3, | typeValue: 3, | ||||
| tagValue: 4, | tagValue: 4, | ||||
| iconPathPrefix: 'model', | |||||
| prefix: 'model', | |||||
| deleteModalTitle: '确定删除该条模型实例吗?', | deleteModalTitle: '确定删除该条模型实例吗?', | ||||
| addBtnTitle: '新建模型', | addBtnTitle: '新建模型', | ||||
| addVersionReq: addModelsVersionDetail, | |||||
| idParamKey: 'models_id', | idParamKey: 'models_id', | ||||
| uploadAction: '/api/mmp/models/upload', | uploadAction: '/api/mmp/models/upload', | ||||
| uploadAccept: undefined, | uploadAccept: undefined, | ||||
| downloadAllAction: '/api/mmp/models/downloadAllFiles', | |||||
| downloadSingleAction: '/api/mmp/models/download_model', | |||||
| infoTypePropertyName: 'model_type_name', | |||||
| infoTagPropertyName: 'model_tag_name', | |||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -119,11 +141,29 @@ export type CategoryData = { | |||||
| path: string; | path: string; | ||||
| }; | }; | ||||
| // 数据类型 | |||||
| // 资源数据 | |||||
| export type ResourceData = { | export type ResourceData = { | ||||
| id: number; | id: number; | ||||
| name: string; | name: string; | ||||
| description: string; | description: string; | ||||
| create_by: string; | create_by: string; | ||||
| update_time: string; | update_time: string; | ||||
| model_type_name?: string; | |||||
| model_tag_name?: string; | |||||
| dataset_type_name?: string; | |||||
| dataset_tag_name?: string; | |||||
| }; | |||||
| // 版本文件数据 | |||||
| export type ResourceFileData = { | |||||
| id: number; | |||||
| file_name: string; | |||||
| file_size: string; | |||||
| description: string; | |||||
| create_by: string; | |||||
| create_time: string; | |||||
| update_by: string; | |||||
| update_time: string; | |||||
| url: string; | |||||
| version: string; | |||||
| }; | }; | ||||
| @@ -1,5 +1,5 @@ | |||||
| import ResourcePage from './components/ResourcePage'; | import ResourcePage from './components/ResourcePage'; | ||||
| import { ResourceType } from './types'; | |||||
| import { ResourceType } from './config'; | |||||
| const DatasetPage = () => { | const DatasetPage = () => { | ||||
| return <ResourcePage resourceType={ResourceType.Dataset} />; | return <ResourcePage resourceType={ResourceType.Dataset} />; | ||||
| @@ -1,263 +0,0 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { ResourceType } from '@/pages/Dataset/types'; | |||||
| import { | |||||
| deleteDatasetVersion, | |||||
| getDatasetById, | |||||
| getDatasetVersionIdList, | |||||
| getDatasetVersionsById, | |||||
| } from '@/services/dataset/index.js'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { App, Button, Input, Select, Table, Tabs } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import AddVersionModal from './components/AddVersionModal'; | |||||
| import Styles from './intro.less'; | |||||
| const { Search } = Input; | |||||
| const { TabPane } = Tabs; | |||||
| const Dataset = () => { | |||||
| const { message } = App.useApp(); | |||||
| const [formList, setFormList] = useState([]); | |||||
| const [datasetDetailObj, setDatasetDetailObj] = useState({}); | |||||
| const [version, setVersion] = useState(null); | |||||
| const [versionList, setVersionList] = useState([]); | |||||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||||
| const [searchParams] = useSearchParams(); | |||||
| const [wordList, setWordList] = useState([]); | |||||
| const [activeTabKey, setActiveTabKey] = useState('1'); | |||||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||||
| const getDatasetByDetail = () => { | |||||
| getDatasetById(locationParams.id).then((ret) => { | |||||
| console.log(ret); | |||||
| setDatasetDetailObj(ret.data); | |||||
| }); | |||||
| }; | |||||
| // 获取数据集版本 | |||||
| const getDatasetVersionList = () => { | |||||
| getDatasetVersionsById(locationParams.id).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.data && ret.data.length > 0) { | |||||
| setVersionList( | |||||
| ret.data.map((item) => { | |||||
| return { | |||||
| label: item, | |||||
| value: item, | |||||
| }; | |||||
| }), | |||||
| ); | |||||
| setVersion(ret.data[0]); | |||||
| getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id }); | |||||
| } else { | |||||
| setVersion(null); | |||||
| setWordList([]); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| getDatasetByDetail(); | |||||
| getDatasetVersionList(); | |||||
| return () => {}; | |||||
| }, []); | |||||
| const showModal = () => { | |||||
| const { close } = openAntdModal(AddVersionModal, { | |||||
| resourceType: ResourceType.Dataset, | |||||
| resourceId: locationParams.id, | |||||
| initialName: datasetDetailObj.name, | |||||
| onOk: () => { | |||||
| getDatasetVersionList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const deleteDataset = () => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该数据集版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => { | |||||
| getDatasetVersionList(); | |||||
| message.success('删除成功'); | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 获取版本下的文件列表 | |||||
| const getDatasetVersions = (params) => { | |||||
| getDatasetVersionIdList(params).then((res) => { | |||||
| setWordList(res?.data?.content ?? []); | |||||
| }); | |||||
| }; | |||||
| const handleExport = async () => { | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version }); | |||||
| }; | |||||
| const downloadAlone = (e, record) => { | |||||
| console.log(record); | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/dataset/download/${record.id}`); | |||||
| }; | |||||
| const handleChange = (value) => { | |||||
| console.log(value); | |||||
| if (value) { | |||||
| getDatasetVersions({ version: value, dataset_id: locationParams.id }); | |||||
| setVersion(value); | |||||
| } else { | |||||
| setVersion(null); | |||||
| } | |||||
| }; | |||||
| const columns = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: 80, | |||||
| render(text, record, index) { | |||||
| return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>; | |||||
| }, | |||||
| // render: (text, record, index) => `${((curPage-1)*10)+(index+1)}`, | |||||
| }, | |||||
| { | |||||
| title: '文件名称', | |||||
| dataIndex: 'file_name', | |||||
| key: 'file_name', | |||||
| render: (text, record) => <a onClick={(e) => downloadAlone(e, record)}>{text}</a>, | |||||
| }, | |||||
| { | |||||
| title: '版本号', | |||||
| dataIndex: 'version', | |||||
| key: 'version', | |||||
| }, | |||||
| { | |||||
| title: '文件大小', | |||||
| dataIndex: 'file_size', | |||||
| key: 'file_size', | |||||
| }, | |||||
| { | |||||
| title: '更新时间', | |||||
| dataIndex: 'update_time', | |||||
| key: 'update_time', | |||||
| render: (text) => <span>{formatDate(text)}</span>, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'option', | |||||
| width: '100px', | |||||
| key: 'option', | |||||
| render: (_, record) => [ | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="download" | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| onClick={(e) => downloadAlone(e, record)} | |||||
| > | |||||
| 下载 | |||||
| </Button>, | |||||
| ], | |||||
| }, | |||||
| ]; | |||||
| const pageOption = useRef({ page: 1, size: 10 }); | |||||
| // 当前页面切换 | |||||
| const paginationChange = async (current, size) => { | |||||
| console.log('page', current, size); | |||||
| pageOption.current = { | |||||
| page: current, | |||||
| size: size, | |||||
| }; | |||||
| // getList() | |||||
| }; | |||||
| return ( | |||||
| <div className={Styles.datasetBox}> | |||||
| <div className={Styles.datasetIntroTopBox}> | |||||
| <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | |||||
| <div className={Styles.smallTagBox}> | |||||
| <div className={Styles.tagItem}>数据集 id:{datasetDetailObj.id}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.dataset_type_name || '...'}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.dataset_tag_name || '...'}</div> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.datasetIntroCneterBox}> | |||||
| <Tabs activeKey={activeTabKey} onChange={(key) => setActiveTabKey(key)}> | |||||
| <TabPane tab="数据集简介" key="1"> | |||||
| <div className={Styles.datasetIntroTitle}>简介</div> | |||||
| <div className={Styles.datasetIntroText}>{datasetDetailObj.description}</div> | |||||
| </TabPane> | |||||
| <TabPane tab="数据集文件/版本" key="2"> | |||||
| <div className={Styles.dataListBox}> | |||||
| <div>数据集文件列表</div> | |||||
| <div className={Styles.dataButtonList}> | |||||
| <div | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||||
| > | |||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ | |||||
| width: 160, | |||||
| }} | |||||
| allowClear | |||||
| value={version} | |||||
| onChange={handleChange} | |||||
| options={versionList} | |||||
| /> | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| onClick={showModal} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 创建新版本 | |||||
| </Button> | |||||
| </div> | |||||
| <div | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||||
| > | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={deleteDataset} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| )} | |||||
| <Button | |||||
| type="default" | |||||
| disabled={!version} | |||||
| className={Styles.plusButton} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={handleExport} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| </div> | |||||
| </div> | |||||
| <div style={{ marginBottom: '10px', fontSize: '14px' }}> | |||||
| {wordList.length > 0 && wordList[0].description | |||||
| ? '版本描述:' + wordList[0].description | |||||
| : null} | |||||
| </div> | |||||
| <Table columns={columns} dataSource={wordList} pagination={false} rowKey="id" /> | |||||
| </div> | |||||
| </TabPane> | |||||
| </Tabs> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default Dataset; | |||||
| @@ -1,82 +0,0 @@ | |||||
| .datasetIntroTopBox { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 25px 30px; | |||||
| background-image: url(/assets/images/dataset-back.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| .smallTagBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #1664ff; | |||||
| font-size: 14px; | |||||
| .tagItem { | |||||
| margin-right: 20px; | |||||
| padding: 4px 10px; | |||||
| background: rgba(22, 100, 255, 0.1); | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .dataListBox { | |||||
| padding: 20px 30px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| font-family: alibaba; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| .dataButtonList { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin: 24px 0 30px 0; | |||||
| color: #575757; | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| .datasetIntroCneterBox { | |||||
| height: 77vh; | |||||
| padding: 20px 30px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| .datasetIntroTitle { | |||||
| margin: 37px 0 10px 0; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| } | |||||
| .datasetIntroText { | |||||
| margin-bottom: 30px; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| } | |||||
| .datasetBox { | |||||
| background: #f9fafb; | |||||
| :global { | |||||
| .ant-tabs-top > .ant-tabs-nav { | |||||
| margin: 0; | |||||
| } | |||||
| .ant-pagination { | |||||
| text-align: right; | |||||
| } | |||||
| } | |||||
| } | |||||
| .plusButton { | |||||
| margin: 0 18px 0 20px; | |||||
| } | |||||
| .tipContent { | |||||
| margin-top: 5px; | |||||
| color: #c73131; | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| import ResourceIntro from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import { ResourceType } from '@/pages/Dataset/config'; | |||||
| function DatasetIntro() { | |||||
| return <ResourceIntro resourceType={ResourceType.Dataset} />; | |||||
| } | |||||
| export default DatasetIntro; | |||||
| @@ -1,3 +1,5 @@ | |||||
| import ParameterInput from '@/components/ParameterInput'; | |||||
| import ParameterSelect from '@/components/ParameterSelect'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | import { useComputingResource } from '@/hooks/resource'; | ||||
| import { PipelineNodeModelSerialize } from '@/types'; | import { PipelineNodeModelSerialize } from '@/types'; | ||||
| @@ -122,15 +124,8 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| <TextArea disabled /> | <TextArea disabled /> | ||||
| </Form.Item> | </Form.Item> | ||||
| {controlStrategyList.map((item) => ( | {controlStrategyList.map((item) => ( | ||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['control_strategy', item.key]} | |||||
| label={item.value.label} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.showValue || e.value }; | |||||
| }} | |||||
| > | |||||
| <Input disabled /> | |||||
| <Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}> | |||||
| <ParameterInput disabled /> | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| <div className={styles['experiment-parameter__title']}> | <div className={styles['experiment-parameter__title']}> | ||||
| @@ -142,11 +137,12 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| name={['in_parameters', item.key]} | name={['in_parameters', item.key]} | ||||
| label={item.value.label + '(' + item.key + ')'} | label={item.value.label + '(' + item.key + ')'} | ||||
| rules={[{ required: item.value.require ? true : false }]} | rules={[{ required: item.value.require ? true : false }]} | ||||
| getValueProps={(e) => { | |||||
| return { value: e.showValue || e.value }; | |||||
| }} | |||||
| > | > | ||||
| <Input disabled /> | |||||
| {item.value.type === 'select' ? ( | |||||
| <ParameterSelect disabled /> | |||||
| ) : ( | |||||
| <ParameterInput disabled /> | |||||
| )} | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| <div className={styles['experiment-parameter__title']}> | <div className={styles['experiment-parameter__title']}> | ||||
| @@ -158,11 +154,8 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| name={['out_parameters', item.key]} | name={['out_parameters', item.key]} | ||||
| label={item.value.label + '(' + item.key + ')'} | label={item.value.label + '(' + item.key + ')'} | ||||
| rules={[{ required: item.value.require ? true : false }]} | rules={[{ required: item.value.require ? true : false }]} | ||||
| getValueProps={(e) => { | |||||
| return { value: e.showValue || e.value }; | |||||
| }} | |||||
| > | > | ||||
| <Input disabled /> | |||||
| <ParameterInput disabled /> | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| </Form> | </Form> | ||||
| @@ -35,4 +35,9 @@ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| &__empty { | |||||
| margin-top: 10px; | |||||
| text-align: center; | |||||
| } | |||||
| } | } | ||||
| @@ -23,34 +23,38 @@ function ExperimentResult({ results }: ExperimentResultProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-result']}> | <div className={styles['experiment-result']}> | ||||
| <div className={styles['experiment-result__content']}> | <div className={styles['experiment-result__content']}> | ||||
| {results?.map((item) => ( | |||||
| <div key={item.name} className={styles['experiment-result__item']}> | |||||
| <div className={styles['experiment-result__item__name']}> | |||||
| <span>{item.name}</span> | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| onClick={() => { | |||||
| exportResult(item.path); | |||||
| }} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| {/* <a style={{ marginRight: '10px' }}>导出到模型库</a> | |||||
| {results && results.length > 0 ? ( | |||||
| results.map((item) => ( | |||||
| <div key={item.name} className={styles['experiment-result__item']}> | |||||
| <div className={styles['experiment-result__item__name']}> | |||||
| <span>{item.name}</span> | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| onClick={() => { | |||||
| exportResult(item.path); | |||||
| }} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| {/* <a style={{ marginRight: '10px' }}>导出到模型库</a> | |||||
| <a style={{ marginRight: '10px' }}>导出到数据集</a> */} | <a style={{ marginRight: '10px' }}>导出到数据集</a> */} | ||||
| </div> | |||||
| <div style={{ margin: '15px 0' }} className={styles['experiment-result__item__file']}> | |||||
| <span>文件名称</span> | |||||
| <span>文件大小</span> | |||||
| </div> | |||||
| {item.value?.map((ele) => ( | |||||
| <div className={styles['experiment-result__item__file']} key={ele.name}> | |||||
| <span>{ele.name}</span> | |||||
| <span>{ele.size}</span> | |||||
| </div> | </div> | ||||
| ))} | |||||
| </div> | |||||
| ))} | |||||
| <div style={{ margin: '15px 0' }} className={styles['experiment-result__item__file']}> | |||||
| <span>文件名称</span> | |||||
| <span>文件大小</span> | |||||
| </div> | |||||
| {item.value?.map((ele) => ( | |||||
| <div className={styles['experiment-result__item__file']} key={ele.name}> | |||||
| <span>{ele.name}</span> | |||||
| <span>{ele.size}</span> | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| )) | |||||
| ) : ( | |||||
| <div className={styles['experiment-result__empty']}>暂无结果</div> | |||||
| )} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -21,8 +21,13 @@ | |||||
| color: white; | color: white; | ||||
| font-size: 14px; | font-size: 14px; | ||||
| white-space: pre-line; | white-space: pre-line; | ||||
| text-align: left; | |||||
| word-break: break-all; | word-break: break-all; | ||||
| background: #19253b; | background: #19253b; | ||||
| &--empty { | |||||
| text-align: center; | |||||
| } | |||||
| } | } | ||||
| &__more-button { | &__more-button { | ||||
| @@ -10,6 +10,7 @@ import { ExperimentLog } from '@/pages/Experiment/training/props'; | |||||
| import { getExperimentPodsLog } from '@/services/experiment/index.js'; | import { getExperimentPodsLog } from '@/services/experiment/index.js'; | ||||
| import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -22,6 +23,21 @@ type Log = { | |||||
| log_content: string; // 日志内容 | log_content: string; // 日志内容 | ||||
| }; | }; | ||||
| // 滚动到底部 | |||||
| const scrollToBottom = (smooth: boolean = true) => { | |||||
| const element = document.getElementsByClassName('ant-tabs-content-holder')?.[0]; | |||||
| if (element) { | |||||
| if (smooth) { | |||||
| element.scrollTo({ | |||||
| top: element.scrollHeight, | |||||
| behavior: 'smooth', | |||||
| }); | |||||
| } else { | |||||
| element.scrollTo({ top: element.scrollHeight }); | |||||
| } | |||||
| } | |||||
| }; | |||||
| function LogGroup({ | function LogGroup({ | ||||
| log_type = 'normal', | log_type = 'normal', | ||||
| pod_name = '', | pod_name = '', | ||||
| @@ -32,8 +48,11 @@ function LogGroup({ | |||||
| const [collapse, setCollapse] = useState(true); | const [collapse, setCollapse] = useState(true); | ||||
| const [logList, setLogList, logListRef] = useStateRef<Log[]>([]); | const [logList, setLogList, logListRef] = useStateRef<Log[]>([]); | ||||
| const [completed, setCompleted] = useState(false); | const [completed, setCompleted] = useState(false); | ||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |||||
| const [isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| scrollToBottom(false); | |||||
| if (status === ExperimentStatus.Running) { | if (status === ExperimentStatus.Running) { | ||||
| const timerId = setInterval(() => { | const timerId = setInterval(() => { | ||||
| requestExperimentPodsLog(); | requestExperimentPodsLog(); | ||||
| @@ -44,6 +63,24 @@ function LogGroup({ | |||||
| } | } | ||||
| }, []); | }, []); | ||||
| useEffect(() => { | |||||
| const mouseDown = () => { | |||||
| setIsMouseDown(true); | |||||
| }; | |||||
| const mouseUp = () => { | |||||
| setIsMouseDown(false); | |||||
| }; | |||||
| document.addEventListener('mousedown', mouseDown); | |||||
| document.addEventListener('mouseup', mouseUp); | |||||
| return () => { | |||||
| document.removeEventListener('mousedown', mouseDown); | |||||
| document.removeEventListener('mouseup', mouseUp); | |||||
| }; | |||||
| }, []); | |||||
| // 请求日志 | // 请求日志 | ||||
| const requestExperimentPodsLog = async () => { | const requestExperimentPodsLog = async () => { | ||||
| const list = logListRef.current; | const list = logListRef.current; | ||||
| @@ -54,8 +91,14 @@ function LogGroup({ | |||||
| }; | }; | ||||
| const res = await getExperimentPodsLog(params); | const res = await getExperimentPodsLog(params); | ||||
| const { log_detail } = res.data; | const { log_detail } = res.data; | ||||
| if (log_detail && log_detail.log_content) { | |||||
| if (log_detail) { | |||||
| setLogList((oldList) => oldList.concat(log_detail)); | setLogList((oldList) => oldList.concat(log_detail)); | ||||
| if (!isMouseDownRef.current && log_detail.log_content) { | |||||
| setTimeout(() => { | |||||
| scrollToBottom(); | |||||
| }, 100); | |||||
| } | |||||
| } else { | } else { | ||||
| setCompleted(true); | setCompleted(true); | ||||
| } | } | ||||
| @@ -96,7 +139,15 @@ function LogGroup({ | |||||
| {collapse ? <DownOutlined /> : <UpOutlined />} | {collapse ? <DownOutlined /> : <UpOutlined />} | ||||
| </div> | </div> | ||||
| )} | )} | ||||
| {showLog && <div className={styles['log-group__detail']}>{logText}</div>} | |||||
| {showLog && ( | |||||
| <div | |||||
| className={classNames(styles['log-group__detail'], { | |||||
| [styles['log-group__detail--empty']]: !logText, | |||||
| })} | |||||
| > | |||||
| {logText ? logText : '暂无日志'} | |||||
| </div> | |||||
| )} | |||||
| <div className={styles['log-group__more-button']}> | <div className={styles['log-group__more-button']}> | ||||
| {showMoreBtn && ( | {showMoreBtn && ( | ||||
| <Button | <Button | ||||
| @@ -1,3 +1,13 @@ | |||||
| .log-list { | .log-list { | ||||
| padding: 8px; | padding: 8px; | ||||
| &__empty { | |||||
| padding: 15px; | |||||
| color: white; | |||||
| font-size: 14px; | |||||
| white-space: pre-line; | |||||
| text-align: center; | |||||
| word-break: break-all; | |||||
| background: #19253b; | |||||
| } | |||||
| } | } | ||||
| @@ -11,9 +11,11 @@ type LogListProps = { | |||||
| function LogList({ list = [], status }: LogListProps) { | function LogList({ list = [], status }: LogListProps) { | ||||
| return ( | return ( | ||||
| <div className={styles['log-list']}> | <div className={styles['log-list']}> | ||||
| {list.map((v) => ( | |||||
| <LogGroup key={v.pod_name} {...v} status={status} /> | |||||
| ))} | |||||
| {list.length > 0 ? ( | |||||
| list.map((v) => <LogGroup key={v.pod_name} {...v} status={status} />) | |||||
| ) : ( | |||||
| <div className={styles['log-list__empty']}>暂无日志</div> | |||||
| )} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -8,15 +8,15 @@ | |||||
| font-size: 15px; | font-size: 15px; | ||||
| &--running { | &--running { | ||||
| color: #6ac21d; | |||||
| color: @success-color; | |||||
| } | } | ||||
| &--failed { | &--failed { | ||||
| color: #df6d6d; | |||||
| color: @error-color; | |||||
| } | } | ||||
| } | } | ||||
| &__icon { | &__icon { | ||||
| width: 14px; | width: 14px; | ||||
| color: #6ac21d; | |||||
| color: @success-color; | |||||
| cursor: pointer; | cursor: pointer; | ||||
| & + & { | & + & { | ||||
| @@ -11,8 +11,8 @@ | |||||
| margin-bottom: 15px; | margin-bottom: 15px; | ||||
| &_label { | &_label { | ||||
| width: 120px; | |||||
| color: #1d1d20; | |||||
| width: 180px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | font-size: 15px; | ||||
| } | } | ||||
| &_value { | &_value { | ||||
| @@ -20,8 +20,8 @@ | |||||
| width: 100px; | width: 100px; | ||||
| margin-left: 15px; | margin-left: 15px; | ||||
| padding: 10px 20px; | padding: 10px 20px; | ||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| line-height: 20px; | line-height: 20px; | ||||
| background: #f6f6f6; | background: #f6f6f6; | ||||
| border: 1px solid #e0e0e1; | border: 1px solid #e0e0e1; | ||||
| @@ -18,7 +18,7 @@ import themes from '@/styles/theme.less'; | |||||
| import { elapsedTime, formatDate } from '@/utils/date'; | import { elapsedTime, formatDate } from '@/utils/date'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { App, Button, ConfigProvider, Space, Table } from 'antd'; | |||||
| import { App, Button, ConfigProvider, Space, Table, Tooltip } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| @@ -84,7 +84,6 @@ function Experiment() { | |||||
| // 获取实验实例 | // 获取实验实例 | ||||
| const getQueryByExperiment = (val) => { | const getQueryByExperiment = (val) => { | ||||
| getQueryByExperimentId(val).then((ret) => { | getQueryByExperimentId(val).then((ret) => { | ||||
| console.log(val); | |||||
| setExpandedRowKeys(val); | setExpandedRowKeys(val); | ||||
| if (ret && ret.data && ret.data.length > 0) { | if (ret && ret.data && ret.data.length > 0) { | ||||
| try { | try { | ||||
| @@ -162,7 +161,6 @@ function Experiment() { | |||||
| }; | }; | ||||
| const expandChange = (e, record) => { | const expandChange = (e, record) => { | ||||
| clearExperimentInTimers(); | clearExperimentInTimers(); | ||||
| console.log(e, record); | |||||
| if (record.id === expandedRowKeys) { | if (record.id === expandedRowKeys) { | ||||
| setExpandedRowKeys(null); | setExpandedRowKeys(null); | ||||
| } else { | } else { | ||||
| @@ -238,7 +236,6 @@ function Experiment() { | |||||
| }; | }; | ||||
| // 当前页面切换 | // 当前页面切换 | ||||
| const paginationChange = async (current, size) => { | const paginationChange = async (current, size) => { | ||||
| console.log('page', current, size); | |||||
| pageOption.current = { | pageOption.current = { | ||||
| page: current, | page: current, | ||||
| size: size, | size: size, | ||||
| @@ -279,14 +276,14 @@ function Experiment() { | |||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| render: (text) => <div>{text}</div>, | render: (text) => <div>{text}</div>, | ||||
| width: '20%', | |||||
| width: '16%', | |||||
| }, | }, | ||||
| { | { | ||||
| title: '关联流水线名称', | title: '关联流水线名称', | ||||
| dataIndex: 'workflow_name', | dataIndex: 'workflow_name', | ||||
| key: 'workflow_name', | key: 'workflow_name', | ||||
| render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>, | render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>, | ||||
| width: '20%', | |||||
| width: '16%', | |||||
| }, | }, | ||||
| { | { | ||||
| title: '实验描述', | title: '实验描述', | ||||
| @@ -443,13 +440,17 @@ function Experiment() { | |||||
| <div style={{ width: '50%' }}> | <div style={{ width: '50%' }}> | ||||
| {elapsedTime(item.create_time, item.finish_time)} | {elapsedTime(item.create_time, item.finish_time)} | ||||
| </div> | </div> | ||||
| <div style={{ width: '50%' }}>{formatDate(item.create_time)}</div> | |||||
| <div style={{ width: '50%' }} className={Styles.startTime}> | |||||
| <Tooltip title={formatDate(item.create_time)}> | |||||
| <span>{formatDate(item.create_time)}</span> | |||||
| </Tooltip> | |||||
| </div> | |||||
| </div> | </div> | ||||
| <div className={Styles.statusBox}> | <div className={Styles.statusBox}> | ||||
| <img | <img | ||||
| style={{ width: '17px', marginRight: '7px' }} | style={{ width: '17px', marginRight: '7px' }} | ||||
| src={experimentStatusInfo[item.status]?.icon} | src={experimentStatusInfo[item.status]?.icon} | ||||
| />{' '} | |||||
| /> | |||||
| <span | <span | ||||
| style={{ color: experimentStatusInfo[item.status]?.color }} | style={{ color: experimentStatusInfo[item.status]?.color }} | ||||
| className={Styles.statusIcon} | className={Styles.statusIcon} | ||||
| @@ -36,17 +36,21 @@ | |||||
| } | } | ||||
| .index { | .index { | ||||
| width: calc((100% + 32px + 33px) / 5); | |||||
| width: calc((100% + 32px + 33px) / 6.25); | |||||
| } | } | ||||
| .tensorBoard { | .tensorBoard { | ||||
| width: calc((100% + 32px + 33px) / 5); | |||||
| width: calc((100% + 32px + 33px) / 6.25); | |||||
| } | } | ||||
| .description { | .description { | ||||
| display: flex; | display: flex; | ||||
| flex: 1; | flex: 1; | ||||
| align-items: center; | align-items: center; | ||||
| .startTime { | |||||
| .singleLine(); | |||||
| } | |||||
| } | } | ||||
| .status { | .status { | ||||
| @@ -80,8 +84,9 @@ | |||||
| .statusBox:hover .statusIcon { | .statusBox:hover .statusIcon { | ||||
| visibility: visible; | visibility: visible; | ||||
| } | } | ||||
| .experimentBox { | .experimentBox { | ||||
| height: calc(100% - 20px); | |||||
| height: 100%; | |||||
| .experimentTable { | .experimentTable { | ||||
| height: calc(100% - 60px); | height: calc(100% - 60px); | ||||
| :global { | :global { | ||||
| @@ -1,3 +1,5 @@ | |||||
| import themes from '@/styles/theme.less'; | |||||
| export interface StatusInfo { | export interface StatusInfo { | ||||
| label: string; | label: string; | ||||
| color: string; | color: string; | ||||
| @@ -18,42 +20,42 @@ export enum ExperimentStatus { | |||||
| export const experimentStatusInfo: Record<ExperimentStatus, StatusInfo | undefined> = { | export const experimentStatusInfo: Record<ExperimentStatus, StatusInfo | undefined> = { | ||||
| Running: { | Running: { | ||||
| label: '运行中', | label: '运行中', | ||||
| color: '#1664ff', | |||||
| color: themes.primaryColor, | |||||
| icon: '/assets/images/running-icon.png', | icon: '/assets/images/running-icon.png', | ||||
| }, | }, | ||||
| Succeeded: { | Succeeded: { | ||||
| label: '成功', | label: '成功', | ||||
| color: '#63a728', | |||||
| color: themes.successColor, | |||||
| icon: '/assets/images/success-icon.png', | icon: '/assets/images/success-icon.png', | ||||
| }, | }, | ||||
| Pending: { | Pending: { | ||||
| label: '等待中', | label: '等待中', | ||||
| color: '#f981eb', | |||||
| color: themes.pendingColor, | |||||
| icon: '/assets/images/pending-icon.png', | icon: '/assets/images/pending-icon.png', | ||||
| }, | }, | ||||
| Failed: { | Failed: { | ||||
| label: '失败', | label: '失败', | ||||
| color: '#c73131', | |||||
| color: themes.errorColor, | |||||
| icon: '/assets/images/fail-icon.png', | icon: '/assets/images/fail-icon.png', | ||||
| }, | }, | ||||
| Error: { | Error: { | ||||
| label: '错误', | label: '错误', | ||||
| color: '#c73131', | |||||
| color: themes.errorColor, | |||||
| icon: '/assets/images/fail-icon.png', | icon: '/assets/images/fail-icon.png', | ||||
| }, | }, | ||||
| Terminated: { | Terminated: { | ||||
| label: '终止', | label: '终止', | ||||
| color: '#8a8a8a', | |||||
| color: themes.abortColor, | |||||
| icon: '/assets/images/omitted-icon.png', | icon: '/assets/images/omitted-icon.png', | ||||
| }, | }, | ||||
| Skipped: { | Skipped: { | ||||
| label: '未执行', | label: '未执行', | ||||
| color: '#8a8a8a', | |||||
| color: themes.abortColor, | |||||
| icon: '/assets/images/omitted-icon.png', | icon: '/assets/images/omitted-icon.png', | ||||
| }, | }, | ||||
| Omitted: { | Omitted: { | ||||
| label: '未执行', | label: '未执行', | ||||
| color: '#8a8a8a', | |||||
| color: themes.abortColor, | |||||
| icon: '/assets/images/omitted-icon.png', | icon: '/assets/images/omitted-icon.png', | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -109,7 +109,8 @@ const Props = forwardRef((_, ref) => { | |||||
| // 获取实验日志和实验结果 | // 获取实验日志和实验结果 | ||||
| setExperimentLogList([]); | setExperimentLogList([]); | ||||
| setExperimentResults([]); | setExperimentResults([]); | ||||
| if (e.item && e.item.getModel()) { | |||||
| // 如果已经运行到了 | |||||
| if (e.item?.getModel()?.component_id) { | |||||
| const model = e.item.getModel(); | const model = e.item.getModel(); | ||||
| const start_time = dayjs(model.experimentStartTime).valueOf() * 1.0e6; | const start_time = dayjs(model.experimentStartTime).valueOf() * 1.0e6; | ||||
| const params = { | const params = { | ||||
| @@ -1,5 +1,5 @@ | |||||
| import ResourcePage from '@/pages/Dataset/components/ResourcePage'; | import ResourcePage from '@/pages/Dataset/components/ResourcePage'; | ||||
| import { ResourceType } from '@/pages/Dataset/types'; | |||||
| import { ResourceType } from '@/pages/Dataset/config'; | |||||
| const ModelPage = () => { | const ModelPage = () => { | ||||
| return <ResourcePage resourceType={ResourceType.Model} />; | return <ResourcePage resourceType={ResourceType.Model} />; | ||||
| @@ -1,262 +0,0 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | |||||
| import { ResourceType } from '@/pages/Dataset/types'; | |||||
| import { | |||||
| deleteModelVersion, | |||||
| getModelById, | |||||
| getModelVersionIdList, | |||||
| getModelVersionsById, | |||||
| } from '@/services/dataset/index.js'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { App, Button, Input, Select, Table, Tabs } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import Styles from './intro.less'; | |||||
| const { Search } = Input; | |||||
| const { TabPane } = Tabs; | |||||
| const Dataset = () => { | |||||
| const [formList, setFormList] = useState([]); | |||||
| const [datasetDetailObj, setDatasetDetailObj] = useState({}); | |||||
| const [version, setVersion] = useState(null); | |||||
| const [versionList, setVersionList] = useState([]); | |||||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||||
| const [searchParams] = useSearchParams(); | |||||
| const [wordList, setWordList] = useState([]); | |||||
| const { message } = App.useApp(); | |||||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||||
| const getModelByDetail = () => { | |||||
| getModelById(locationParams.id).then((ret) => { | |||||
| console.log(ret); | |||||
| setDatasetDetailObj(ret.data); | |||||
| }); | |||||
| }; | |||||
| const getModelVersionsList = () => { | |||||
| getModelVersionsById(locationParams.id).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret && ret.data && ret.data.length > 0) { | |||||
| setVersionList( | |||||
| ret.data.map((item) => { | |||||
| return { | |||||
| label: item, | |||||
| value: item, | |||||
| }; | |||||
| }), | |||||
| ); | |||||
| setVersion(ret.data[0]); | |||||
| getModelVersions({ version: ret.data[0], models_id: locationParams.id }); | |||||
| } else { | |||||
| setVersion(null); | |||||
| setWordList([]); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| getModelByDetail(); | |||||
| getModelVersionsList(); | |||||
| return () => {}; | |||||
| }, []); | |||||
| const showModal = () => { | |||||
| const { close } = openAntdModal(AddVersionModal, { | |||||
| resourceType: ResourceType.Model, | |||||
| resourceId: locationParams.id, | |||||
| initialName: datasetDetailObj.name, | |||||
| onOk: () => { | |||||
| getModelVersionsList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const deleteDataset = () => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| deleteModelVersion({ models_id: locationParams.id, version }).then((ret) => { | |||||
| getModelVersionsList(); | |||||
| message.success('删除成功'); | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const getModelVersions = (params) => { | |||||
| getModelVersionIdList(params).then((ret) => { | |||||
| setWordList(ret?.data?.content ?? []); | |||||
| }); | |||||
| }; | |||||
| const handleExport = async () => { | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/models/downloadAllFiles`, { models_id: locationParams.id, version }); | |||||
| }; | |||||
| const downloadAlone = (e, record) => { | |||||
| console.log(record); | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/models/download_model/${record.id}`); | |||||
| }; | |||||
| const handleChange = (value) => { | |||||
| console.log(value); | |||||
| if (value) { | |||||
| getModelVersions({ version: value, models_id: locationParams.id }); | |||||
| setVersion(value); | |||||
| } else { | |||||
| setVersion(''); | |||||
| } | |||||
| }; | |||||
| const columns = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: 80, | |||||
| render(text, record, index) { | |||||
| return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>; | |||||
| }, | |||||
| // render: (text, record, index) => `${((curPage-1)*10)+(index+1)}`, | |||||
| }, | |||||
| { | |||||
| title: '文件名称', | |||||
| dataIndex: 'file_name', | |||||
| key: 'file_name', | |||||
| render: (text, record) => <a onClick={(e) => downloadAlone(e, record)}>{text}</a>, | |||||
| }, | |||||
| { | |||||
| title: '版本号', | |||||
| dataIndex: 'version', | |||||
| key: 'version', | |||||
| }, | |||||
| { | |||||
| title: '文件大小', | |||||
| dataIndex: 'file_size', | |||||
| key: 'file_size', | |||||
| }, | |||||
| { | |||||
| title: '更新时间', | |||||
| dataIndex: 'update_time', | |||||
| key: 'update_time', | |||||
| render: (text) => <span>{formatDate(text)}</span>, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'option', | |||||
| width: '100px', | |||||
| key: 'option', | |||||
| render: (_, record) => [ | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="download" | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| onClick={(e) => downloadAlone(e, record)} | |||||
| > | |||||
| 下载 | |||||
| </Button>, | |||||
| ], | |||||
| }, | |||||
| ]; | |||||
| const pageOption = useRef({ page: 1, size: 10 }); | |||||
| // 当前页面切换 | |||||
| const paginationChange = async (current, size) => { | |||||
| console.log('page', current, size); | |||||
| pageOption.current = { | |||||
| page: current, | |||||
| size: size, | |||||
| }; | |||||
| // getList() | |||||
| }; | |||||
| return ( | |||||
| <div className={Styles.datasetBox}> | |||||
| <div className={Styles.datasetIntroTopBox}> | |||||
| <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | |||||
| <div className={Styles.smallTagBox}> | |||||
| <div className={Styles.tagItem}>模型 id:{datasetDetailObj.id}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.model_type_name || '...'}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.model_tag_name || '...'}</div> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.datasetIntroCneterBox}> | |||||
| <Tabs defaultActiveKey="1"> | |||||
| <TabPane tab="模型简介" key="1"> | |||||
| <div className={Styles.datasetIntroTitle}>简介</div> | |||||
| <div className={Styles.datasetIntroText}>{datasetDetailObj.description}</div> | |||||
| </TabPane> | |||||
| <TabPane tab="模型文件/版本" key="2"> | |||||
| <div className={Styles.dataListBox}> | |||||
| <div>模型列表</div> | |||||
| <div className={Styles.dataButtonList}> | |||||
| <div | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||||
| > | |||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ | |||||
| width: 160, | |||||
| }} | |||||
| value={version} | |||||
| allowClear | |||||
| onChange={handleChange} | |||||
| options={versionList} | |||||
| /> | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| onClick={showModal} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 创建新版本 | |||||
| </Button> | |||||
| </div> | |||||
| <div | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||||
| > | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={deleteDataset} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| )} | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| disabled={!version} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={handleExport} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| </div> | |||||
| </div> | |||||
| <div style={{ marginBottom: '10px', fontSize: '14px' }}> | |||||
| {wordList.length > 0 && wordList[0].description | |||||
| ? '版本描述:' + wordList[0].description | |||||
| : null} | |||||
| </div> | |||||
| <Table columns={columns} dataSource={wordList} pagination={false} rowKey="id" /> | |||||
| </div> | |||||
| </TabPane> | |||||
| </Tabs> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default Dataset; | |||||
| @@ -1,80 +0,0 @@ | |||||
| .datasetIntroTopBox { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 25px 30px; | |||||
| background-image: url(/assets/images/dataset-back.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| .smallTagBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #1664ff; | |||||
| font-size: 14px; | |||||
| .tagItem { | |||||
| margin-right: 20px; | |||||
| padding: 4px 10px; | |||||
| background: rgba(22, 100, 255, 0.1); | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .dataListBox { | |||||
| padding: 20px 30px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| .dataButtonList { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin: 24px 0 30px 0; | |||||
| color: #575757; | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| .datasetIntroCneterBox { | |||||
| height: 77vh; | |||||
| padding: 20px 30px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| .datasetIntroTitle { | |||||
| margin: 37px 0 10px 0; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| } | |||||
| .datasetIntroText { | |||||
| margin-bottom: 30px; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| } | |||||
| .datasetBox { | |||||
| font-family: 'Alibaba'; | |||||
| background: #f9fafb; | |||||
| :global { | |||||
| .ant-tabs-top > .ant-tabs-nav { | |||||
| margin: 0; | |||||
| } | |||||
| .ant-pagination { | |||||
| text-align: right; | |||||
| } | |||||
| } | |||||
| } | |||||
| .plusButton { | |||||
| margin: 0 18px 0 20px; | |||||
| } | |||||
| .tipContent { | |||||
| margin-top: 5px; | |||||
| color: #c73131; | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| import ResourceIntro from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import { ResourceType } from '@/pages/Dataset/config'; | |||||
| function ModelIntro() { | |||||
| return <ResourceIntro resourceType={ResourceType.Model} />; | |||||
| } | |||||
| export default ModelIntro; | |||||
| @@ -1,6 +1,16 @@ | |||||
| .model-deployment-info { | .model-deployment-info { | ||||
| height: 100%; | height: 100%; | ||||
| &__content { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 30px 30px 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| } | |||||
| &__basic { | &__basic { | ||||
| &__item { | &__item { | ||||
| display: flex; | display: flex; | ||||
| @@ -23,34 +33,13 @@ | |||||
| } | } | ||||
| } | } | ||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| &__guide { | |||||
| flex: 1; | |||||
| margin-top: 10px; | margin-top: 10px; | ||||
| padding: 30px 30px 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| } | |||||
| &__table { | |||||
| :global { | |||||
| .ant-table-wrapper { | |||||
| height: 100%; | |||||
| .ant-spin-nested-loading { | |||||
| height: 100%; | |||||
| } | |||||
| .ant-spin-container { | |||||
| height: 100%; | |||||
| } | |||||
| .ant-table { | |||||
| height: calc(100% - 74px); | |||||
| overflow: auto; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| padding: 10px; | |||||
| overflow-y: auto; | |||||
| color: white; | |||||
| white-space: pre-wrap; | |||||
| background-color: rgba(0, 0, 0, 0.85); | |||||
| } | } | ||||
| } | } | ||||
| @@ -8,48 +8,70 @@ import PageTitle from '@/components/PageTitle'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | import { useComputingResource } from '@/hooks/resource'; | ||||
| import { useSessionStorage } from '@/hooks/sessionStorage'; | import { useSessionStorage } from '@/hooks/sessionStorage'; | ||||
| import { getModelDeploymentDocsReq } from '@/services/modelDeployment'; | |||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { to } from '@/utils/promise'; | |||||
| import { modelDeploymentInfoKey } from '@/utils/sessionStorage'; | import { modelDeploymentInfoKey } from '@/utils/sessionStorage'; | ||||
| import { Col, Row, Tabs, type TabsProps } from 'antd'; | import { Col, Row, Tabs, type TabsProps } from 'antd'; | ||||
| import { pick } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell'; | import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell'; | ||||
| import { ModelDeploymentData } from '../types'; | import { ModelDeploymentData } from '../types'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export enum ModelDeploymentTabKey { | |||||
| Predict = 'Predict', | |||||
| Guide = 'Guide', | |||||
| Log = 'Log', | |||||
| } | |||||
| const tabItems = [ | const tabItems = [ | ||||
| { | { | ||||
| key: '1', | |||||
| key: ModelDeploymentTabKey.Predict, | |||||
| label: '预测', | label: '预测', | ||||
| icon: <KFIcon type="icon-yuce" />, | icon: <KFIcon type="icon-yuce" />, | ||||
| }, | }, | ||||
| { | { | ||||
| key: '2', | |||||
| key: ModelDeploymentTabKey.Guide, | |||||
| label: '调用指南', | label: '调用指南', | ||||
| icon: <KFIcon type="icon-tiaoyongzhinan" />, | icon: <KFIcon type="icon-tiaoyongzhinan" />, | ||||
| }, | }, | ||||
| { | { | ||||
| key: '3', | |||||
| key: ModelDeploymentTabKey.Log, | |||||
| label: '服务日志', | label: '服务日志', | ||||
| icon: <KFIcon type="icon-fuwurizhi" />, | icon: <KFIcon type="icon-fuwurizhi" />, | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| function ModelDeploymentInfo() { | function ModelDeploymentInfo() { | ||||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||||
| const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict); | |||||
| const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>( | const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>( | ||||
| modelDeploymentInfoKey, | modelDeploymentInfoKey, | ||||
| true, | true, | ||||
| undefined, | undefined, | ||||
| ); | ); | ||||
| const getResourceDescription = useComputingResource()[2]; | const getResourceDescription = useComputingResource()[2]; | ||||
| const [docs, setDocs] = useState(''); | |||||
| useEffect(() => {}, []); | |||||
| useEffect(() => { | |||||
| getModelDeploymentDocs(); | |||||
| }, [modelDeployementInfo]); | |||||
| // 获取模型部署文档 | |||||
| const getModelDeploymentDocs = async () => { | |||||
| const params = pick(modelDeployementInfo, ['service_id', 'service_ins_id']); | |||||
| const [res] = await to(getModelDeploymentDocsReq(params)); | |||||
| if (res && res.data && res.data.docs) { | |||||
| setDocs(JSON.stringify(res.data.docs, null, 2)); | |||||
| } | |||||
| }; | |||||
| // 切换 Tab,重置数据 | // 切换 Tab,重置数据 | ||||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | const hanleTabChange: TabsProps['onChange'] = (value) => { | ||||
| setActiveTab(value); | setActiveTab(value); | ||||
| }; | }; | ||||
| // 格式化环境变量 | |||||
| const formatEnvText = () => { | const formatEnvText = () => { | ||||
| if (!modelDeployementInfo?.env) { | if (!modelDeployementInfo?.env) { | ||||
| return '--'; | return '--'; | ||||
| @@ -64,128 +86,130 @@ function ModelDeploymentInfo() { | |||||
| <div className={styles['model-deployment-info']}> | <div className={styles['model-deployment-info']}> | ||||
| <PageTitle title="服务详情"></PageTitle> | <PageTitle title="服务详情"></PageTitle> | ||||
| <div className={styles['model-deployment-info__content']}> | <div className={styles['model-deployment-info__content']}> | ||||
| <div> | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['model-deployment-info__basic']}> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>服务名称:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.service_name ?? '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>镜 像:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.image ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>状 态:</div> | |||||
| <div className={styles['value']}> | |||||
| {ModelDeploymentStatusCell(modelDeployementInfo?.status)} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>模 型:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.model?.show_value ?? '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>创建人:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.created_by ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>挂载路径:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.model_path ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>API URL:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.url ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>副本数量:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.replicas ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>创建时间:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.create_time | |||||
| ? formatDate(modelDeployementInfo.create_time) | |||||
| : '--'} | |||||
| </div> | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['model-deployment-info__basic']}> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>服务名称:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.service_name ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>镜 像:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.image ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>状 态:</div> | |||||
| <div className={styles['value']}> | |||||
| {ModelDeploymentStatusCell(modelDeployementInfo?.status)} | |||||
| </div> | </div> | ||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>更新时间:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.update_time | |||||
| ? formatDate(modelDeployementInfo.update_time) | |||||
| : '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>模 型:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.model?.show_value ?? '--'} | |||||
| </div> | </div> | ||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>环境变量:</div> | |||||
| <div className={styles['value']}>{formatEnvText()}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>创建人:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.created_by ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>挂载路径:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.model_path ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>API URL:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.url ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>副本数量:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.replicas ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>创建时间:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.create_time | |||||
| ? formatDate(modelDeployementInfo.create_time) | |||||
| : '--'} | |||||
| </div> | </div> | ||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>资源规格:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.resource | |||||
| ? getResourceDescription(modelDeployementInfo.resource) | |||||
| : '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>更新时间:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.update_time | |||||
| ? formatDate(modelDeployementInfo.update_time) | |||||
| : '--'} | |||||
| </div> | </div> | ||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40}> | |||||
| <Col span={18}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>描 述:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.description ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>环境变量:</div> | |||||
| <div className={styles['value']}>{formatEnvText()}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>资源规格:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.resource | |||||
| ? getResourceDescription(modelDeployementInfo.resource) | |||||
| : '--'} | |||||
| </div> | </div> | ||||
| </Col> | |||||
| </Row> | |||||
| </div> | |||||
| <div style={{ marginTop: '20px' }}> | |||||
| <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40}> | |||||
| <Col span={18}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>描 述:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.description ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| </div> | </div> | ||||
| <Tabs | |||||
| activeKey={activeTab} | |||||
| style={{ marginTop: '20px' }} | |||||
| items={tabItems} | |||||
| onChange={hanleTabChange} | |||||
| /> | |||||
| {activeTab === ModelDeploymentTabKey.Guide && ( | |||||
| <div className={styles['model-deployment-info__guide']}>{docs}</div> | |||||
| )} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -0,0 +1,90 @@ | |||||
| import { getComponentAll } from '@/services/pipeline/index.js'; | |||||
| import { PipelineNodeModel } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Collapse, type CollapseProps } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import Styles from './index.less'; | |||||
| type ModelMenuData = { | |||||
| key: string; | |||||
| name: string; | |||||
| value: PipelineNodeModel[]; | |||||
| }; | |||||
| type ModelMenuProps = { | |||||
| onComponentDragEnd: ( | |||||
| data: PipelineNodeModel & { x: number; y: number; label: string; img: string }, | |||||
| ) => void; | |||||
| }; | |||||
| const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => { | |||||
| const [modelMenusList, setModelMenusList] = useState<ModelMenuData[]>([]); | |||||
| const [collapseItems, setCollapseItems] = useState<CollapseProps['items']>([]); | |||||
| useEffect(() => { | |||||
| getAllComponents(); | |||||
| }, []); | |||||
| // 获取所有组件 | |||||
| const getAllComponents = async () => { | |||||
| const [res] = await to(getComponentAll()); | |||||
| if (res && res.data) { | |||||
| const menus = res.data as ModelMenuData[]; | |||||
| setModelMenusList(menus); | |||||
| const items = menus.map((item) => { | |||||
| return { | |||||
| key: item.key, | |||||
| label: item.name, | |||||
| children: item.value.map((ele) => { | |||||
| return ( | |||||
| <div | |||||
| key={ele.id} | |||||
| draggable="true" | |||||
| onDragEnd={(e) => { | |||||
| dragEnd(e, ele); | |||||
| }} | |||||
| className={Styles.collapseItem} | |||||
| > | |||||
| {ele.icon_path && ( | |||||
| <img | |||||
| style={{ height: '16px', marginRight: '15px' }} | |||||
| src={`/assets/images/${ele.icon_path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| )} | |||||
| {ele.component_label} | |||||
| </div> | |||||
| ); | |||||
| }), | |||||
| }; | |||||
| }); | |||||
| setCollapseItems(items); | |||||
| } | |||||
| }; | |||||
| const dragEnd = (e: React.DragEvent<HTMLDivElement>, data: PipelineNodeModel) => { | |||||
| onComponentDragEnd({ | |||||
| ...data, | |||||
| x: e.clientX, | |||||
| y: e.clientY, | |||||
| label: data.component_label, | |||||
| img: `/assets/images/${data.icon_path}.png`, | |||||
| }); | |||||
| }; | |||||
| const defaultActiveKey = modelMenusList.map((item) => item.key + ''); | |||||
| return ( | |||||
| <div className={Styles.collapse}> | |||||
| <div className={Styles.modelMenusTitle}>组件库</div> | |||||
| {modelMenusList.length > 0 ? ( | |||||
| <Collapse | |||||
| collapsible="header" | |||||
| expandIconPosition="end" | |||||
| defaultActiveKey={defaultActiveKey} | |||||
| items={collapseItems} | |||||
| ></Collapse> | |||||
| ) : null} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default ModelMenu; | |||||
| @@ -20,9 +20,9 @@ export enum ResourceSelectorType { | |||||
| } | } | ||||
| export type MirrorVersion = { | export type MirrorVersion = { | ||||
| id: number; // 镜像版本id | |||||
| id: number; // 镜像版本 id | |||||
| status: MirrorVersionStatus; // 镜像版本状态 | status: MirrorVersionStatus; // 镜像版本状态 | ||||
| tag_name: string; // 镜像版本 | |||||
| tag_name: string; // 镜像版本 name | |||||
| url: string; // 镜像版本路径 | url: string; // 镜像版本路径 | ||||
| }; | }; | ||||
| @@ -39,12 +39,13 @@ export type SelectorTypeInfo = { | |||||
| tabItems: TabsProps['items']; | tabItems: TabsProps['items']; | ||||
| }; | }; | ||||
| // 获取镜像列表,为了兼容数据集和模型 | |||||
| // 获取镜像文件列表,为了兼容数据集和模型 | |||||
| const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => { | const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => { | ||||
| const index = version.indexOf('-'); | const index = version.indexOf('-'); | ||||
| const url = version.slice(index + 1); | const url = version.slice(index + 1); | ||||
| return Promise.resolve({ | return Promise.resolve({ | ||||
| data: { | data: { | ||||
| path: url, | |||||
| content: [ | content: [ | ||||
| { | { | ||||
| id: `${id}-${version}`, | id: `${id}-${version}`, | ||||
| @@ -15,26 +15,18 @@ import { MirrorVersion, ResourceSelectorType, selectorTypeConfig } from './confi | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export { ResourceSelectorType, selectorTypeConfig }; | export { ResourceSelectorType, selectorTypeConfig }; | ||||
| // 选择数据集和模型的返回类型 | |||||
| // 选择数据集\模型\镜像的返回类型 | |||||
| export type ResourceSelectorResponse = { | export type ResourceSelectorResponse = { | ||||
| id: number; // 数据集或者模型 id | |||||
| name: string; // 数据集或者模型 name | |||||
| version: string; // 数据集或者模型版本 | |||||
| path: string; // 数据集或者模型版本路径 | |||||
| id: number; // 数据集\模型\镜像 id | |||||
| name: string; // 数据集\模型\镜像 name | |||||
| version: string; // 数据集\模型\镜像版本 | |||||
| path: string; // 数据集\模型\镜像版本路径 | |||||
| activeTab: CommonTabKeys; // 是我的还是公开的 | activeTab: CommonTabKeys; // 是我的还是公开的 | ||||
| }; | }; | ||||
| export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| type: ResourceSelectorType; // 模型 | 数据集 | |||||
| defaultExpandedKeys?: React.Key[]; | |||||
| defaultCheckedKeys?: React.Key[]; | |||||
| defaultActiveTab?: CommonTabKeys; | |||||
| onOk?: (params: ResourceSelectorResponse | string | null) => void; | |||||
| } | |||||
| type ResourceGroup = { | type ResourceGroup = { | ||||
| id: number; // 数据集或者模型 id | |||||
| name: string; // 数据集或者模型 id | |||||
| id: number; // 数据集\模型\镜像 id | |||||
| name: string; // 数据集\模型\镜像 name | |||||
| }; | }; | ||||
| type ResourceFile = { | type ResourceFile = { | ||||
| @@ -42,9 +34,17 @@ type ResourceFile = { | |||||
| file_name: string; // 文件 name | file_name: string; // 文件 name | ||||
| }; | }; | ||||
| export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| type: ResourceSelectorType; // 数据集\模型\镜像 | |||||
| defaultExpandedKeys?: React.Key[]; | |||||
| defaultCheckedKeys?: React.Key[]; | |||||
| defaultActiveTab?: CommonTabKeys; | |||||
| onOk?: (params: ResourceSelectorResponse | null) => void; | |||||
| } | |||||
| type TreeRef = GetRef<typeof Tree<TreeDataNode>>; | type TreeRef = GetRef<typeof Tree<TreeDataNode>>; | ||||
| // list 转成 treeData | |||||
| // list 数据转成 treeData | |||||
| const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { | const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { | ||||
| return list.map((v) => ({ | return list.map((v) => ({ | ||||
| title: v.name, | title: v.name, | ||||
| @@ -54,7 +54,7 @@ const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { | |||||
| })); | })); | ||||
| }; | }; | ||||
| // 版本转成 treeData | |||||
| // 版本数据转成 treeData | |||||
| const convertVersionToTreeData = (parentId: number) => { | const convertVersionToTreeData = (parentId: number) => { | ||||
| return (item: string | MirrorVersion): TreeDataNode => { | return (item: string | MirrorVersion): TreeDataNode => { | ||||
| if (typeof item === 'string') { | if (typeof item === 'string') { | ||||
| @@ -88,7 +88,7 @@ const updateChildren = (parentId: number, children: TreeDataNode[]) => { | |||||
| }; | }; | ||||
| }; | }; | ||||
| // 得到数据集或者模型 id 和下属版本号 | |||||
| // 得到数据集\模型\镜像 id 和下属版本号 | |||||
| const getIdAndVersion = (versionKey: string) => { | const getIdAndVersion = (versionKey: string) => { | ||||
| const index = versionKey.indexOf('-'); | const index = versionKey.indexOf('-'); | ||||
| const id = Number(versionKey.slice(0, index)); | const id = Number(versionKey.slice(0, index)); | ||||
| @@ -137,12 +137,12 @@ function ResourceSelectorModal({ | |||||
| [originTreeData, searchText], | [originTreeData, searchText], | ||||
| ); | ); | ||||
| // 获取数据集或模型列表 | |||||
| // 获取数据集\模型\镜像列表 | |||||
| const getTreeData = async () => { | const getTreeData = async () => { | ||||
| const available_range = activeTab === CommonTabKeys.Private ? 0 : 1; | const available_range = activeTab === CommonTabKeys.Private ? 0 : 1; | ||||
| const params = { | const params = { | ||||
| page: 0, | page: 0, | ||||
| size: 200, | |||||
| size: 1000, | |||||
| [selectorTypeConfig[type].litReqParamKey]: available_range, | [selectorTypeConfig[type].litReqParamKey]: available_range, | ||||
| }; | }; | ||||
| const getListReq = selectorTypeConfig[type].getList; | const getListReq = selectorTypeConfig[type].getList; | ||||
| @@ -159,7 +159,7 @@ function ResourceSelectorModal({ | |||||
| } | } | ||||
| }; | }; | ||||
| // 获取数据集或模型版本列表 | |||||
| // 获取数据集\模型\镜像版本列表 | |||||
| const getVersions = async (parentId: number) => { | const getVersions = async (parentId: number) => { | ||||
| const getVersionsReq = selectorTypeConfig[type].getVersions; | const getVersionsReq = selectorTypeConfig[type].getVersions; | ||||
| const [res, error] = await to(getVersionsReq(parentId)); | const [res, error] = await to(getVersionsReq(parentId)); | ||||
| @@ -266,21 +266,17 @@ function ResourceSelectorModal({ | |||||
| // 提交 | // 提交 | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| if (checkedKeys.length > 0) { | if (checkedKeys.length > 0) { | ||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| onOk?.(files[0].file_name); | |||||
| } else { | |||||
| const last = checkedKeys[0] as string; | |||||
| const { id, version } = getIdAndVersion(last); | |||||
| const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string; | |||||
| const res = { | |||||
| id, | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| activeTab: activeTab as CommonTabKeys, | |||||
| }; | |||||
| onOk?.(res); | |||||
| } | |||||
| const last = checkedKeys[0] as string; | |||||
| const { id, version } = getIdAndVersion(last); | |||||
| const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string; | |||||
| const res = { | |||||
| id, | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| activeTab: activeTab as CommonTabKeys, | |||||
| }; | |||||
| onOk?.(res); | |||||
| } else { | } else { | ||||
| onOk?.(null); | onOk?.(null); | ||||
| } | } | ||||
| @@ -8,8 +8,8 @@ import { useEffect, useRef } from 'react'; | |||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||
| import { s8 } from '../../../utils'; | import { s8 } from '../../../utils'; | ||||
| import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | ||||
| import ModelMenu from '../components/ModelMenu'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import ModelMenus from './modelMenus'; | |||||
| import Props from './props'; | import Props from './props'; | ||||
| import { findAllParentNodes, findFirstDuplicate } from './utils'; | import { findAllParentNodes, findFirstDuplicate } from './utils'; | ||||
| @@ -51,8 +51,16 @@ const EditPipeline = () => { | |||||
| return item.id === val.id; | return item.id === val.id; | ||||
| }); | }); | ||||
| data.nodes[index] = val; | data.nodes[index] = val; | ||||
| const zoom = graph.getZoom(); | |||||
| // 在拉取新数据重新渲染页面之前先获取点(0, 0)在画布上的位置 | |||||
| const lastPoint = graph.getCanvasByPoint(0, 0); | |||||
| graph.changeData(data); | graph.changeData(data); | ||||
| graph.render(); | graph.render(); | ||||
| graph.zoomTo(zoom); | |||||
| // 获取重新渲染之后点(0, 0)在画布的位置 | |||||
| const newPoint = graph.getCanvasByPoint(0, 0); | |||||
| // 移动画布相对位移; | |||||
| graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y); | |||||
| } | } | ||||
| }; | }; | ||||
| const savePipeline = async (val) => { | const savePipeline = async (val) => { | ||||
| @@ -89,7 +97,7 @@ const EditPipeline = () => { | |||||
| closeParamsDrawer(); | closeParamsDrawer(); | ||||
| setTimeout(() => { | setTimeout(() => { | ||||
| if (val) { | if (val) { | ||||
| navgite({ pathname: `/pipeline` }); | |||||
| navgite({ pathname: `/pipeline/template` }); | |||||
| } | } | ||||
| }, 500); | }, 500); | ||||
| }); | }); | ||||
| @@ -432,7 +440,7 @@ const EditPipeline = () => { | |||||
| height: graphRef.current.clientHeight || '100%', | height: graphRef.current.clientHeight || '100%', | ||||
| animate: false, | animate: false, | ||||
| groupByTypes: false, | groupByTypes: false, | ||||
| fitView: false, | |||||
| fitView: true, | |||||
| plugins: [contextMenu], | plugins: [contextMenu], | ||||
| enabledStack: true, | enabledStack: true, | ||||
| modes: { | modes: { | ||||
| @@ -691,7 +699,7 @@ const EditPipeline = () => { | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={styles['pipeline-container']}> | <div className={styles['pipeline-container']}> | ||||
| <ModelMenus onParDragEnd={onDragEnd}></ModelMenus> | |||||
| <ModelMenu onComponentDragEnd={onDragEnd}></ModelMenu> | |||||
| <div className={styles['pipeline-container__workflow']}> | <div className={styles['pipeline-container__workflow']}> | ||||
| <div className={styles['pipeline-container__workflow__top']}> | <div className={styles['pipeline-container__workflow__top']}> | ||||
| <Button | <Button | ||||
| @@ -1,67 +0,0 @@ | |||||
| import { getComponentAll } from '@/services/pipeline/index.js'; | |||||
| import { Collapse } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import Styles from './modelMenus.less'; | |||||
| const ModelMenus = ({ onParDragEnd }) => { | |||||
| const [modelMenusList, setModelMenusList] = useState([]); | |||||
| useEffect(() => { | |||||
| getComponentAll().then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code === 200) { | |||||
| setModelMenusList(ret.data); | |||||
| } | |||||
| }); | |||||
| }, []); | |||||
| const dragEnd = (e, data) => { | |||||
| console.log(e, data); | |||||
| onParDragEnd({ | |||||
| ...data, | |||||
| x: e.clientX, | |||||
| y: e.clientY, | |||||
| label: data.component_label, | |||||
| img: `/assets/images/${data.icon_path}.png`, | |||||
| }); | |||||
| }; | |||||
| const { Panel } = Collapse; | |||||
| return ( | |||||
| <div className={Styles.collapse}> | |||||
| <div className={Styles.modelMenusTitle}>组件库</div> | |||||
| {modelMenusList && modelMenusList.length > 0 ? ( | |||||
| <Collapse | |||||
| collapsible="header" | |||||
| defaultActiveKey={modelMenusList.map((item) => item.key + '')} | |||||
| expandIconPosition="end" | |||||
| > | |||||
| {modelMenusList && modelMenusList.length > 0 | |||||
| ? modelMenusList.map((item) => ( | |||||
| <Panel header={<div>{item.name}</div>} key={item.key}> | |||||
| {item.value && item.value.length > 0 | |||||
| ? item.value.map((ele) => ( | |||||
| <div | |||||
| key={ele.id} | |||||
| draggable="true" | |||||
| onDragEnd={(e) => { | |||||
| dragEnd(e, ele); | |||||
| }} | |||||
| className={Styles.collapseItem} | |||||
| > | |||||
| {ele.icon_path && ( | |||||
| <img | |||||
| style={{ height: '16px', marginRight: '15px' }} | |||||
| src={`/assets/images/${ele.icon_path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| )} | |||||
| {ele.component_label} | |||||
| </div> | |||||
| )) | |||||
| : ''} | |||||
| </Panel> | |||||
| )) | |||||
| : ''} | |||||
| </Collapse> | |||||
| ) : null} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default ModelMenus; | |||||
| @@ -1,11 +1,19 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import ParameterInput from '@/components/ParameterInput'; | import ParameterInput from '@/components/ParameterInput'; | ||||
| import ParameterSelect from '@/components/ParameterSelect'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | import { useComputingResource } from '@/hooks/resource'; | ||||
| import { | |||||
| PipelineGlobalParam, | |||||
| PipelineNodeModelParameter, | |||||
| PipelineNodeModelSerialize, | |||||
| } from '@/types'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Button, Drawer, Form, Input, Select } from 'antd'; | |||||
| import { pick } from 'lodash'; | |||||
| import { INode } from '@antv/g6'; | |||||
| import { Button, Drawer, Form, Input, MenuProps, Select } from 'antd'; | |||||
| import { NamePath } from 'antd/es/form/interface'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | import { forwardRef, useImperativeHandle, useState } from 'react'; | ||||
| import PropsLabel from '../components/PropsLabel'; | import PropsLabel from '../components/PropsLabel'; | ||||
| import ResourceSelectorModal, { | import ResourceSelectorModal, { | ||||
| @@ -16,18 +24,22 @@ import styles from './props.less'; | |||||
| import { canInput, createMenuItems } from './utils'; | import { canInput, createMenuItems } from './utils'; | ||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| type PipelineNodeParameterProps = { | |||||
| onParentChange: (data: PipelineNodeModelSerialize) => void; | |||||
| }; | |||||
| const PipelineNodeParameter = forwardRef(({ onParentChange }: PipelineNodeParameterProps, ref) => { | |||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [stagingItem, setStagingItem] = useState({}); | |||||
| const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>( | |||||
| {} as PipelineNodeModelSerialize, | |||||
| ); | |||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const [selectedModel, setSelectedModel] = useState(undefined); // 选择的模型,为了再次打开时恢复原来的选择 | |||||
| const [selectedDataset, setSelectedDataset] = useState(undefined); // 选择的数据集,为了再次打开时恢复原来的选择 | |||||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); // 资源规模 | const [resourceStandardList, filterResourceStandard] = useComputingResource(); // 资源规模 | ||||
| const [menuItems, setMenuItems] = useState([]); | |||||
| const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | |||||
| const afterOpenChange = () => { | const afterOpenChange = () => { | ||||
| if (!open) { | if (!open) { | ||||
| console.log('zzzzz', form.getFieldsValue()); | |||||
| console.log('getFieldsValue', form.getFieldsValue()); | |||||
| const control_strategy = form.getFieldValue('control_strategy'); | const control_strategy = form.getFieldValue('control_strategy'); | ||||
| const in_parameters = form.getFieldValue('in_parameters'); | const in_parameters = form.getFieldValue('in_parameters'); | ||||
| const out_parameters = form.getFieldValue('out_parameters'); | const out_parameters = form.getFieldValue('out_parameters'); | ||||
| @@ -54,7 +66,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| return Promise.reject(propsError); | return Promise.reject(propsError); | ||||
| } | } | ||||
| }, | }, | ||||
| showDrawer(e, params, parentNodes) { | |||||
| showDrawer(e: any, params: PipelineGlobalParam[], parentNodes: INode[]) { | |||||
| if (e.item && e.item.getModel()) { | if (e.item && e.item.getModel()) { | ||||
| form.resetFields(); | form.resetFields(); | ||||
| const model = e.item.getModel(); | const model = e.item.getModel(); | ||||
| @@ -75,8 +87,6 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| } catch (error) { | } catch (error) { | ||||
| console.log(error); | console.log(error); | ||||
| } | } | ||||
| setSelectedModel(undefined); | |||||
| setSelectedDataset(undefined); | |||||
| setOpen(true); | setOpen(true); | ||||
| // 参数下拉菜单 | // 参数下拉菜单 | ||||
| @@ -89,50 +99,70 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| })); | })); | ||||
| // 选择数据集、模型、镜像 | // 选择数据集、模型、镜像 | ||||
| const selectResource = (name, item) => { | |||||
| let type; | |||||
| let resource; | |||||
| const selectResource = ( | |||||
| formItemName: NamePath, | |||||
| item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | |||||
| ) => { | |||||
| let type: ResourceSelectorType; | |||||
| switch (item.item_type) { | switch (item.item_type) { | ||||
| case 'dataset': | case 'dataset': | ||||
| type = ResourceSelectorType.Dataset; | type = ResourceSelectorType.Dataset; | ||||
| resource = selectedDataset; | |||||
| break; | break; | ||||
| case 'model': | case 'model': | ||||
| type = ResourceSelectorType.Model; | type = ResourceSelectorType.Model; | ||||
| resource = selectedModel; | |||||
| break; | break; | ||||
| default: | default: | ||||
| type = ResourceSelectorType.Mirror; | type = ResourceSelectorType.Mirror; | ||||
| break; | break; | ||||
| } | } | ||||
| const fieldValue = form.getFieldValue(formItemName); | |||||
| const activeTab = fieldValue?.activeTab as CommonTabKeys | undefined; | |||||
| const expandedKeys = Array.isArray(fieldValue?.expandedKeys) ? fieldValue?.expandedKeys : []; | |||||
| const checkedKeys = Array.isArray(fieldValue?.checkedKeys) ? fieldValue?.checkedKeys : []; | |||||
| const { close } = openAntdModal(ResourceSelectorModal, { | const { close } = openAntdModal(ResourceSelectorModal, { | ||||
| type, | type, | ||||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||||
| defaultActiveTab: resource?.activeTab, | |||||
| defaultExpandedKeys: expandedKeys, | |||||
| defaultCheckedKeys: checkedKeys, | |||||
| defaultActiveTab: activeTab, | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| form.setFieldValue(name, res); | |||||
| const { activeTab, id, version, path } = res; | |||||
| if (formItemName === 'image') { | |||||
| form.setFieldValue(formItemName, path); | |||||
| } else { | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: path, | |||||
| showValue: path, | |||||
| fromSelect: true, | |||||
| activeTab, | |||||
| expandedKeys: [id], | |||||
| checkedKeys: [`${id}-${version}`], | |||||
| }); | |||||
| } | |||||
| } else { | } else { | ||||
| const jsonObj = pick(res, ['id', 'version', 'path']); | |||||
| const { activeTab, id, name, version, path } = res; | |||||
| const jsonObj = { | |||||
| id, | |||||
| version, | |||||
| path, | |||||
| }; | |||||
| const value = JSON.stringify(jsonObj); | const value = JSON.stringify(jsonObj); | ||||
| const showValue = `${res.name}:${res.version}`; | |||||
| form.setFieldValue(name, { ...item, value, showValue, fromSelect: true }); | |||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(res); | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(res); | |||||
| } | |||||
| const showValue = `${name}:${version}`; | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value, | |||||
| showValue, | |||||
| fromSelect: true, | |||||
| activeTab, | |||||
| expandedKeys: [id], | |||||
| checkedKeys: [`${id}-${version}`], | |||||
| }); | |||||
| } | } | ||||
| } else { | } else { | ||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(undefined); | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(undefined); | |||||
| } | |||||
| form.setFieldValue(name, ''); | |||||
| form.setFieldValue(formItemName, ''); | |||||
| } | } | ||||
| close(); | close(); | ||||
| }, | }, | ||||
| @@ -140,9 +170,9 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| }; | }; | ||||
| // 获取选择数据集、模型后面按钮 icon | // 获取选择数据集、模型后面按钮 icon | ||||
| const getSelectBtnIcon = (item) => { | |||||
| const getSelectBtnIcon = (item: { item_type: string }) => { | |||||
| const type = item.item_type; | const type = item.item_type; | ||||
| let selectorType; | |||||
| let selectorType: ResourceSelectorType; | |||||
| if (type === 'dataset') { | if (type === 'dataset') { | ||||
| selectorType = ResourceSelectorType.Dataset; | selectorType = ResourceSelectorType.Dataset; | ||||
| } else if (type === 'model') { | } else if (type === 'model') { | ||||
| @@ -155,10 +185,33 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| }; | }; | ||||
| // 参数回填 | // 参数回填 | ||||
| const handleParameterClick = (name, value) => { | |||||
| const handleParameterClick = (name: NamePath, value: any) => { | |||||
| form.setFieldValue(name, value); | form.setFieldValue(name, value); | ||||
| }; | }; | ||||
| // form item label | |||||
| const getLabel = ( | |||||
| item: { key: string; value: PipelineNodeModelParameter }, | |||||
| namePrefix: string, | |||||
| ) => { | |||||
| return item.value.type === 'select' ? ( | |||||
| item.value.label + '(' + item.key + ')' | |||||
| ) : ( | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick([namePrefix, item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| // 控制策略 | // 控制策略 | ||||
| const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( | const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( | ||||
| ([key, value]) => ({ key, value }), | ([key, value]) => ({ key, value }), | ||||
| @@ -290,7 +343,6 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| ]} | ]} | ||||
| > | > | ||||
| <Select | <Select | ||||
| showSearch | |||||
| placeholder="请选择资源规格" | placeholder="请选择资源规格" | ||||
| filterOption={filterResourceStandard} | filterOption={filterResourceStandard} | ||||
| options={resourceStandardList} | options={resourceStandardList} | ||||
| @@ -298,6 +350,8 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| label: 'description', | label: 'description', | ||||
| value: 'standard', | value: 'standard', | ||||
| }} | }} | ||||
| showSearch | |||||
| allowClear | |||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| @@ -332,31 +386,9 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| <Form.Item | <Form.Item | ||||
| key={item.key} | key={item.key} | ||||
| name={['control_strategy', item.key]} | name={['control_strategy', item.key]} | ||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['control_strategy', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| // getValueProps={(e) => { | |||||
| // return { value: e.value }; | |||||
| // }} | |||||
| // getValueFromEvent={(e) => { | |||||
| // return { | |||||
| // ...item.value, | |||||
| // value: e.target.value, | |||||
| // }; | |||||
| // }} | |||||
| label={getLabel(item, 'control_strategy')} | |||||
| > | > | ||||
| <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> | |||||
| <ParameterInput allowClear></ParameterInput> | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| <div className={styles['pipeline-drawer__title']}> | <div className={styles['pipeline-drawer__title']}> | ||||
| @@ -365,20 +397,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| {inParametersList.map((item) => ( | {inParametersList.map((item) => ( | ||||
| <Form.Item | <Form.Item | ||||
| key={item.key} | key={item.key} | ||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['in_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| label={getLabel(item, 'in_parameters')} | |||||
| required={item.value.require ? true : false} | required={item.value.require ? true : false} | ||||
| > | > | ||||
| <div className={styles['pipeline-drawer__ref-row']}> | <div className={styles['pipeline-drawer__ref-row']}> | ||||
| @@ -387,11 +406,11 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| noStyle | noStyle | ||||
| rules={[{ required: item.value.require ? true : false }]} | rules={[{ required: item.value.require ? true : false }]} | ||||
| > | > | ||||
| <ParameterInput | |||||
| placeholder={item.value.placeholder} | |||||
| canInput={canInput(item.value)} | |||||
| allowClear | |||||
| ></ParameterInput> | |||||
| {item.value.type === 'select' ? ( | |||||
| <ParameterSelect /> | |||||
| ) : ( | |||||
| <ParameterInput canInput={canInput(item.value)} allowClear></ParameterInput> | |||||
| )} | |||||
| </Form.Item> | </Form.Item> | ||||
| {item.value.type === 'ref' && ( | {item.value.type === 'ref' && ( | ||||
| <Form.Item noStyle> | <Form.Item noStyle> | ||||
| @@ -416,32 +435,10 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| <Form.Item | <Form.Item | ||||
| key={item.key} | key={item.key} | ||||
| name={['out_parameters', item.key]} | name={['out_parameters', item.key]} | ||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['out_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| label={getLabel(item, 'out_parameters')} | |||||
| rules={[{ required: item.value.require ? true : false }]} | rules={[{ required: item.value.require ? true : false }]} | ||||
| // getValueProps={(e) => { | |||||
| // return { value: e.value }; | |||||
| // }} | |||||
| // getValueFromEvent={(e) => { | |||||
| // return { | |||||
| // ...item.value, | |||||
| // value: e.target.value, | |||||
| // }; | |||||
| // }} | |||||
| > | > | ||||
| <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> | |||||
| <ParameterInput allowClear></ParameterInput> | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| </Form> | </Form> | ||||
| @@ -449,4 +446,4 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| ); | ); | ||||
| }); | }); | ||||
| export default Props; | |||||
| export default PipelineNodeParameter; | |||||
| @@ -77,6 +77,7 @@ export function getInParameterComponent( | |||||
| return null; | return null; | ||||
| } | } | ||||
| // 判断是否允许输入 | |||||
| export function canInput(parameter: PipelineNodeModelParameter) { | export function canInput(parameter: PipelineNodeModelParameter) { | ||||
| const { type, item_type } = parameter; | const { type, item_type } = parameter; | ||||
| return !( | return !( | ||||
| @@ -101,9 +101,8 @@ const Pipeline = () => { | |||||
| page: pageOption.current.page - 1, | page: pageOption.current.page - 1, | ||||
| size: pageOption.current.size, | size: pageOption.current.size, | ||||
| }; | }; | ||||
| console.log(params, pageOption); | |||||
| getWorkflow(params).then((ret) => { | getWorkflow(params).then((ret) => { | ||||
| if (ret.code == 200) { | |||||
| if (ret.code === 200) { | |||||
| setPipeList(ret.data.content); | setPipeList(ret.data.content); | ||||
| setTotal(ret.data.totalElements); | setTotal(ret.data.totalElements); | ||||
| @@ -13,7 +13,7 @@ | |||||
| } | } | ||||
| .PipelineBox { | .PipelineBox { | ||||
| height: calc(100% - 20px); | |||||
| height: 100%; | |||||
| .PipelineTable { | .PipelineTable { | ||||
| height: calc(100% - 60px); | height: calc(100% - 60px); | ||||
| :global { | :global { | ||||
| @@ -59,3 +59,11 @@ export function updateModelDeploymentReq(data: any) { | |||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 获取模型部署操作指南 | |||||
| export function getModelDeploymentDocsReq(data: any) { | |||||
| return request(`/api/v1/model/getDocs`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| @@ -4,7 +4,7 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: flex-start; | justify-content: flex-start; | ||||
| font-size: 16px; | |||||
| font-size: @font-size-content; | |||||
| .anticon.kf-menu-item__default-icon { | .anticon.kf-menu-item__default-icon { | ||||
| display: inline !important; | display: inline !important; | ||||
| @@ -54,6 +54,6 @@ | |||||
| .ant-menu-submenu { | .ant-menu-submenu { | ||||
| .ant-menu-submenu-title:hover { | .ant-menu-submenu-title:hover { | ||||
| color: #1664ff !important; | |||||
| color: @primary-color !important; | |||||
| } | } | ||||
| } | } | ||||
| @@ -11,10 +11,11 @@ | |||||
| @background-color: #f9fafb; // 页面背景颜色 | @background-color: #f9fafb; // 页面背景颜色 | ||||
| @text-color: #1d1d20; | @text-color: #1d1d20; | ||||
| @text-color-secondary: #575757; | @text-color-secondary: #575757; | ||||
| @success-color: #1ace62; | |||||
| @success-color: #6ac21d; | |||||
| @error-color: #c73131; | @error-color: #c73131; | ||||
| @warning-color: #f98e1b; | @warning-color: #f98e1b; | ||||
| @abort-color: #8a8a8a; | @abort-color: #8a8a8a; | ||||
| @pending-color: #ecb934; | |||||
| @border-color: rgba(22, 100, 255, 0.3); | @border-color: rgba(22, 100, 255, 0.3); | ||||
| @border-color-secondary: rgba(22, 100, 255, 0.1); | @border-color-secondary: rgba(22, 100, 255, 0.1); | ||||
| @@ -78,4 +79,6 @@ | |||||
| fontSizeInput: @font-size-input; | fontSizeInput: @font-size-input; | ||||
| fontSizeInputLg: @font-size-input-lg; | fontSizeInputLg: @font-size-input-lg; | ||||
| siderBGColor: @sider-background-color; | siderBGColor: @sider-background-color; | ||||
| abortColor: @abort-color; | |||||
| pendingColor: @pending-color; | |||||
| } | } | ||||
| @@ -41,20 +41,25 @@ export type PipelineNodeModel = { | |||||
| control_strategy: string; | control_strategy: string; | ||||
| in_parameters: string; | in_parameters: string; | ||||
| out_parameters: string; | out_parameters: string; | ||||
| component_label: string; | |||||
| icon_path: string; | |||||
| }; | }; | ||||
| // 流水线 | |||||
| // 流水线节点模型数据 | |||||
| export type PipelineNodeModelParameter = { | export type PipelineNodeModelParameter = { | ||||
| label: string; | |||||
| value: any; | |||||
| require: number; | |||||
| type: string; | type: string; | ||||
| item_type: string; | item_type: string; | ||||
| label: string; | |||||
| value: any; | |||||
| require?: number; | |||||
| placeholder?: string; | placeholder?: string; | ||||
| describe?: string; | describe?: string; | ||||
| fromSelect?: boolean; | fromSelect?: boolean; | ||||
| showValue?: any; | showValue?: any; | ||||
| editable: number; | |||||
| editable?: number; | |||||
| activeTab?: string; // ResourceSelectorModal tab | |||||
| expandedKeys?: string[]; // ResourceSelectorModal expandedKeys | |||||
| checkedKeys?: string[]; // ResourceSelectorModal checkedKeys | |||||
| }; | }; | ||||
| // type ChangePropertyType<T, K extends keyof T, NewType> = Omit<T, K> & { [P in K]: NewType } | // type ChangePropertyType<T, K extends keyof T, NewType> = Omit<T, K> & { [P in K]: NewType } | ||||
| @@ -29,7 +29,7 @@ export function resolveBlob(res: any, mimeType: string) { | |||||
| document.body.removeChild(aLink); | document.body.removeChild(aLink); | ||||
| } | } | ||||
| export function downLoadZip(url: string, params: any) { | |||||
| export function downLoadZip(url: string, params?: any) { | |||||
| request(url, { | request(url, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| @@ -105,7 +105,7 @@ public class ModelDependencyController extends BaseController { | |||||
| @PostMapping("/queryModelAtlas") | @PostMapping("/queryModelAtlas") | ||||
| @ApiOperation("根据对象查询") | |||||
| @ApiOperation("根据模型id与版本两个属性得到模型的演化图谱") | |||||
| public GenericsAjaxResult<ModelDependcyTreeVo> queryModelAtlas(@RequestBody ModelDependency modelDependency) throws Exception { | public GenericsAjaxResult<ModelDependcyTreeVo> queryModelAtlas(@RequestBody ModelDependency modelDependency) throws Exception { | ||||
| return genericsSuccess(this.modelDependencyService.getModelDependencyTree(modelDependency)); | return genericsSuccess(this.modelDependencyService.getModelDependencyTree(modelDependency)); | ||||
| } | } | ||||
| @@ -70,6 +70,7 @@ public class ModelDependencyServiceImpl implements ModelDependencyService { | |||||
| @Override | @Override | ||||
| public ModelDependcyTreeVo getModelDependencyTree(ModelDependency modelDependencyQuery) throws Exception { | public ModelDependcyTreeVo getModelDependencyTree(ModelDependency modelDependencyQuery) throws Exception { | ||||
| //查询当前模型 | //查询当前模型 | ||||
| modelDependencyQuery.setState(1); | |||||
| List<ModelDependency> modelDependencyList = modelDependencyDao.queryByModelDependency(modelDependencyQuery); | List<ModelDependency> modelDependencyList = modelDependencyDao.queryByModelDependency(modelDependencyQuery); | ||||
| if (modelDependencyList==null || modelDependencyList.size()==0){ | if (modelDependencyList==null || modelDependencyList.size()==0){ | ||||
| throw new Exception("当前模型依赖关系不存在"); | throw new Exception("当前模型依赖关系不存在"); | ||||
| @@ -3,10 +3,12 @@ package com.ruoyi.platform.service.impl; | |||||
| import com.ruoyi.common.security.utils.SecurityUtils; | import com.ruoyi.common.security.utils.SecurityUtils; | ||||
| import com.ruoyi.platform.annotations.CheckDuplicate; | import com.ruoyi.platform.annotations.CheckDuplicate; | ||||
| import com.ruoyi.platform.domain.Dataset; | import com.ruoyi.platform.domain.Dataset; | ||||
| import com.ruoyi.platform.domain.ModelDependency; | |||||
| import com.ruoyi.platform.domain.Models; | import com.ruoyi.platform.domain.Models; | ||||
| import com.ruoyi.platform.domain.ModelsVersion; | import com.ruoyi.platform.domain.ModelsVersion; | ||||
| import com.ruoyi.platform.mapper.ModelsDao; | import com.ruoyi.platform.mapper.ModelsDao; | ||||
| import com.ruoyi.platform.mapper.ModelsVersionDao; | import com.ruoyi.platform.mapper.ModelsVersionDao; | ||||
| import com.ruoyi.platform.service.ModelDependencyService; | |||||
| import com.ruoyi.platform.service.ModelsVersionService; | import com.ruoyi.platform.service.ModelsVersionService; | ||||
| import com.ruoyi.system.api.model.LoginUser; | import com.ruoyi.system.api.model.LoginUser; | ||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| @@ -36,7 +38,8 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { | |||||
| @Resource | @Resource | ||||
| private ModelsDao modelsDao; | private ModelsDao modelsDao; | ||||
| @Resource | |||||
| private ModelDependencyService modelDependencyService; | |||||
| // 固定存储桶名 | // 固定存储桶名 | ||||
| @Value("${minio.dataReleaseBucketName}") | @Value("${minio.dataReleaseBucketName}") | ||||
| private String bucketName; | private String bucketName; | ||||
| @@ -81,6 +84,7 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { | |||||
| modelsVersion.setCreateTime(new Date()); | modelsVersion.setCreateTime(new Date()); | ||||
| modelsVersion.setState(1); | modelsVersion.setState(1); | ||||
| this.modelsVersionDao.insert(modelsVersion); | this.modelsVersionDao.insert(modelsVersion); | ||||
| insertModelsDependency(modelsVersion); | |||||
| return modelsVersion; | return modelsVersion; | ||||
| } | } | ||||
| @@ -210,6 +214,7 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { | |||||
| for(ModelsVersion modelsVersion : modelsVersions) { | for(ModelsVersion modelsVersion : modelsVersions) { | ||||
| insertPrepare(modelsVersion); | insertPrepare(modelsVersion); | ||||
| } | } | ||||
| insertModelsDependency(modelsVersions.get(0)); | |||||
| this.modelsVersionDao.insertBatch(modelsVersions); | this.modelsVersionDao.insertBatch(modelsVersions); | ||||
| return "新增模型版本成功"; | return "新增模型版本成功"; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| @@ -248,4 +253,23 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { | |||||
| } | } | ||||
| //新增模型依赖关系 | |||||
| private void insertModelsDependency(ModelsVersion modelsVersion) throws Exception { | |||||
| ModelDependency modelDependency = new ModelDependency(); | |||||
| modelDependency.setCurrentModelId(modelsVersion.getModelsId()); | |||||
| modelDependency.setVersion(modelsVersion.getVersion()); | |||||
| modelDependency.setState(2); | |||||
| List<ModelDependency> modelDependencyList = modelDependencyService.queryByModelDependency(modelDependency); | |||||
| if (modelDependencyList != null && modelDependencyList.size()>0){ | |||||
| //查到2,说明是之前流水线推送的,你就直接该状态生效就完了 | |||||
| ModelDependency modelDependency1 = modelDependencyList.get(0); | |||||
| modelDependency1.setState(1); | |||||
| modelDependencyService.update(modelDependency1); | |||||
| }else { | |||||
| modelDependency.setState(1); | |||||
| modelDependencyService.insert(modelDependency); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -5,9 +5,11 @@ import com.fasterxml.jackson.databind.DeserializationFeature; | |||||
| import com.fasterxml.jackson.databind.ObjectMapper; | import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.fasterxml.jackson.databind.SerializationFeature; | import com.fasterxml.jackson.databind.SerializationFeature; | ||||
| import com.fasterxml.jackson.databind.type.CollectionType; | import com.fasterxml.jackson.databind.type.CollectionType; | ||||
| import com.ruoyi.common.core.utils.StringUtils; | |||||
| import java.text.SimpleDateFormat; | import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||
| import java.util.HashMap; | |||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.Map; | import java.util.Map; | ||||
| @@ -140,6 +142,7 @@ public class JacksonUtil { | |||||
| */ | */ | ||||
| public static Map<String, Object> parseJSONStr2Map(String jsonStr) { | public static Map<String, Object> parseJSONStr2Map(String jsonStr) { | ||||
| try { | try { | ||||
| if (StringUtils.isEmpty(jsonStr)) {return new HashMap<String, Object>();} | |||||
| // 对于json字符串新增的字段,由于返回的是map,不管 compatNewProps 设置成什么值都不会抛出异常 | // 对于json字符串新增的字段,由于返回的是map,不管 compatNewProps 设置成什么值都不会抛出异常 | ||||
| ObjectMapper objectMapper = getObjectMapper(null, false, false, true); | ObjectMapper objectMapper = getObjectMapper(null, false, false, true); | ||||
| return objectMapper.readValue(jsonStr, Map.class); | return objectMapper.readValue(jsonStr, Map.class); | ||||
| @@ -158,6 +161,7 @@ public class JacksonUtil { | |||||
| */ | */ | ||||
| public static List<Map<String, Object>> parseJSONStr2MapList(String jsonStr) { | public static List<Map<String, Object>> parseJSONStr2MapList(String jsonStr) { | ||||
| try { | try { | ||||
| if (StringUtils.isEmpty(jsonStr)) {return new ArrayList<>();} | |||||
| // 对于json字符串新增的字段,由于返回的是map,不管 compatNewProps 设置成什么值都不会抛出异常 | // 对于json字符串新增的字段,由于返回的是map,不管 compatNewProps 设置成什么值都不会抛出异常 | ||||
| ObjectMapper objectMapper = getObjectMapper(null, false, false, true); | ObjectMapper objectMapper = getObjectMapper(null, false, false, true); | ||||
| CollectionType listType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Map.class); | CollectionType listType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Map.class); | ||||