| @@ -12,12 +12,21 @@ import './index.less'; | |||
| export interface KFModalProps extends ModalProps { | |||
| image?: string; | |||
| } | |||
| function KFModal({ title, image, children, className = '', centered, ...rest }: KFModalProps) { | |||
| function KFModal({ | |||
| title, | |||
| image, | |||
| children, | |||
| className = '', | |||
| centered, | |||
| maskClosable, | |||
| ...rest | |||
| }: KFModalProps) { | |||
| return ( | |||
| <Modal | |||
| className={classNames(['kf-modal', className])} | |||
| {...rest} | |||
| centered={centered ?? true} | |||
| maskClosable={maskClosable ?? false} | |||
| title={<ModalTitle title={title} image={image}></ModalTitle>} | |||
| > | |||
| {children} | |||
| @@ -29,7 +29,6 @@ function ParameterInput({ | |||
| onClick, | |||
| canInput = true, | |||
| textArea = false, | |||
| placeholder, | |||
| allowClear, | |||
| className, | |||
| style, | |||
| @@ -37,8 +36,6 @@ function ParameterInput({ | |||
| disabled = false, | |||
| ...rest | |||
| }: ParameterInputProps) { | |||
| // console.log('ParameterInput', value); | |||
| const valueObj = | |||
| typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | |||
| if (valueObj && !valueObj.showValue) { | |||
| @@ -46,6 +43,7 @@ function ParameterInput({ | |||
| } | |||
| const isSelect = valueObj?.fromSelect; | |||
| const InputComponent = textArea ? Input.TextArea : Input; | |||
| const placeholder = valueObj?.placeholder; | |||
| return ( | |||
| <> | |||
| @@ -68,9 +66,12 @@ function ParameterInput({ | |||
| e.stopPropagation(); | |||
| onChange?.({ | |||
| ...valueObj, | |||
| fromSelect: false, | |||
| value: 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 { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| App, | |||
| Button, | |||
| Form, | |||
| Input, | |||
| @@ -15,12 +14,13 @@ import { | |||
| Select, | |||
| Upload, | |||
| UploadFile, | |||
| message, | |||
| type ModalProps, | |||
| type UploadProps, | |||
| } from 'antd'; | |||
| import { omit } from 'lodash'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { CategoryData } from '../../types'; | |||
| import { CategoryData } from '../../config'; | |||
| import styles from './index.less'; | |||
| interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||
| @@ -32,7 +32,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | |||
| const [uuid] = useState(Date.now()); | |||
| const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | |||
| const { message } = App.useApp(); | |||
| useEffect(() => { | |||
| getClusterOptions(); | |||
| @@ -1,18 +1,18 @@ | |||
| import { getAccessToken } from '@/access'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| 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 { to } from '@/utils/promise'; | |||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| App, | |||
| Button, | |||
| Form, | |||
| Input, | |||
| Select, | |||
| Upload, | |||
| UploadFile, | |||
| message, | |||
| type ModalProps, | |||
| type UploadProps, | |||
| } from 'antd'; | |||
| @@ -28,7 +28,6 @@ interface AddModelModalProps extends Omit<ModalProps, 'onOk'> { | |||
| function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) { | |||
| const [uuid] = useState(Date.now()); | |||
| const { message } = App.useApp(); | |||
| // 上传组件参数 | |||
| const uploadProps: UploadProps = { | |||
| @@ -1,16 +1,16 @@ | |||
| import { getAccessToken } from '@/access'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| 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 { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| App, | |||
| Button, | |||
| Form, | |||
| Input, | |||
| Upload, | |||
| UploadFile, | |||
| message, | |||
| type ModalProps, | |||
| type UploadProps, | |||
| } from 'antd'; | |||
| @@ -33,7 +33,6 @@ function AddVersionModal({ | |||
| ...rest | |||
| }: AddVersionModalProps) { | |||
| const [uuid] = useState(Date.now()); | |||
| const { message } = App.useApp(); | |||
| // 上传组件参数 | |||
| const uploadProps: UploadProps = { | |||
| @@ -46,7 +45,7 @@ function AddVersionModal({ | |||
| // 上传请求 | |||
| const createDatasetVersion = async (params: any) => { | |||
| const request = resourceConfig[resourceType].addVersionReq; | |||
| const request = resourceConfig[resourceType].addVersion; | |||
| const [res] = await to(request(params)); | |||
| if (res) { | |||
| message.success('创建成功'); | |||
| @@ -1,5 +1,5 @@ | |||
| import classNames from 'classnames'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../config'; | |||
| import styles from './index.less'; | |||
| type CategoryItemProps = { | |||
| @@ -20,13 +20,13 @@ function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemP | |||
| <img | |||
| className={styles['category-item__icon']} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}.png`} | |||
| src={`/assets/images/${resourceConfig[resourceType].prefix}/${item.path}.png`} | |||
| alt="" | |||
| /> | |||
| <img | |||
| className={styles['category-item__active-icon']} | |||
| style={{ width: '22px' }} | |||
| src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}-hover.png`} | |||
| src={`/assets/images/${resourceConfig[resourceType].prefix}/${item.path}-hover.png`} | |||
| alt="" | |||
| /> | |||
| <span className={styles['category-item__name']}>{item.name}</span> | |||
| @@ -1,5 +1,5 @@ | |||
| import { Flex, Input } from 'antd'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../config'; | |||
| import CategoryItem from '../CategoryItem'; | |||
| 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 { App, Button, Input, Pagination, PaginationProps } from 'antd'; | |||
| 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 ResourceItem from '../Resourcetem'; | |||
| import styles from './index.less'; | |||
| @@ -129,11 +129,8 @@ function ResourceList( | |||
| activeType: dataType, | |||
| 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 { Flex, Tabs, type TabsProps } from 'antd'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../config'; | |||
| import CategoryList from '../CategoryList'; | |||
| import ResourceList, { ResourceListRef } from '../ResourceList'; | |||
| 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 { formatDate } from '@/utils/date'; | |||
| import { Button, Flex, Typography } from 'antd'; | |||
| import { ResourceData } from '../../types'; | |||
| import { ResourceData } from '../../config'; | |||
| import styles from './index.less'; | |||
| type ResourceItemProps = { | |||
| @@ -4,10 +4,14 @@ import { | |||
| addDatasetVersionDetail, | |||
| addModelsVersionDetail, | |||
| deleteDataset, | |||
| deleteDatasetVersion, | |||
| deleteModel, | |||
| deleteModelVersion, | |||
| getDatasetById, | |||
| getDatasetList, | |||
| getDatasetVersionIdList, | |||
| getDatasetVersionsById, | |||
| getModelById, | |||
| getModelList, | |||
| getModelVersionIdList, | |||
| getModelVersionsById, | |||
| @@ -24,6 +28,9 @@ type ResourceTypeInfo = { | |||
| getVersions: (params: any) => Promise<any>; | |||
| getFiles: (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; | |||
| typeParamKey: string; | |||
| tagParamKey: string; | |||
| @@ -33,13 +40,16 @@ type ResourceTypeInfo = { | |||
| tagTitle: string; | |||
| typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue) | |||
| tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue) | |||
| iconPathPrefix: string; // 图标路径前缀 | |||
| prefix: string; // 前缀 | |||
| deleteModalTitle: string; // 删除弹框的title | |||
| addBtnTitle: string; // 新增按钮的title | |||
| addVersionReq: (params: any) => Promise<any>; | |||
| idParamKey: string; | |||
| idParamKey: 'models_id' | 'dataset_id'; | |||
| uploadAction: string; | |||
| uploadAccept?: string; | |||
| downloadAllAction: string; | |||
| downloadSingleAction: string; | |||
| infoTypePropertyName: string; | |||
| infoTagPropertyName: string; | |||
| }; | |||
| export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||
| @@ -48,6 +58,9 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||
| getVersions: getDatasetVersionsById, | |||
| getFiles: getDatasetVersionIdList, | |||
| deleteRecord: deleteDataset, | |||
| addVersion: addDatasetVersionDetail, | |||
| deleteVersion: deleteDatasetVersion, | |||
| getInfo: getDatasetById, | |||
| name: '数据集', | |||
| typeParamKey: 'data_type', | |||
| tagParamKey: 'data_tag', | |||
| @@ -68,19 +81,25 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||
| tagTitle: '研究方向/应用领域', | |||
| typeValue: 1, | |||
| tagValue: 2, | |||
| iconPathPrefix: 'dataset', | |||
| prefix: 'dataset', | |||
| deleteModalTitle: '确定删除该条数据集实例吗?', | |||
| addBtnTitle: '新建数据集', | |||
| addVersionReq: addDatasetVersionDetail, | |||
| idParamKey: 'dataset_id', | |||
| uploadAction: '/api/mmp/dataset/upload', | |||
| uploadAccept: '.zip,.tgz', | |||
| downloadAllAction: '/api/mmp/dataset/downloadAllFilesl', | |||
| downloadSingleAction: '/api/mmp/dataset/download', | |||
| infoTypePropertyName: 'dataset_type_name', | |||
| infoTagPropertyName: 'dataset_tag_name', | |||
| }, | |||
| [ResourceType.Model]: { | |||
| getList: getModelList, | |||
| getVersions: getModelVersionsById, | |||
| getFiles: getModelVersionIdList, | |||
| deleteRecord: deleteModel, | |||
| addVersion: addModelsVersionDetail, | |||
| deleteVersion: deleteModelVersion, | |||
| getInfo: getModelById, | |||
| name: '模型', | |||
| typeParamKey: 'model_type', | |||
| tagParamKey: 'model_tag', | |||
| @@ -101,13 +120,16 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||
| tagTitle: '模型能力', | |||
| typeValue: 3, | |||
| tagValue: 4, | |||
| iconPathPrefix: 'model', | |||
| prefix: 'model', | |||
| deleteModalTitle: '确定删除该条模型实例吗?', | |||
| addBtnTitle: '新建模型', | |||
| addVersionReq: addModelsVersionDetail, | |||
| idParamKey: 'models_id', | |||
| uploadAction: '/api/mmp/models/upload', | |||
| 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; | |||
| }; | |||
| // 数据类型 | |||
| // 资源数据 | |||
| export type ResourceData = { | |||
| id: number; | |||
| name: string; | |||
| description: string; | |||
| create_by: 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 { ResourceType } from './types'; | |||
| import { ResourceType } from './config'; | |||
| const DatasetPage = () => { | |||
| 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 { useComputingResource } from '@/hooks/resource'; | |||
| import { PipelineNodeModelSerialize } from '@/types'; | |||
| @@ -122,15 +124,8 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||
| <TextArea disabled /> | |||
| </Form.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> | |||
| ))} | |||
| <div className={styles['experiment-parameter__title']}> | |||
| @@ -142,11 +137,12 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||
| name={['in_parameters', item.key]} | |||
| label={item.value.label + '(' + item.key + ')'} | |||
| 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> | |||
| ))} | |||
| <div className={styles['experiment-parameter__title']}> | |||
| @@ -158,11 +154,8 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||
| name={['out_parameters', item.key]} | |||
| label={item.value.label + '(' + item.key + ')'} | |||
| rules={[{ required: item.value.require ? true : false }]} | |||
| getValueProps={(e) => { | |||
| return { value: e.showValue || e.value }; | |||
| }} | |||
| > | |||
| <Input disabled /> | |||
| <ParameterInput disabled /> | |||
| </Form.Item> | |||
| ))} | |||
| </Form> | |||
| @@ -35,4 +35,9 @@ | |||
| } | |||
| } | |||
| } | |||
| &__empty { | |||
| margin-top: 10px; | |||
| text-align: center; | |||
| } | |||
| } | |||
| @@ -23,34 +23,38 @@ function ExperimentResult({ results }: ExperimentResultProps) { | |||
| return ( | |||
| <div className={styles['experiment-result']}> | |||
| <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> */} | |||
| </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 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> | |||
| ); | |||
| @@ -21,8 +21,13 @@ | |||
| color: white; | |||
| font-size: 14px; | |||
| white-space: pre-line; | |||
| text-align: left; | |||
| word-break: break-all; | |||
| background: #19253b; | |||
| &--empty { | |||
| text-align: center; | |||
| } | |||
| } | |||
| &__more-button { | |||
| @@ -10,6 +10,7 @@ import { ExperimentLog } from '@/pages/Experiment/training/props'; | |||
| import { getExperimentPodsLog } from '@/services/experiment/index.js'; | |||
| import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | |||
| import { Button } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './index.less'; | |||
| @@ -22,6 +23,21 @@ type Log = { | |||
| 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({ | |||
| log_type = 'normal', | |||
| pod_name = '', | |||
| @@ -32,8 +48,11 @@ function LogGroup({ | |||
| const [collapse, setCollapse] = useState(true); | |||
| const [logList, setLogList, logListRef] = useStateRef<Log[]>([]); | |||
| const [completed, setCompleted] = useState(false); | |||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |||
| const [isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); | |||
| useEffect(() => { | |||
| scrollToBottom(false); | |||
| if (status === ExperimentStatus.Running) { | |||
| const timerId = setInterval(() => { | |||
| 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 list = logListRef.current; | |||
| @@ -54,8 +91,14 @@ function LogGroup({ | |||
| }; | |||
| const res = await getExperimentPodsLog(params); | |||
| const { log_detail } = res.data; | |||
| if (log_detail && log_detail.log_content) { | |||
| if (log_detail) { | |||
| setLogList((oldList) => oldList.concat(log_detail)); | |||
| if (!isMouseDownRef.current && log_detail.log_content) { | |||
| setTimeout(() => { | |||
| scrollToBottom(); | |||
| }, 100); | |||
| } | |||
| } else { | |||
| setCompleted(true); | |||
| } | |||
| @@ -96,7 +139,15 @@ function LogGroup({ | |||
| {collapse ? <DownOutlined /> : <UpOutlined />} | |||
| </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']}> | |||
| {showMoreBtn && ( | |||
| <Button | |||
| @@ -1,3 +1,13 @@ | |||
| .log-list { | |||
| 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) { | |||
| return ( | |||
| <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> | |||
| ); | |||
| } | |||
| @@ -8,15 +8,15 @@ | |||
| font-size: 15px; | |||
| &--running { | |||
| color: #6ac21d; | |||
| color: @success-color; | |||
| } | |||
| &--failed { | |||
| color: #df6d6d; | |||
| color: @error-color; | |||
| } | |||
| } | |||
| &__icon { | |||
| width: 14px; | |||
| color: #6ac21d; | |||
| color: @success-color; | |||
| cursor: pointer; | |||
| & + & { | |||
| @@ -11,8 +11,8 @@ | |||
| margin-bottom: 15px; | |||
| &_label { | |||
| width: 120px; | |||
| color: #1d1d20; | |||
| width: 180px; | |||
| color: @text-color; | |||
| font-size: 15px; | |||
| } | |||
| &_value { | |||
| @@ -20,8 +20,8 @@ | |||
| width: 100px; | |||
| margin-left: 15px; | |||
| padding: 10px 20px; | |||
| color: #1d1d20; | |||
| font-size: 15px; | |||
| color: @text-color; | |||
| font-size: @font-size; | |||
| line-height: 20px; | |||
| background: #f6f6f6; | |||
| border: 1px solid #e0e0e1; | |||
| @@ -18,7 +18,7 @@ import themes from '@/styles/theme.less'; | |||
| import { elapsedTime, formatDate } from '@/utils/date'; | |||
| import { to } from '@/utils/promise'; | |||
| 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 { useEffect, useRef, useState } from 'react'; | |||
| import { useNavigate } from 'react-router-dom'; | |||
| @@ -84,7 +84,6 @@ function Experiment() { | |||
| // 获取实验实例 | |||
| const getQueryByExperiment = (val) => { | |||
| getQueryByExperimentId(val).then((ret) => { | |||
| console.log(val); | |||
| setExpandedRowKeys(val); | |||
| if (ret && ret.data && ret.data.length > 0) { | |||
| try { | |||
| @@ -162,7 +161,6 @@ function Experiment() { | |||
| }; | |||
| const expandChange = (e, record) => { | |||
| clearExperimentInTimers(); | |||
| console.log(e, record); | |||
| if (record.id === expandedRowKeys) { | |||
| setExpandedRowKeys(null); | |||
| } else { | |||
| @@ -238,7 +236,6 @@ function Experiment() { | |||
| }; | |||
| // 当前页面切换 | |||
| const paginationChange = async (current, size) => { | |||
| console.log('page', current, size); | |||
| pageOption.current = { | |||
| page: current, | |||
| size: size, | |||
| @@ -279,14 +276,14 @@ function Experiment() { | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| render: (text) => <div>{text}</div>, | |||
| width: '20%', | |||
| width: '16%', | |||
| }, | |||
| { | |||
| title: '关联流水线名称', | |||
| dataIndex: 'workflow_name', | |||
| key: 'workflow_name', | |||
| render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>, | |||
| width: '20%', | |||
| width: '16%', | |||
| }, | |||
| { | |||
| title: '实验描述', | |||
| @@ -443,13 +440,17 @@ function Experiment() { | |||
| <div style={{ width: '50%' }}> | |||
| {elapsedTime(item.create_time, item.finish_time)} | |||
| </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 className={Styles.statusBox}> | |||
| <img | |||
| style={{ width: '17px', marginRight: '7px' }} | |||
| src={experimentStatusInfo[item.status]?.icon} | |||
| />{' '} | |||
| /> | |||
| <span | |||
| style={{ color: experimentStatusInfo[item.status]?.color }} | |||
| className={Styles.statusIcon} | |||
| @@ -36,17 +36,21 @@ | |||
| } | |||
| .index { | |||
| width: calc((100% + 32px + 33px) / 5); | |||
| width: calc((100% + 32px + 33px) / 6.25); | |||
| } | |||
| .tensorBoard { | |||
| width: calc((100% + 32px + 33px) / 5); | |||
| width: calc((100% + 32px + 33px) / 6.25); | |||
| } | |||
| .description { | |||
| display: flex; | |||
| flex: 1; | |||
| align-items: center; | |||
| .startTime { | |||
| .singleLine(); | |||
| } | |||
| } | |||
| .status { | |||
| @@ -80,8 +84,9 @@ | |||
| .statusBox:hover .statusIcon { | |||
| visibility: visible; | |||
| } | |||
| .experimentBox { | |||
| height: calc(100% - 20px); | |||
| height: 100%; | |||
| .experimentTable { | |||
| height: calc(100% - 60px); | |||
| :global { | |||
| @@ -1,3 +1,5 @@ | |||
| import themes from '@/styles/theme.less'; | |||
| export interface StatusInfo { | |||
| label: string; | |||
| color: string; | |||
| @@ -18,42 +20,42 @@ export enum ExperimentStatus { | |||
| export const experimentStatusInfo: Record<ExperimentStatus, StatusInfo | undefined> = { | |||
| Running: { | |||
| label: '运行中', | |||
| color: '#1664ff', | |||
| color: themes.primaryColor, | |||
| icon: '/assets/images/running-icon.png', | |||
| }, | |||
| Succeeded: { | |||
| label: '成功', | |||
| color: '#63a728', | |||
| color: themes.successColor, | |||
| icon: '/assets/images/success-icon.png', | |||
| }, | |||
| Pending: { | |||
| label: '等待中', | |||
| color: '#f981eb', | |||
| color: themes.pendingColor, | |||
| icon: '/assets/images/pending-icon.png', | |||
| }, | |||
| Failed: { | |||
| label: '失败', | |||
| color: '#c73131', | |||
| color: themes.errorColor, | |||
| icon: '/assets/images/fail-icon.png', | |||
| }, | |||
| Error: { | |||
| label: '错误', | |||
| color: '#c73131', | |||
| color: themes.errorColor, | |||
| icon: '/assets/images/fail-icon.png', | |||
| }, | |||
| Terminated: { | |||
| label: '终止', | |||
| color: '#8a8a8a', | |||
| color: themes.abortColor, | |||
| icon: '/assets/images/omitted-icon.png', | |||
| }, | |||
| Skipped: { | |||
| label: '未执行', | |||
| color: '#8a8a8a', | |||
| color: themes.abortColor, | |||
| icon: '/assets/images/omitted-icon.png', | |||
| }, | |||
| Omitted: { | |||
| label: '未执行', | |||
| color: '#8a8a8a', | |||
| color: themes.abortColor, | |||
| icon: '/assets/images/omitted-icon.png', | |||
| }, | |||
| }; | |||
| @@ -109,7 +109,8 @@ const Props = forwardRef((_, ref) => { | |||
| // 获取实验日志和实验结果 | |||
| setExperimentLogList([]); | |||
| setExperimentResults([]); | |||
| if (e.item && e.item.getModel()) { | |||
| // 如果已经运行到了 | |||
| if (e.item?.getModel()?.component_id) { | |||
| const model = e.item.getModel(); | |||
| const start_time = dayjs(model.experimentStartTime).valueOf() * 1.0e6; | |||
| const params = { | |||
| @@ -1,5 +1,5 @@ | |||
| import ResourcePage from '@/pages/Dataset/components/ResourcePage'; | |||
| import { ResourceType } from '@/pages/Dataset/types'; | |||
| import { ResourceType } from '@/pages/Dataset/config'; | |||
| const ModelPage = () => { | |||
| 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 { | |||
| 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 { | |||
| &__item { | |||
| display: flex; | |||
| @@ -23,34 +33,13 @@ | |||
| } | |||
| } | |||
| &__content { | |||
| height: calc(100% - 60px); | |||
| &__guide { | |||
| flex: 1; | |||
| 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 { useComputingResource } from '@/hooks/resource'; | |||
| import { useSessionStorage } from '@/hooks/sessionStorage'; | |||
| import { getModelDeploymentDocsReq } from '@/services/modelDeployment'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { to } from '@/utils/promise'; | |||
| import { modelDeploymentInfoKey } from '@/utils/sessionStorage'; | |||
| import { Col, Row, Tabs, type TabsProps } from 'antd'; | |||
| import { pick } from 'lodash'; | |||
| import { useEffect, useState } from 'react'; | |||
| import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell'; | |||
| import { ModelDeploymentData } from '../types'; | |||
| import styles from './index.less'; | |||
| export enum ModelDeploymentTabKey { | |||
| Predict = 'Predict', | |||
| Guide = 'Guide', | |||
| Log = 'Log', | |||
| } | |||
| const tabItems = [ | |||
| { | |||
| key: '1', | |||
| key: ModelDeploymentTabKey.Predict, | |||
| label: '预测', | |||
| icon: <KFIcon type="icon-yuce" />, | |||
| }, | |||
| { | |||
| key: '2', | |||
| key: ModelDeploymentTabKey.Guide, | |||
| label: '调用指南', | |||
| icon: <KFIcon type="icon-tiaoyongzhinan" />, | |||
| }, | |||
| { | |||
| key: '3', | |||
| key: ModelDeploymentTabKey.Log, | |||
| label: '服务日志', | |||
| icon: <KFIcon type="icon-fuwurizhi" />, | |||
| }, | |||
| ]; | |||
| function ModelDeploymentInfo() { | |||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||
| const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict); | |||
| const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>( | |||
| modelDeploymentInfoKey, | |||
| true, | |||
| undefined, | |||
| ); | |||
| 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,重置数据 | |||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | |||
| setActiveTab(value); | |||
| }; | |||
| // 格式化环境变量 | |||
| const formatEnvText = () => { | |||
| if (!modelDeployementInfo?.env) { | |||
| return '--'; | |||
| @@ -64,128 +86,130 @@ function ModelDeploymentInfo() { | |||
| <div className={styles['model-deployment-info']}> | |||
| <PageTitle title="服务详情"></PageTitle> | |||
| <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> | |||
| </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> | |||
| </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> | |||
| </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> | |||
| </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> | |||
| </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> | |||
| <Tabs | |||
| activeKey={activeTab} | |||
| style={{ marginTop: '20px' }} | |||
| items={tabItems} | |||
| onChange={hanleTabChange} | |||
| /> | |||
| {activeTab === ModelDeploymentTabKey.Guide && ( | |||
| <div className={styles['model-deployment-info__guide']}>{docs}</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 = { | |||
| id: number; // 镜像版本id | |||
| id: number; // 镜像版本 id | |||
| status: MirrorVersionStatus; // 镜像版本状态 | |||
| tag_name: string; // 镜像版本 | |||
| tag_name: string; // 镜像版本 name | |||
| url: string; // 镜像版本路径 | |||
| }; | |||
| @@ -39,12 +39,13 @@ export type SelectorTypeInfo = { | |||
| tabItems: TabsProps['items']; | |||
| }; | |||
| // 获取镜像列表,为了兼容数据集和模型 | |||
| // 获取镜像文件列表,为了兼容数据集和模型 | |||
| const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => { | |||
| const index = version.indexOf('-'); | |||
| const url = version.slice(index + 1); | |||
| return Promise.resolve({ | |||
| data: { | |||
| path: url, | |||
| content: [ | |||
| { | |||
| id: `${id}-${version}`, | |||
| @@ -15,26 +15,18 @@ import { MirrorVersion, ResourceSelectorType, selectorTypeConfig } from './confi | |||
| import styles from './index.less'; | |||
| export { ResourceSelectorType, selectorTypeConfig }; | |||
| // 选择数据集和模型的返回类型 | |||
| // 选择数据集\模型\镜像的返回类型 | |||
| 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; // 是我的还是公开的 | |||
| }; | |||
| 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 = { | |||
| id: number; // 数据集或者模型 id | |||
| name: string; // 数据集或者模型 id | |||
| id: number; // 数据集\模型\镜像 id | |||
| name: string; // 数据集\模型\镜像 name | |||
| }; | |||
| type ResourceFile = { | |||
| @@ -42,9 +34,17 @@ type ResourceFile = { | |||
| 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>>; | |||
| // list 转成 treeData | |||
| // list 数据转成 treeData | |||
| const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { | |||
| return list.map((v) => ({ | |||
| title: v.name, | |||
| @@ -54,7 +54,7 @@ const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { | |||
| })); | |||
| }; | |||
| // 版本转成 treeData | |||
| // 版本数据转成 treeData | |||
| const convertVersionToTreeData = (parentId: number) => { | |||
| return (item: string | MirrorVersion): TreeDataNode => { | |||
| if (typeof item === 'string') { | |||
| @@ -88,7 +88,7 @@ const updateChildren = (parentId: number, children: TreeDataNode[]) => { | |||
| }; | |||
| }; | |||
| // 得到数据集或者模型 id 和下属版本号 | |||
| // 得到数据集\模型\镜像 id 和下属版本号 | |||
| const getIdAndVersion = (versionKey: string) => { | |||
| const index = versionKey.indexOf('-'); | |||
| const id = Number(versionKey.slice(0, index)); | |||
| @@ -137,12 +137,12 @@ function ResourceSelectorModal({ | |||
| [originTreeData, searchText], | |||
| ); | |||
| // 获取数据集或模型列表 | |||
| // 获取数据集\模型\镜像列表 | |||
| const getTreeData = async () => { | |||
| const available_range = activeTab === CommonTabKeys.Private ? 0 : 1; | |||
| const params = { | |||
| page: 0, | |||
| size: 200, | |||
| size: 1000, | |||
| [selectorTypeConfig[type].litReqParamKey]: available_range, | |||
| }; | |||
| const getListReq = selectorTypeConfig[type].getList; | |||
| @@ -159,7 +159,7 @@ function ResourceSelectorModal({ | |||
| } | |||
| }; | |||
| // 获取数据集或模型版本列表 | |||
| // 获取数据集\模型\镜像版本列表 | |||
| const getVersions = async (parentId: number) => { | |||
| const getVersionsReq = selectorTypeConfig[type].getVersions; | |||
| const [res, error] = await to(getVersionsReq(parentId)); | |||
| @@ -266,21 +266,17 @@ function ResourceSelectorModal({ | |||
| // 提交 | |||
| const handleOk = () => { | |||
| 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 { | |||
| onOk?.(null); | |||
| } | |||
| @@ -8,8 +8,8 @@ import { useEffect, useRef } from 'react'; | |||
| import { useNavigate, useParams } from 'react-router-dom'; | |||
| import { s8 } from '../../../utils'; | |||
| import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | |||
| import ModelMenu from '../components/ModelMenu'; | |||
| import styles from './index.less'; | |||
| import ModelMenus from './modelMenus'; | |||
| import Props from './props'; | |||
| import { findAllParentNodes, findFirstDuplicate } from './utils'; | |||
| @@ -51,8 +51,16 @@ const EditPipeline = () => { | |||
| return item.id === val.id; | |||
| }); | |||
| data.nodes[index] = val; | |||
| const zoom = graph.getZoom(); | |||
| // 在拉取新数据重新渲染页面之前先获取点(0, 0)在画布上的位置 | |||
| const lastPoint = graph.getCanvasByPoint(0, 0); | |||
| graph.changeData(data); | |||
| 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) => { | |||
| @@ -89,7 +97,7 @@ const EditPipeline = () => { | |||
| closeParamsDrawer(); | |||
| setTimeout(() => { | |||
| if (val) { | |||
| navgite({ pathname: `/pipeline` }); | |||
| navgite({ pathname: `/pipeline/template` }); | |||
| } | |||
| }, 500); | |||
| }); | |||
| @@ -432,7 +440,7 @@ const EditPipeline = () => { | |||
| height: graphRef.current.clientHeight || '100%', | |||
| animate: false, | |||
| groupByTypes: false, | |||
| fitView: false, | |||
| fitView: true, | |||
| plugins: [contextMenu], | |||
| enabledStack: true, | |||
| modes: { | |||
| @@ -691,7 +699,7 @@ const EditPipeline = () => { | |||
| }; | |||
| return ( | |||
| <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__top']}> | |||
| <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 ParameterInput from '@/components/ParameterInput'; | |||
| import ParameterSelect from '@/components/ParameterSelect'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { | |||
| PipelineGlobalParam, | |||
| PipelineNodeModelParameter, | |||
| PipelineNodeModelSerialize, | |||
| } from '@/types'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| 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 PropsLabel from '../components/PropsLabel'; | |||
| import ResourceSelectorModal, { | |||
| @@ -16,18 +24,22 @@ import styles from './props.less'; | |||
| import { canInput, createMenuItems } from './utils'; | |||
| 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 [stagingItem, setStagingItem] = useState({}); | |||
| const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>( | |||
| {} as PipelineNodeModelSerialize, | |||
| ); | |||
| const [open, setOpen] = useState(false); | |||
| const [selectedModel, setSelectedModel] = useState(undefined); // 选择的模型,为了再次打开时恢复原来的选择 | |||
| const [selectedDataset, setSelectedDataset] = useState(undefined); // 选择的数据集,为了再次打开时恢复原来的选择 | |||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); // 资源规模 | |||
| const [menuItems, setMenuItems] = useState([]); | |||
| const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | |||
| const afterOpenChange = () => { | |||
| if (!open) { | |||
| console.log('zzzzz', form.getFieldsValue()); | |||
| console.log('getFieldsValue', form.getFieldsValue()); | |||
| const control_strategy = form.getFieldValue('control_strategy'); | |||
| const in_parameters = form.getFieldValue('in_parameters'); | |||
| const out_parameters = form.getFieldValue('out_parameters'); | |||
| @@ -54,7 +66,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| return Promise.reject(propsError); | |||
| } | |||
| }, | |||
| showDrawer(e, params, parentNodes) { | |||
| showDrawer(e: any, params: PipelineGlobalParam[], parentNodes: INode[]) { | |||
| if (e.item && e.item.getModel()) { | |||
| form.resetFields(); | |||
| const model = e.item.getModel(); | |||
| @@ -75,8 +87,6 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| } catch (error) { | |||
| console.log(error); | |||
| } | |||
| setSelectedModel(undefined); | |||
| setSelectedDataset(undefined); | |||
| 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) { | |||
| case 'dataset': | |||
| type = ResourceSelectorType.Dataset; | |||
| resource = selectedDataset; | |||
| break; | |||
| case 'model': | |||
| type = ResourceSelectorType.Model; | |||
| resource = selectedModel; | |||
| break; | |||
| default: | |||
| type = ResourceSelectorType.Mirror; | |||
| 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, { | |||
| type, | |||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||
| defaultActiveTab: resource?.activeTab, | |||
| defaultExpandedKeys: expandedKeys, | |||
| defaultCheckedKeys: checkedKeys, | |||
| defaultActiveTab: activeTab, | |||
| onOk: (res) => { | |||
| if (res) { | |||
| 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 { | |||
| 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 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 { | |||
| if (type === ResourceSelectorType.Dataset) { | |||
| setSelectedDataset(undefined); | |||
| } else if (type === ResourceSelectorType.Model) { | |||
| setSelectedModel(undefined); | |||
| } | |||
| form.setFieldValue(name, ''); | |||
| form.setFieldValue(formItemName, ''); | |||
| } | |||
| close(); | |||
| }, | |||
| @@ -140,9 +170,9 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| }; | |||
| // 获取选择数据集、模型后面按钮 icon | |||
| const getSelectBtnIcon = (item) => { | |||
| const getSelectBtnIcon = (item: { item_type: string }) => { | |||
| const type = item.item_type; | |||
| let selectorType; | |||
| let selectorType: ResourceSelectorType; | |||
| if (type === 'dataset') { | |||
| selectorType = ResourceSelectorType.Dataset; | |||
| } 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 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( | |||
| ([key, value]) => ({ key, value }), | |||
| @@ -290,7 +343,6 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| ]} | |||
| > | |||
| <Select | |||
| showSearch | |||
| placeholder="请选择资源规格" | |||
| filterOption={filterResourceStandard} | |||
| options={resourceStandardList} | |||
| @@ -298,6 +350,8 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| label: 'description', | |||
| value: 'standard', | |||
| }} | |||
| showSearch | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| @@ -332,31 +386,9 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| <Form.Item | |||
| key={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> | |||
| ))} | |||
| <div className={styles['pipeline-drawer__title']}> | |||
| @@ -365,20 +397,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| {inParametersList.map((item) => ( | |||
| <Form.Item | |||
| 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} | |||
| > | |||
| <div className={styles['pipeline-drawer__ref-row']}> | |||
| @@ -387,11 +406,11 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| noStyle | |||
| 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> | |||
| {item.value.type === 'ref' && ( | |||
| <Form.Item noStyle> | |||
| @@ -416,32 +435,10 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| <Form.Item | |||
| key={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 }]} | |||
| // 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> | |||
| @@ -449,4 +446,4 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| ); | |||
| }); | |||
| export default Props; | |||
| export default PipelineNodeParameter; | |||
| @@ -77,6 +77,7 @@ export function getInParameterComponent( | |||
| return null; | |||
| } | |||
| // 判断是否允许输入 | |||
| export function canInput(parameter: PipelineNodeModelParameter) { | |||
| const { type, item_type } = parameter; | |||
| return !( | |||
| @@ -101,9 +101,8 @@ const Pipeline = () => { | |||
| page: pageOption.current.page - 1, | |||
| size: pageOption.current.size, | |||
| }; | |||
| console.log(params, pageOption); | |||
| getWorkflow(params).then((ret) => { | |||
| if (ret.code == 200) { | |||
| if (ret.code === 200) { | |||
| setPipeList(ret.data.content); | |||
| setTotal(ret.data.totalElements); | |||
| @@ -13,7 +13,7 @@ | |||
| } | |||
| .PipelineBox { | |||
| height: calc(100% - 20px); | |||
| height: 100%; | |||
| .PipelineTable { | |||
| height: calc(100% - 60px); | |||
| :global { | |||
| @@ -59,3 +59,11 @@ export function updateModelDeploymentReq(data: any) { | |||
| data, | |||
| }); | |||
| } | |||
| // 获取模型部署操作指南 | |||
| export function getModelDeploymentDocsReq(data: any) { | |||
| return request(`/api/v1/model/getDocs`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| @@ -4,7 +4,7 @@ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-start; | |||
| font-size: 16px; | |||
| font-size: @font-size-content; | |||
| .anticon.kf-menu-item__default-icon { | |||
| display: inline !important; | |||
| @@ -54,6 +54,6 @@ | |||
| .ant-menu-submenu { | |||
| .ant-menu-submenu-title:hover { | |||
| color: #1664ff !important; | |||
| color: @primary-color !important; | |||
| } | |||
| } | |||
| @@ -11,10 +11,11 @@ | |||
| @background-color: #f9fafb; // 页面背景颜色 | |||
| @text-color: #1d1d20; | |||
| @text-color-secondary: #575757; | |||
| @success-color: #1ace62; | |||
| @success-color: #6ac21d; | |||
| @error-color: #c73131; | |||
| @warning-color: #f98e1b; | |||
| @abort-color: #8a8a8a; | |||
| @pending-color: #ecb934; | |||
| @border-color: rgba(22, 100, 255, 0.3); | |||
| @border-color-secondary: rgba(22, 100, 255, 0.1); | |||
| @@ -78,4 +79,6 @@ | |||
| fontSizeInput: @font-size-input; | |||
| fontSizeInputLg: @font-size-input-lg; | |||
| siderBGColor: @sider-background-color; | |||
| abortColor: @abort-color; | |||
| pendingColor: @pending-color; | |||
| } | |||
| @@ -41,20 +41,25 @@ export type PipelineNodeModel = { | |||
| control_strategy: string; | |||
| in_parameters: string; | |||
| out_parameters: string; | |||
| component_label: string; | |||
| icon_path: string; | |||
| }; | |||
| // 流水线 | |||
| // 流水线节点模型数据 | |||
| export type PipelineNodeModelParameter = { | |||
| label: string; | |||
| value: any; | |||
| require: number; | |||
| type: string; | |||
| item_type: string; | |||
| label: string; | |||
| value: any; | |||
| require?: number; | |||
| placeholder?: string; | |||
| describe?: string; | |||
| fromSelect?: boolean; | |||
| 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 } | |||
| @@ -29,7 +29,7 @@ export function resolveBlob(res: any, mimeType: string) { | |||
| document.body.removeChild(aLink); | |||
| } | |||
| export function downLoadZip(url: string, params: any) { | |||
| export function downLoadZip(url: string, params?: any) { | |||
| request(url, { | |||
| method: 'GET', | |||
| params, | |||
| @@ -105,7 +105,7 @@ public class ModelDependencyController extends BaseController { | |||
| @PostMapping("/queryModelAtlas") | |||
| @ApiOperation("根据对象查询") | |||
| @ApiOperation("根据模型id与版本两个属性得到模型的演化图谱") | |||
| public GenericsAjaxResult<ModelDependcyTreeVo> queryModelAtlas(@RequestBody ModelDependency modelDependency) throws Exception { | |||
| return genericsSuccess(this.modelDependencyService.getModelDependencyTree(modelDependency)); | |||
| } | |||
| @@ -70,6 +70,7 @@ public class ModelDependencyServiceImpl implements ModelDependencyService { | |||
| @Override | |||
| public ModelDependcyTreeVo getModelDependencyTree(ModelDependency modelDependencyQuery) throws Exception { | |||
| //查询当前模型 | |||
| modelDependencyQuery.setState(1); | |||
| List<ModelDependency> modelDependencyList = modelDependencyDao.queryByModelDependency(modelDependencyQuery); | |||
| if (modelDependencyList==null || modelDependencyList.size()==0){ | |||
| throw new Exception("当前模型依赖关系不存在"); | |||
| @@ -3,10 +3,12 @@ package com.ruoyi.platform.service.impl; | |||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||
| import com.ruoyi.platform.annotations.CheckDuplicate; | |||
| import com.ruoyi.platform.domain.Dataset; | |||
| import com.ruoyi.platform.domain.ModelDependency; | |||
| import com.ruoyi.platform.domain.Models; | |||
| import com.ruoyi.platform.domain.ModelsVersion; | |||
| import com.ruoyi.platform.mapper.ModelsDao; | |||
| import com.ruoyi.platform.mapper.ModelsVersionDao; | |||
| import com.ruoyi.platform.service.ModelDependencyService; | |||
| import com.ruoyi.platform.service.ModelsVersionService; | |||
| import com.ruoyi.system.api.model.LoginUser; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| @@ -36,7 +38,8 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { | |||
| @Resource | |||
| private ModelsDao modelsDao; | |||
| @Resource | |||
| private ModelDependencyService modelDependencyService; | |||
| // 固定存储桶名 | |||
| @Value("${minio.dataReleaseBucketName}") | |||
| private String bucketName; | |||
| @@ -81,6 +84,7 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { | |||
| modelsVersion.setCreateTime(new Date()); | |||
| modelsVersion.setState(1); | |||
| this.modelsVersionDao.insert(modelsVersion); | |||
| insertModelsDependency(modelsVersion); | |||
| return modelsVersion; | |||
| } | |||
| @@ -210,6 +214,7 @@ public class ModelsVersionServiceImpl implements ModelsVersionService { | |||
| for(ModelsVersion modelsVersion : modelsVersions) { | |||
| insertPrepare(modelsVersion); | |||
| } | |||
| insertModelsDependency(modelsVersions.get(0)); | |||
| this.modelsVersionDao.insertBatch(modelsVersions); | |||
| return "新增模型版本成功"; | |||
| } 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.SerializationFeature; | |||
| import com.fasterxml.jackson.databind.type.CollectionType; | |||
| import com.ruoyi.common.core.utils.StringUtils; | |||
| import java.text.SimpleDateFormat; | |||
| import java.util.ArrayList; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| @@ -140,6 +142,7 @@ public class JacksonUtil { | |||
| */ | |||
| public static Map<String, Object> parseJSONStr2Map(String jsonStr) { | |||
| try { | |||
| if (StringUtils.isEmpty(jsonStr)) {return new HashMap<String, Object>();} | |||
| // 对于json字符串新增的字段,由于返回的是map,不管 compatNewProps 设置成什么值都不会抛出异常 | |||
| ObjectMapper objectMapper = getObjectMapper(null, false, false, true); | |||
| return objectMapper.readValue(jsonStr, Map.class); | |||
| @@ -158,6 +161,7 @@ public class JacksonUtil { | |||
| */ | |||
| public static List<Map<String, Object>> parseJSONStr2MapList(String jsonStr) { | |||
| try { | |||
| if (StringUtils.isEmpty(jsonStr)) {return new ArrayList<>();} | |||
| // 对于json字符串新增的字段,由于返回的是map,不管 compatNewProps 设置成什么值都不会抛出异常 | |||
| ObjectMapper objectMapper = getObjectMapper(null, false, false, true); | |||
| CollectionType listType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Map.class); | |||