| @@ -57,6 +57,7 @@ | |||||
| "@ant-design/pro-components": "^2.4.4", | "@ant-design/pro-components": "^2.4.4", | ||||
| "@ant-design/use-emotion-css": "1.0.4", | "@ant-design/use-emotion-css": "1.0.4", | ||||
| "@antv/g6": "^4.8.24", | "@antv/g6": "^4.8.24", | ||||
| "@antv/hierarchy": "^0.6.12", | |||||
| "@umijs/route-utils": "^4.0.1", | "@umijs/route-utils": "^4.0.1", | ||||
| "antd": "^5.4.4", | "antd": "^5.4.4", | ||||
| "classnames": "^2.3.2", | "classnames": "^2.3.2", | ||||
| @@ -21,6 +21,7 @@ import './styles/menu.less'; | |||||
| export { requestConfig as request } from './requestConfig'; | export { requestConfig as request } from './requestConfig'; | ||||
| // const isDev = process.env.NODE_ENV === 'development'; | // const isDev = process.env.NODE_ENV === 'development'; | ||||
| import { menuItemRender } from '@/utils/menuRender'; | import { menuItemRender } from '@/utils/menuRender'; | ||||
| import { gotoLoginPage } from './utils/ui'; | |||||
| /** | /** | ||||
| * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | ||||
| * */ | * */ | ||||
| @@ -45,7 +46,7 @@ export async function getInitialState(): Promise<{ | |||||
| } as API.CurrentUser; | } as API.CurrentUser; | ||||
| } catch (error) { | } catch (error) { | ||||
| console.log(error); | console.log(error); | ||||
| history.push(PageEnum.LOGIN); | |||||
| gotoLoginPage(); | |||||
| } | } | ||||
| return undefined; | return undefined; | ||||
| }; | }; | ||||
| @@ -97,7 +98,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| const { location } = history; | const { location } = history; | ||||
| // 如果没有登录,重定向到 login | // 如果没有登录,重定向到 login | ||||
| if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) { | if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) { | ||||
| history.push(PageEnum.LOGIN); | |||||
| gotoLoginPage(); | |||||
| } | } | ||||
| }, | }, | ||||
| layoutBgImgList: [ | layoutBgImgList: [ | ||||
| @@ -12,12 +12,21 @@ import './index.less'; | |||||
| export interface KFModalProps extends ModalProps { | export interface KFModalProps extends ModalProps { | ||||
| image?: string; | image?: string; | ||||
| } | } | ||||
| function KFModal({ title, image, children, className = '', centered, ...rest }: KFModalProps) { | |||||
| function KFModal({ | |||||
| title, | |||||
| image, | |||||
| children, | |||||
| className = '', | |||||
| centered, | |||||
| maskClosable, | |||||
| ...rest | |||||
| }: KFModalProps) { | |||||
| return ( | return ( | ||||
| <Modal | <Modal | ||||
| className={classNames(['kf-modal', className])} | className={classNames(['kf-modal', className])} | ||||
| {...rest} | {...rest} | ||||
| centered={centered ?? true} | centered={centered ?? true} | ||||
| maskClosable={maskClosable ?? false} | |||||
| title={<ModalTitle title={title} image={image}></ModalTitle>} | title={<ModalTitle title={title} image={image}></ModalTitle>} | ||||
| > | > | ||||
| {children} | {children} | ||||
| @@ -29,7 +29,6 @@ function ParameterInput({ | |||||
| onClick, | onClick, | ||||
| canInput = true, | canInput = true, | ||||
| textArea = false, | textArea = false, | ||||
| placeholder, | |||||
| allowClear, | allowClear, | ||||
| className, | className, | ||||
| style, | style, | ||||
| @@ -37,8 +36,6 @@ function ParameterInput({ | |||||
| disabled = false, | disabled = false, | ||||
| ...rest | ...rest | ||||
| }: ParameterInputProps) { | }: ParameterInputProps) { | ||||
| // console.log('ParameterInput', value); | |||||
| const valueObj = | const valueObj = | ||||
| typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | ||||
| if (valueObj && !valueObj.showValue) { | if (valueObj && !valueObj.showValue) { | ||||
| @@ -46,6 +43,7 @@ function ParameterInput({ | |||||
| } | } | ||||
| const isSelect = valueObj?.fromSelect; | const isSelect = valueObj?.fromSelect; | ||||
| const InputComponent = textArea ? Input.TextArea : Input; | const InputComponent = textArea ? Input.TextArea : Input; | ||||
| const placeholder = valueObj?.placeholder; | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -68,9 +66,12 @@ function ParameterInput({ | |||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| onChange?.({ | onChange?.({ | ||||
| ...valueObj, | ...valueObj, | ||||
| fromSelect: false, | |||||
| value: undefined, | value: undefined, | ||||
| showValue: undefined, | showValue: undefined, | ||||
| fromSelect: false, | |||||
| activeTab: undefined, | |||||
| expandedKeys: undefined, | |||||
| checkedKeys: undefined, | |||||
| }); | }); | ||||
| }} | }} | ||||
| /> | /> | ||||
| @@ -0,0 +1,72 @@ | |||||
| import { getDatasetList, getModelList } from '@/services/dataset/index.js'; | |||||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||||
| import { ComputingResource } from '@/types'; | |||||
| import { type SelectProps } from 'antd'; | |||||
| // 过滤资源规格 | |||||
| const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | |||||
| input: string, | |||||
| option?: ComputingResource, | |||||
| ) => { | |||||
| return ( | |||||
| option?.computing_resource?.toLocaleLowerCase()?.includes(input.toLocaleLowerCase()) ?? false | |||||
| ); | |||||
| }; | |||||
| // id 从 number 转换为 string | |||||
| const convertId = (item: any) => ({ ...item, id: String(item.id) }); | |||||
| export type SelectPropsConfig = { | |||||
| getOptions: () => Promise<any>; | |||||
| fieldNames?: SelectProps['fieldNames']; | |||||
| optionFilterProp?: SelectProps['optionFilterProp']; | |||||
| filterOption?: SelectProps['filterOption']; | |||||
| }; | |||||
| export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| dataset: { | |||||
| getOptions: async () => { | |||||
| const res = await getDatasetList({ | |||||
| page: 0, | |||||
| size: 1000, | |||||
| available_range: 0, | |||||
| }); | |||||
| return res?.data?.content?.map(convertId) ?? []; | |||||
| }, | |||||
| fieldNames: { | |||||
| label: 'name', | |||||
| value: 'id', | |||||
| }, | |||||
| optionFilterProp: 'name', | |||||
| }, | |||||
| model: { | |||||
| getOptions: async () => { | |||||
| const res = await getModelList({ | |||||
| page: 0, | |||||
| size: 1000, | |||||
| available_range: 0, | |||||
| }); | |||||
| return res?.data?.content?.map(convertId) ?? []; | |||||
| }, | |||||
| fieldNames: { | |||||
| label: 'name', | |||||
| value: 'id', | |||||
| }, | |||||
| optionFilterProp: 'name', | |||||
| }, | |||||
| resource: { | |||||
| getOptions: async () => { | |||||
| const res = await getComputingResourceReq({ | |||||
| page: 0, | |||||
| size: 1000, | |||||
| resource_type: '', | |||||
| }); | |||||
| return res?.data?.content ?? []; | |||||
| }, | |||||
| fieldNames: { | |||||
| label: 'description', | |||||
| value: 'standard', | |||||
| }, | |||||
| filterOption: filterResourceStandard as SelectProps['filterOption'], | |||||
| }, | |||||
| }; | |||||
| @@ -0,0 +1,58 @@ | |||||
| import { PipelineNodeModelParameter } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Select } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { paramSelectConfig } from './config'; | |||||
| type ParameterSelectProps = { | |||||
| value?: PipelineNodeModelParameter; | |||||
| onChange?: (value: PipelineNodeModelParameter) => void; | |||||
| disabled?: boolean; | |||||
| }; | |||||
| function ParameterSelect({ value, onChange, disabled = false }: ParameterSelectProps) { | |||||
| const [options, setOptions] = useState([]); | |||||
| const valueNonNullable = value ?? ({} as PipelineNodeModelParameter); | |||||
| const { item_type } = valueNonNullable; | |||||
| const propsConfig = paramSelectConfig[item_type]; | |||||
| useEffect(() => { | |||||
| getSelectOptions(); | |||||
| }, []); | |||||
| const hangleChange = (e: string) => { | |||||
| onChange?.({ | |||||
| ...valueNonNullable, | |||||
| value: e, | |||||
| }); | |||||
| }; | |||||
| // 获取下拉数据 | |||||
| const getSelectOptions = async () => { | |||||
| if (!propsConfig) { | |||||
| return; | |||||
| } | |||||
| const getOptions = propsConfig.getOptions; | |||||
| const [res] = await to(getOptions()); | |||||
| if (res) { | |||||
| setOptions(res); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <Select | |||||
| placeholder={valueNonNullable.placeholder} | |||||
| filterOption={propsConfig?.filterOption} | |||||
| options={options} | |||||
| fieldNames={propsConfig?.fieldNames} | |||||
| value={valueNonNullable.value} | |||||
| optionFilterProp={propsConfig.optionFilterProp} | |||||
| onChange={hangleChange} | |||||
| disabled={disabled} | |||||
| showSearch | |||||
| allowClear | |||||
| /> | |||||
| ); | |||||
| } | |||||
| export default ParameterSelect; | |||||
| @@ -1,7 +1,7 @@ | |||||
| /* | /* | ||||
| * @Author: 赵伟 | * @Author: 赵伟 | ||||
| * @Date: 2024-04-15 10:01:29 | * @Date: 2024-04-15 10:01:29 | ||||
| * @Description: | |||||
| * @Description: 自定义 hooks | |||||
| */ | */ | ||||
| import { FormInstance } from 'antd'; | import { FormInstance } from 'antd'; | ||||
| import { debounce } from 'lodash'; | import { debounce } from 'lodash'; | ||||
| @@ -126,3 +126,28 @@ export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => { | |||||
| } | } | ||||
| }, [form, prevOpen, open]); | }, [form, prevOpen, open]); | ||||
| }; | }; | ||||
| /** | |||||
| * Executes the effect function when the specified condition is true. | |||||
| * | |||||
| * @param effect - The effect function to execute. | |||||
| * @param deps - The dependencies for the effect. | |||||
| * @param when - The condition to trigger the effect. | |||||
| */ | |||||
| export const useEffectWhen = (effect: () => void, deps: React.DependencyList, when: boolean) => { | |||||
| const requestFns = useRef<(() => void)[]>([]); | |||||
| useEffect(() => { | |||||
| if (when) { | |||||
| effect(); | |||||
| } else { | |||||
| requestFns.current.splice(0, 1, effect); | |||||
| } | |||||
| }, deps); | |||||
| useEffect(() => { | |||||
| if (when) { | |||||
| const fn = requestFns.current.pop(); | |||||
| fn?.(); | |||||
| } | |||||
| }, [when]); | |||||
| }; | |||||
| @@ -7,7 +7,6 @@ import { getDictSelectOption } from '@/services/system/dict'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| import { | import { | ||||
| App, | |||||
| Button, | Button, | ||||
| Form, | Form, | ||||
| Input, | Input, | ||||
| @@ -15,12 +14,13 @@ import { | |||||
| Select, | Select, | ||||
| Upload, | Upload, | ||||
| UploadFile, | UploadFile, | ||||
| message, | |||||
| type ModalProps, | type ModalProps, | ||||
| type UploadProps, | type UploadProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import { omit } from 'lodash'; | import { omit } from 'lodash'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { CategoryData } from '../../types'; | |||||
| import { CategoryData } from '../../config'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| @@ -32,7 +32,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | ||||
| const [uuid] = useState(Date.now()); | const [uuid] = useState(Date.now()); | ||||
| const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | ||||
| const { message } = App.useApp(); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getClusterOptions(); | getClusterOptions(); | ||||
| @@ -1,18 +1,18 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { CategoryData } from '@/pages/Dataset/types'; | |||||
| import { CategoryData } from '@/pages/Dataset/config'; | |||||
| import { addModel } from '@/services/dataset/index.js'; | import { addModel } from '@/services/dataset/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| import { | import { | ||||
| App, | |||||
| Button, | Button, | ||||
| Form, | Form, | ||||
| Input, | Input, | ||||
| Select, | Select, | ||||
| Upload, | Upload, | ||||
| UploadFile, | UploadFile, | ||||
| message, | |||||
| type ModalProps, | type ModalProps, | ||||
| type UploadProps, | type UploadProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| @@ -28,7 +28,6 @@ interface AddModelModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) { | function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) { | ||||
| const [uuid] = useState(Date.now()); | const [uuid] = useState(Date.now()); | ||||
| const { message } = App.useApp(); | |||||
| // 上传组件参数 | // 上传组件参数 | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| @@ -1,16 +1,16 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { ResourceType, resourceConfig } from '@/pages/Dataset/types'; | |||||
| import { ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| import { | import { | ||||
| App, | |||||
| Button, | Button, | ||||
| Form, | Form, | ||||
| Input, | Input, | ||||
| Upload, | Upload, | ||||
| UploadFile, | UploadFile, | ||||
| message, | |||||
| type ModalProps, | type ModalProps, | ||||
| type UploadProps, | type UploadProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| @@ -33,7 +33,6 @@ function AddVersionModal({ | |||||
| ...rest | ...rest | ||||
| }: AddVersionModalProps) { | }: AddVersionModalProps) { | ||||
| const [uuid] = useState(Date.now()); | const [uuid] = useState(Date.now()); | ||||
| const { message } = App.useApp(); | |||||
| // 上传组件参数 | // 上传组件参数 | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| @@ -46,7 +45,7 @@ function AddVersionModal({ | |||||
| // 上传请求 | // 上传请求 | ||||
| const createDatasetVersion = async (params: any) => { | const createDatasetVersion = async (params: any) => { | ||||
| const request = resourceConfig[resourceType].addVersionReq; | |||||
| const request = resourceConfig[resourceType].addVersion; | |||||
| const [res] = await to(request(params)); | const [res] = await to(request(params)); | ||||
| if (res) { | if (res) { | ||||
| message.success('创建成功'); | message.success('创建成功'); | ||||
| @@ -1,5 +1,5 @@ | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../config'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type CategoryItemProps = { | type CategoryItemProps = { | ||||
| @@ -20,13 +20,13 @@ function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemP | |||||
| <img | <img | ||||
| className={styles['category-item__icon']} | className={styles['category-item__icon']} | ||||
| style={{ width: '22px' }} | style={{ width: '22px' }} | ||||
| src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}.png`} | |||||
| src={`/assets/images/${resourceConfig[resourceType].prefix}/${item.path}.png`} | |||||
| alt="" | alt="" | ||||
| /> | /> | ||||
| <img | <img | ||||
| className={styles['category-item__active-icon']} | className={styles['category-item__active-icon']} | ||||
| style={{ width: '22px' }} | style={{ width: '22px' }} | ||||
| src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}-hover.png`} | |||||
| src={`/assets/images/${resourceConfig[resourceType].prefix}/${item.path}-hover.png`} | |||||
| alt="" | alt="" | ||||
| /> | /> | ||||
| <span className={styles['category-item__name']}>{item.name}</span> | <span className={styles['category-item__name']}>{item.name}</span> | ||||
| @@ -1,5 +1,5 @@ | |||||
| import { Flex, Input } from 'antd'; | import { Flex, Input } from 'antd'; | ||||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../config'; | |||||
| import CategoryItem from '../CategoryItem'; | import CategoryItem from '../CategoryItem'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -0,0 +1,65 @@ | |||||
| .resource-intro { | |||||
| height: 100%; | |||||
| &__top { | |||||
| width: 100%; | |||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 20px 30px 0; | |||||
| 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: 20px; | |||||
| } | |||||
| &__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); | |||||
| :global { | |||||
| .ant-tabs { | |||||
| height: 100%; | |||||
| .ant-tabs-content-holder { | |||||
| height: 100%; | |||||
| .ant-tabs-content { | |||||
| height: 100%; | |||||
| .ant-tabs-tabpane { | |||||
| height: 100%; | |||||
| overflow-y: auto; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__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,153 @@ | |||||
| import ModelEvolution from '@/pages/Model/components/ModelEvolution'; | |||||
| 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; | |||||
| }; | |||||
| enum TabKeys { | |||||
| Introduction = '1', | |||||
| Version = '2', | |||||
| Evolution = '3', | |||||
| } | |||||
| const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||||
| const [info, setInfo] = useState<ResourceData>({} as ResourceData); | |||||
| const locationParams = useParams(); | |||||
| const [searchParams] = useSearchParams(); | |||||
| const defaultTab = searchParams.get('tab') || '1'; | |||||
| let versionParam = searchParams.get('version'); | |||||
| const [versionList, setVersionList] = useState([]); | |||||
| const [version, setVersion] = useState<string | undefined>(undefined); | |||||
| const [activeTab, setActiveTab] = useState<string>(defaultTab); | |||||
| const resourceId = Number(locationParams.id); | |||||
| const typeName = resourceConfig[resourceType].name; // 数据集/模型 | |||||
| useEffect(() => { | |||||
| getModelByDetail(); | |||||
| getVersionList(); | |||||
| }, []); | |||||
| // 获取详情 | |||||
| const getModelByDetail = async () => { | |||||
| const request = resourceConfig[resourceType].getInfo; | |||||
| const [res] = await to(request(resourceId)); | |||||
| if (res) { | |||||
| setInfo(res.data); | |||||
| } | |||||
| }; | |||||
| // 获取版本列表 | |||||
| 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, | |||||
| }; | |||||
| }), | |||||
| ); | |||||
| if (versionParam) { | |||||
| setVersion(versionParam); | |||||
| versionParam = null; | |||||
| } else { | |||||
| setVersion(res.data[0]); | |||||
| } | |||||
| } else { | |||||
| setVersion(undefined); | |||||
| } | |||||
| }; | |||||
| // 版本变化 | |||||
| const handleVersionChange = (value: string) => { | |||||
| setVersion(value); | |||||
| }; | |||||
| const items = [ | |||||
| { | |||||
| key: TabKeys.Introduction, | |||||
| label: `${typeName}简介`, | |||||
| children: ( | |||||
| <> | |||||
| <div className={styles['resource-intro__title']}>简介</div> | |||||
| <div className={styles['resource-intro__intro']}>{info.description}</div> | |||||
| </> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| key: TabKeys.Version, | |||||
| label: `${typeName}文件/版本`, | |||||
| children: ( | |||||
| <ResourceVersion | |||||
| resourceType={resourceType} | |||||
| resourceId={resourceId} | |||||
| resourceName={info.name} | |||||
| isPublic={info.available_range === 1} | |||||
| versionList={versionList} | |||||
| version={version} | |||||
| isActive={activeTab === TabKeys.Version} | |||||
| getVersionList={getVersionList} | |||||
| onVersionChange={handleVersionChange} | |||||
| ></ResourceVersion> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| if (resourceType === ResourceType.Model) { | |||||
| items.push({ | |||||
| key: TabKeys.Evolution, | |||||
| label: `模型演化`, | |||||
| children: ( | |||||
| <ModelEvolution | |||||
| resourceId={resourceId} | |||||
| resourceName={info.name} | |||||
| versionList={versionList} | |||||
| version={version} | |||||
| isActive={activeTab === TabKeys.Evolution} | |||||
| onVersionChange={handleVersionChange} | |||||
| ></ModelEvolution> | |||||
| ), | |||||
| }); | |||||
| } | |||||
| 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']}> | |||||
| <div className={styles['resource-intro__top__name']}>{info.name}</div> | |||||
| <Flex align="center"> | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {typeName} id:{info.id} | |||||
| </div> | |||||
| {info[infoTypePropertyName] && ( | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {info[infoTypePropertyName] || '--'} | |||||
| </div> | |||||
| )} | |||||
| {info[infoTagPropertyName] && ( | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {info[infoTagPropertyName] || '--'} | |||||
| </div> | |||||
| )} | |||||
| </Flex> | |||||
| </div> | |||||
| <div className={styles['resource-intro__bottom']}> | |||||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default ResourceIntro; | |||||
| @@ -7,7 +7,7 @@ import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | ||||
| import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | ||||
| import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../types'; | |||||
| import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config'; | |||||
| import AddDatasetModal from '../AddDatasetModal'; | import AddDatasetModal from '../AddDatasetModal'; | ||||
| import ResourceItem from '../Resourcetem'; | import ResourceItem from '../Resourcetem'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -129,11 +129,8 @@ function ResourceList( | |||||
| activeType: dataType, | activeType: dataType, | ||||
| activeTag: dataTag, | activeTag: dataTag, | ||||
| }); | }); | ||||
| if (resourceType === ResourceType.Dataset) { | |||||
| navigate(`/dataset/dataset/${record.id}?isPublic=${isPublic}`); | |||||
| } else { | |||||
| navigate(`/dataset/model/${record.id}?isPublic=${isPublic}`); | |||||
| } | |||||
| const prefix = resourceConfig[resourceType].prefix; | |||||
| navigate(`/dataset/${prefix}/${record.id}`); | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -4,7 +4,7 @@ import { getAssetIcon } from '@/services/dataset/index.js'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Flex, Tabs, type TabsProps } from 'antd'; | import { Flex, Tabs, type TabsProps } from 'antd'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../config'; | |||||
| import CategoryList from '../CategoryList'; | import CategoryList from '../CategoryList'; | ||||
| import ResourceList, { ResourceListRef } from '../ResourceList'; | import ResourceList, { ResourceListRef } from '../ResourceList'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -0,0 +1,4 @@ | |||||
| .resource-version { | |||||
| color: @text-color; | |||||
| font-size: @font-size-content; | |||||
| } | |||||
| @@ -0,0 +1,226 @@ | |||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { useEffectWhen } from '@/hooks'; | |||||
| import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | |||||
| import { | |||||
| ResourceFileData, | |||||
| ResourceType, | |||||
| ResourceVersionData, | |||||
| resourceConfig, | |||||
| } 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 { useState } from 'react'; | |||||
| import styles from './index.less'; | |||||
| type ResourceVersionProps = { | |||||
| resourceType: ResourceType; | |||||
| resourceId: number; | |||||
| resourceName: string; | |||||
| isPublic: boolean; | |||||
| versionList: ResourceVersionData[]; | |||||
| version?: string; | |||||
| isActive: boolean; | |||||
| getVersionList: () => void; | |||||
| onVersionChange: (version: string) => void; | |||||
| }; | |||||
| function ResourceVersion({ | |||||
| resourceType, | |||||
| resourceId, | |||||
| resourceName, | |||||
| isPublic, | |||||
| versionList, | |||||
| version, | |||||
| isActive, | |||||
| getVersionList, | |||||
| onVersionChange, | |||||
| }: ResourceVersionProps) { | |||||
| const [fileList, setFileList] = useState<ResourceFileData[]>([]); | |||||
| const { message } = App.useApp(); | |||||
| // 获取版本文件列表 | |||||
| useEffectWhen( | |||||
| () => { | |||||
| if (version) { | |||||
| getFileList(version); | |||||
| } else { | |||||
| setFileList([]); | |||||
| } | |||||
| }, | |||||
| [resourceId, version], | |||||
| isActive, | |||||
| ); | |||||
| // 获取版本下的文件列表 | |||||
| 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 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} | |||||
| onChange={onVersionChange} | |||||
| options={versionList} | |||||
| /> | |||||
| <Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 创建新版本 | |||||
| </Button> | |||||
| </Flex> | |||||
| <Flex align="center"> | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| style={{ marginRight: '20px' }} | |||||
| onClick={hanldeDelete} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| )} | |||||
| <Button | |||||
| type="default" | |||||
| disabled={!version} | |||||
| onClick={handleExport} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| </Flex> | |||||
| </Flex> | |||||
| <div style={{ marginBottom: '30px', fontSize: '15px' }}> | |||||
| {fileList.length > 0 && fileList[0].description | |||||
| ? '版本描述:' + fileList[0].description | |||||
| : null} | |||||
| </div> | |||||
| <Table columns={columns} dataSource={fileList} pagination={false} rowKey="id" /> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ResourceVersion; | |||||
| @@ -3,7 +3,7 @@ import creatByImg from '@/assets/img/creatBy.png'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { Button, Flex, Typography } from 'antd'; | import { Button, Flex, Typography } from 'antd'; | ||||
| import { ResourceData } from '../../types'; | |||||
| import { ResourceData } from '../../config'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ResourceItemProps = { | type ResourceItemProps = { | ||||
| @@ -4,10 +4,14 @@ import { | |||||
| addDatasetVersionDetail, | addDatasetVersionDetail, | ||||
| addModelsVersionDetail, | addModelsVersionDetail, | ||||
| deleteDataset, | deleteDataset, | ||||
| deleteDatasetVersion, | |||||
| deleteModel, | deleteModel, | ||||
| deleteModelVersion, | |||||
| getDatasetById, | |||||
| getDatasetList, | getDatasetList, | ||||
| getDatasetVersionIdList, | getDatasetVersionIdList, | ||||
| getDatasetVersionsById, | getDatasetVersionsById, | ||||
| getModelById, | |||||
| getModelList, | getModelList, | ||||
| getModelVersionIdList, | getModelVersionIdList, | ||||
| getModelVersionsById, | getModelVersionsById, | ||||
| @@ -24,6 +28,9 @@ type ResourceTypeInfo = { | |||||
| getVersions: (params: any) => Promise<any>; | getVersions: (params: any) => Promise<any>; | ||||
| getFiles: (params: any) => Promise<any>; | getFiles: (params: any) => Promise<any>; | ||||
| deleteRecord: (params: any) => Promise<any>; | deleteRecord: (params: any) => Promise<any>; | ||||
| addVersion: (params: any) => Promise<any>; | |||||
| deleteVersion: (params: any) => Promise<any>; | |||||
| getInfo: (params: any) => Promise<any>; | |||||
| name: string; | name: string; | ||||
| typeParamKey: string; | typeParamKey: string; | ||||
| tagParamKey: string; | tagParamKey: string; | ||||
| @@ -33,13 +40,16 @@ type ResourceTypeInfo = { | |||||
| tagTitle: string; | tagTitle: string; | ||||
| typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue) | typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue) | ||||
| tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue) | tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue) | ||||
| iconPathPrefix: string; // 图标路径前缀 | |||||
| prefix: string; // 前缀 | |||||
| deleteModalTitle: string; // 删除弹框的title | deleteModalTitle: string; // 删除弹框的title | ||||
| addBtnTitle: string; // 新增按钮的title | addBtnTitle: string; // 新增按钮的title | ||||
| addVersionReq: (params: any) => Promise<any>; | |||||
| idParamKey: string; | |||||
| idParamKey: 'models_id' | 'dataset_id'; | |||||
| uploadAction: string; | uploadAction: string; | ||||
| uploadAccept?: string; | uploadAccept?: string; | ||||
| downloadAllAction: string; | |||||
| downloadSingleAction: string; | |||||
| infoTypePropertyName: string; | |||||
| infoTagPropertyName: string; | |||||
| }; | }; | ||||
| export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | ||||
| @@ -48,6 +58,9 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| getVersions: getDatasetVersionsById, | getVersions: getDatasetVersionsById, | ||||
| getFiles: getDatasetVersionIdList, | getFiles: getDatasetVersionIdList, | ||||
| deleteRecord: deleteDataset, | deleteRecord: deleteDataset, | ||||
| addVersion: addDatasetVersionDetail, | |||||
| deleteVersion: deleteDatasetVersion, | |||||
| getInfo: getDatasetById, | |||||
| name: '数据集', | name: '数据集', | ||||
| typeParamKey: 'data_type', | typeParamKey: 'data_type', | ||||
| tagParamKey: 'data_tag', | tagParamKey: 'data_tag', | ||||
| @@ -68,19 +81,25 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| tagTitle: '研究方向/应用领域', | tagTitle: '研究方向/应用领域', | ||||
| typeValue: 1, | typeValue: 1, | ||||
| tagValue: 2, | tagValue: 2, | ||||
| iconPathPrefix: 'dataset', | |||||
| prefix: 'dataset', | |||||
| deleteModalTitle: '确定删除该条数据集实例吗?', | deleteModalTitle: '确定删除该条数据集实例吗?', | ||||
| addBtnTitle: '新建数据集', | addBtnTitle: '新建数据集', | ||||
| addVersionReq: addDatasetVersionDetail, | |||||
| idParamKey: 'dataset_id', | idParamKey: 'dataset_id', | ||||
| uploadAction: '/api/mmp/dataset/upload', | uploadAction: '/api/mmp/dataset/upload', | ||||
| uploadAccept: '.zip,.tgz', | uploadAccept: '.zip,.tgz', | ||||
| downloadAllAction: '/api/mmp/dataset/downloadAllFilesl', | |||||
| downloadSingleAction: '/api/mmp/dataset/download', | |||||
| infoTypePropertyName: 'dataset_type_name', | |||||
| infoTagPropertyName: 'dataset_tag_name', | |||||
| }, | }, | ||||
| [ResourceType.Model]: { | [ResourceType.Model]: { | ||||
| getList: getModelList, | getList: getModelList, | ||||
| getVersions: getModelVersionsById, | getVersions: getModelVersionsById, | ||||
| getFiles: getModelVersionIdList, | getFiles: getModelVersionIdList, | ||||
| deleteRecord: deleteModel, | deleteRecord: deleteModel, | ||||
| addVersion: addModelsVersionDetail, | |||||
| deleteVersion: deleteModelVersion, | |||||
| getInfo: getModelById, | |||||
| name: '模型', | name: '模型', | ||||
| typeParamKey: 'model_type', | typeParamKey: 'model_type', | ||||
| tagParamKey: 'model_tag', | tagParamKey: 'model_tag', | ||||
| @@ -101,13 +120,16 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| tagTitle: '模型能力', | tagTitle: '模型能力', | ||||
| typeValue: 3, | typeValue: 3, | ||||
| tagValue: 4, | tagValue: 4, | ||||
| iconPathPrefix: 'model', | |||||
| prefix: 'model', | |||||
| deleteModalTitle: '确定删除该条模型实例吗?', | deleteModalTitle: '确定删除该条模型实例吗?', | ||||
| addBtnTitle: '新建模型', | addBtnTitle: '新建模型', | ||||
| addVersionReq: addModelsVersionDetail, | |||||
| idParamKey: 'models_id', | idParamKey: 'models_id', | ||||
| uploadAction: '/api/mmp/models/upload', | uploadAction: '/api/mmp/models/upload', | ||||
| uploadAccept: undefined, | uploadAccept: undefined, | ||||
| downloadAllAction: '/api/mmp/models/downloadAllFiles', | |||||
| downloadSingleAction: '/api/mmp/models/download_model', | |||||
| infoTypePropertyName: 'model_type_name', | |||||
| infoTagPropertyName: 'model_tag_name', | |||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -119,11 +141,36 @@ export type CategoryData = { | |||||
| path: string; | path: string; | ||||
| }; | }; | ||||
| // 数据类型 | |||||
| // 资源数据 | |||||
| export type ResourceData = { | export type ResourceData = { | ||||
| id: number; | id: number; | ||||
| name: string; | name: string; | ||||
| description: string; | description: string; | ||||
| create_by: string; | create_by: string; | ||||
| update_time: string; | update_time: string; | ||||
| available_range: number; | |||||
| model_type_name?: string; | |||||
| model_tag_name?: string; | |||||
| dataset_type_name?: string; | |||||
| dataset_tag_name?: string; | |||||
| }; | |||||
| // 版本数据 | |||||
| export type ResourceVersionData = { | |||||
| label: string; | |||||
| value: string; | |||||
| }; | |||||
| // 版本文件数据 | |||||
| export type ResourceFileData = { | |||||
| id: number; | |||||
| file_name: string; | |||||
| file_size: string; | |||||
| description: string; | |||||
| create_by: string; | |||||
| create_time: string; | |||||
| update_by: string; | |||||
| update_time: string; | |||||
| url: string; | |||||
| version: string; | |||||
| }; | }; | ||||
| @@ -1,5 +1,5 @@ | |||||
| import ResourcePage from './components/ResourcePage'; | import ResourcePage from './components/ResourcePage'; | ||||
| import { ResourceType } from './types'; | |||||
| import { ResourceType } from './config'; | |||||
| const DatasetPage = () => { | const DatasetPage = () => { | ||||
| return <ResourcePage resourceType={ResourceType.Dataset} />; | return <ResourcePage resourceType={ResourceType.Dataset} />; | ||||
| @@ -1,263 +0,0 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { ResourceType } from '@/pages/Dataset/types'; | |||||
| import { | |||||
| deleteDatasetVersion, | |||||
| getDatasetById, | |||||
| getDatasetVersionIdList, | |||||
| getDatasetVersionsById, | |||||
| } from '@/services/dataset/index.js'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { App, Button, Input, Select, Table, Tabs } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import AddVersionModal from './components/AddVersionModal'; | |||||
| import Styles from './intro.less'; | |||||
| const { Search } = Input; | |||||
| const { TabPane } = Tabs; | |||||
| const Dataset = () => { | |||||
| const { message } = App.useApp(); | |||||
| const [formList, setFormList] = useState([]); | |||||
| const [datasetDetailObj, setDatasetDetailObj] = useState({}); | |||||
| const [version, setVersion] = useState(null); | |||||
| const [versionList, setVersionList] = useState([]); | |||||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||||
| const [searchParams] = useSearchParams(); | |||||
| const [wordList, setWordList] = useState([]); | |||||
| const [activeTabKey, setActiveTabKey] = useState('1'); | |||||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||||
| const getDatasetByDetail = () => { | |||||
| getDatasetById(locationParams.id).then((ret) => { | |||||
| console.log(ret); | |||||
| setDatasetDetailObj(ret.data); | |||||
| }); | |||||
| }; | |||||
| // 获取数据集版本 | |||||
| const getDatasetVersionList = () => { | |||||
| getDatasetVersionsById(locationParams.id).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.data && ret.data.length > 0) { | |||||
| setVersionList( | |||||
| ret.data.map((item) => { | |||||
| return { | |||||
| label: item, | |||||
| value: item, | |||||
| }; | |||||
| }), | |||||
| ); | |||||
| setVersion(ret.data[0]); | |||||
| getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id }); | |||||
| } else { | |||||
| setVersion(null); | |||||
| setWordList([]); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| getDatasetByDetail(); | |||||
| getDatasetVersionList(); | |||||
| return () => {}; | |||||
| }, []); | |||||
| const showModal = () => { | |||||
| const { close } = openAntdModal(AddVersionModal, { | |||||
| resourceType: ResourceType.Dataset, | |||||
| resourceId: locationParams.id, | |||||
| initialName: datasetDetailObj.name, | |||||
| onOk: () => { | |||||
| getDatasetVersionList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const deleteDataset = () => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该数据集版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => { | |||||
| getDatasetVersionList(); | |||||
| message.success('删除成功'); | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 获取版本下的文件列表 | |||||
| const getDatasetVersions = (params) => { | |||||
| getDatasetVersionIdList(params).then((res) => { | |||||
| setWordList(res?.data?.content ?? []); | |||||
| }); | |||||
| }; | |||||
| const handleExport = async () => { | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version }); | |||||
| }; | |||||
| const downloadAlone = (e, record) => { | |||||
| console.log(record); | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/dataset/download/${record.id}`); | |||||
| }; | |||||
| const handleChange = (value) => { | |||||
| console.log(value); | |||||
| if (value) { | |||||
| getDatasetVersions({ version: value, dataset_id: locationParams.id }); | |||||
| setVersion(value); | |||||
| } else { | |||||
| setVersion(null); | |||||
| } | |||||
| }; | |||||
| const columns = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: 80, | |||||
| render(text, record, index) { | |||||
| return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>; | |||||
| }, | |||||
| // render: (text, record, index) => `${((curPage-1)*10)+(index+1)}`, | |||||
| }, | |||||
| { | |||||
| title: '文件名称', | |||||
| dataIndex: 'file_name', | |||||
| key: 'file_name', | |||||
| render: (text, record) => <a onClick={(e) => downloadAlone(e, record)}>{text}</a>, | |||||
| }, | |||||
| { | |||||
| title: '版本号', | |||||
| dataIndex: 'version', | |||||
| key: 'version', | |||||
| }, | |||||
| { | |||||
| title: '文件大小', | |||||
| dataIndex: 'file_size', | |||||
| key: 'file_size', | |||||
| }, | |||||
| { | |||||
| title: '更新时间', | |||||
| dataIndex: 'update_time', | |||||
| key: 'update_time', | |||||
| render: (text) => <span>{formatDate(text)}</span>, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'option', | |||||
| width: '100px', | |||||
| key: 'option', | |||||
| render: (_, record) => [ | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="download" | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| onClick={(e) => downloadAlone(e, record)} | |||||
| > | |||||
| 下载 | |||||
| </Button>, | |||||
| ], | |||||
| }, | |||||
| ]; | |||||
| const pageOption = useRef({ page: 1, size: 10 }); | |||||
| // 当前页面切换 | |||||
| const paginationChange = async (current, size) => { | |||||
| console.log('page', current, size); | |||||
| pageOption.current = { | |||||
| page: current, | |||||
| size: size, | |||||
| }; | |||||
| // getList() | |||||
| }; | |||||
| return ( | |||||
| <div className={Styles.datasetBox}> | |||||
| <div className={Styles.datasetIntroTopBox}> | |||||
| <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | |||||
| <div className={Styles.smallTagBox}> | |||||
| <div className={Styles.tagItem}>数据集 id:{datasetDetailObj.id}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.dataset_type_name || '...'}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.dataset_tag_name || '...'}</div> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.datasetIntroCneterBox}> | |||||
| <Tabs activeKey={activeTabKey} onChange={(key) => setActiveTabKey(key)}> | |||||
| <TabPane tab="数据集简介" key="1"> | |||||
| <div className={Styles.datasetIntroTitle}>简介</div> | |||||
| <div className={Styles.datasetIntroText}>{datasetDetailObj.description}</div> | |||||
| </TabPane> | |||||
| <TabPane tab="数据集文件/版本" key="2"> | |||||
| <div className={Styles.dataListBox}> | |||||
| <div>数据集文件列表</div> | |||||
| <div className={Styles.dataButtonList}> | |||||
| <div | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||||
| > | |||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ | |||||
| width: 160, | |||||
| }} | |||||
| allowClear | |||||
| value={version} | |||||
| onChange={handleChange} | |||||
| options={versionList} | |||||
| /> | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| onClick={showModal} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 创建新版本 | |||||
| </Button> | |||||
| </div> | |||||
| <div | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||||
| > | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={deleteDataset} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| )} | |||||
| <Button | |||||
| type="default" | |||||
| disabled={!version} | |||||
| className={Styles.plusButton} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={handleExport} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| </div> | |||||
| </div> | |||||
| <div style={{ marginBottom: '10px', fontSize: '14px' }}> | |||||
| {wordList.length > 0 && wordList[0].description | |||||
| ? '版本描述:' + wordList[0].description | |||||
| : null} | |||||
| </div> | |||||
| <Table columns={columns} dataSource={wordList} pagination={false} rowKey="id" /> | |||||
| </div> | |||||
| </TabPane> | |||||
| </Tabs> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default Dataset; | |||||
| @@ -1,82 +0,0 @@ | |||||
| .datasetIntroTopBox { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 25px 30px; | |||||
| background-image: url(/assets/images/dataset-back.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| .smallTagBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #1664ff; | |||||
| font-size: 14px; | |||||
| .tagItem { | |||||
| margin-right: 20px; | |||||
| padding: 4px 10px; | |||||
| background: rgba(22, 100, 255, 0.1); | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .dataListBox { | |||||
| padding: 20px 30px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| font-family: alibaba; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| .dataButtonList { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin: 24px 0 30px 0; | |||||
| color: #575757; | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| .datasetIntroCneterBox { | |||||
| height: 77vh; | |||||
| padding: 20px 30px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| .datasetIntroTitle { | |||||
| margin: 37px 0 10px 0; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| } | |||||
| .datasetIntroText { | |||||
| margin-bottom: 30px; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| } | |||||
| .datasetBox { | |||||
| background: #f9fafb; | |||||
| :global { | |||||
| .ant-tabs-top > .ant-tabs-nav { | |||||
| margin: 0; | |||||
| } | |||||
| .ant-pagination { | |||||
| text-align: right; | |||||
| } | |||||
| } | |||||
| } | |||||
| .plusButton { | |||||
| margin: 0 18px 0 20px; | |||||
| } | |||||
| .tipContent { | |||||
| margin-top: 5px; | |||||
| color: #c73131; | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| import ResourceIntro from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import { ResourceType } from '@/pages/Dataset/config'; | |||||
| function DatasetIntro() { | |||||
| return <ResourceIntro resourceType={ResourceType.Dataset} />; | |||||
| } | |||||
| export default DatasetIntro; | |||||
| @@ -16,7 +16,7 @@ function DatasetAnnotation() { | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={styles.container}> | <div className={styles.container}> | ||||
| <iframe src="http://172.20.32.181:31213/label-studio" className={styles.frame}></iframe> | |||||
| {iframeUrl && <iframe src={iframeUrl} className={styles.frame}></iframe>} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,3 +1,5 @@ | |||||
| import ParameterInput from '@/components/ParameterInput'; | |||||
| import ParameterSelect from '@/components/ParameterSelect'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | import { useComputingResource } from '@/hooks/resource'; | ||||
| import { PipelineNodeModelSerialize } from '@/types'; | import { PipelineNodeModelSerialize } from '@/types'; | ||||
| @@ -122,15 +124,8 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| <TextArea disabled /> | <TextArea disabled /> | ||||
| </Form.Item> | </Form.Item> | ||||
| {controlStrategyList.map((item) => ( | {controlStrategyList.map((item) => ( | ||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['control_strategy', item.key]} | |||||
| label={item.value.label} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.showValue || e.value }; | |||||
| }} | |||||
| > | |||||
| <Input disabled /> | |||||
| <Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}> | |||||
| <ParameterInput disabled /> | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| <div className={styles['experiment-parameter__title']}> | <div className={styles['experiment-parameter__title']}> | ||||
| @@ -142,11 +137,12 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| name={['in_parameters', item.key]} | name={['in_parameters', item.key]} | ||||
| label={item.value.label + '(' + item.key + ')'} | label={item.value.label + '(' + item.key + ')'} | ||||
| rules={[{ required: item.value.require ? true : false }]} | rules={[{ required: item.value.require ? true : false }]} | ||||
| getValueProps={(e) => { | |||||
| return { value: e.showValue || e.value }; | |||||
| }} | |||||
| > | > | ||||
| <Input disabled /> | |||||
| {item.value.type === 'select' ? ( | |||||
| <ParameterSelect disabled /> | |||||
| ) : ( | |||||
| <ParameterInput disabled /> | |||||
| )} | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| <div className={styles['experiment-parameter__title']}> | <div className={styles['experiment-parameter__title']}> | ||||
| @@ -158,11 +154,8 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| name={['out_parameters', item.key]} | name={['out_parameters', item.key]} | ||||
| label={item.value.label + '(' + item.key + ')'} | label={item.value.label + '(' + item.key + ')'} | ||||
| rules={[{ required: item.value.require ? true : false }]} | rules={[{ required: item.value.require ? true : false }]} | ||||
| getValueProps={(e) => { | |||||
| return { value: e.showValue || e.value }; | |||||
| }} | |||||
| > | > | ||||
| <Input disabled /> | |||||
| <ParameterInput disabled /> | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| </Form> | </Form> | ||||
| @@ -35,4 +35,9 @@ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| &__empty { | |||||
| margin-top: 10px; | |||||
| text-align: center; | |||||
| } | |||||
| } | } | ||||
| @@ -23,34 +23,38 @@ function ExperimentResult({ results }: ExperimentResultProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-result']}> | <div className={styles['experiment-result']}> | ||||
| <div className={styles['experiment-result__content']}> | <div className={styles['experiment-result__content']}> | ||||
| {results?.map((item) => ( | |||||
| <div key={item.name} className={styles['experiment-result__item']}> | |||||
| <div className={styles['experiment-result__item__name']}> | |||||
| <span>{item.name}</span> | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| onClick={() => { | |||||
| exportResult(item.path); | |||||
| }} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| {/* <a style={{ marginRight: '10px' }}>导出到模型库</a> | |||||
| {results && results.length > 0 ? ( | |||||
| results.map((item) => ( | |||||
| <div key={item.name} className={styles['experiment-result__item']}> | |||||
| <div className={styles['experiment-result__item__name']}> | |||||
| <span>{item.name}</span> | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| onClick={() => { | |||||
| exportResult(item.path); | |||||
| }} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| {/* <a style={{ marginRight: '10px' }}>导出到模型库</a> | |||||
| <a style={{ marginRight: '10px' }}>导出到数据集</a> */} | <a style={{ marginRight: '10px' }}>导出到数据集</a> */} | ||||
| </div> | |||||
| <div style={{ margin: '15px 0' }} className={styles['experiment-result__item__file']}> | |||||
| <span>文件名称</span> | |||||
| <span>文件大小</span> | |||||
| </div> | |||||
| {item.value?.map((ele) => ( | |||||
| <div className={styles['experiment-result__item__file']} key={ele.name}> | |||||
| <span>{ele.name}</span> | |||||
| <span>{ele.size}</span> | |||||
| </div> | </div> | ||||
| ))} | |||||
| </div> | |||||
| ))} | |||||
| <div style={{ margin: '15px 0' }} className={styles['experiment-result__item__file']}> | |||||
| <span>文件名称</span> | |||||
| <span>文件大小</span> | |||||
| </div> | |||||
| {item.value?.map((ele) => ( | |||||
| <div className={styles['experiment-result__item__file']} key={ele.name}> | |||||
| <span>{ele.name}</span> | |||||
| <span>{ele.size}</span> | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| )) | |||||
| ) : ( | |||||
| <div className={styles['experiment-result__empty']}>暂无结果</div> | |||||
| )} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -21,8 +21,13 @@ | |||||
| color: white; | color: white; | ||||
| font-size: 14px; | font-size: 14px; | ||||
| white-space: pre-line; | white-space: pre-line; | ||||
| text-align: left; | |||||
| word-break: break-all; | word-break: break-all; | ||||
| background: #19253b; | background: #19253b; | ||||
| &--empty { | |||||
| text-align: center; | |||||
| } | |||||
| } | } | ||||
| &__more-button { | &__more-button { | ||||
| @@ -10,6 +10,7 @@ import { ExperimentLog } from '@/pages/Experiment/training/props'; | |||||
| import { getExperimentPodsLog } from '@/services/experiment/index.js'; | import { getExperimentPodsLog } from '@/services/experiment/index.js'; | ||||
| import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -22,6 +23,21 @@ type Log = { | |||||
| log_content: string; // 日志内容 | log_content: string; // 日志内容 | ||||
| }; | }; | ||||
| // 滚动到底部 | |||||
| const scrollToBottom = (smooth: boolean = true) => { | |||||
| const element = document.getElementsByClassName('ant-tabs-content-holder')?.[0]; | |||||
| if (element) { | |||||
| if (smooth) { | |||||
| element.scrollTo({ | |||||
| top: element.scrollHeight, | |||||
| behavior: 'smooth', | |||||
| }); | |||||
| } else { | |||||
| element.scrollTo({ top: element.scrollHeight }); | |||||
| } | |||||
| } | |||||
| }; | |||||
| function LogGroup({ | function LogGroup({ | ||||
| log_type = 'normal', | log_type = 'normal', | ||||
| pod_name = '', | pod_name = '', | ||||
| @@ -32,8 +48,11 @@ function LogGroup({ | |||||
| const [collapse, setCollapse] = useState(true); | const [collapse, setCollapse] = useState(true); | ||||
| const [logList, setLogList, logListRef] = useStateRef<Log[]>([]); | const [logList, setLogList, logListRef] = useStateRef<Log[]>([]); | ||||
| const [completed, setCompleted] = useState(false); | const [completed, setCompleted] = useState(false); | ||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |||||
| const [isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| scrollToBottom(false); | |||||
| if (status === ExperimentStatus.Running) { | if (status === ExperimentStatus.Running) { | ||||
| const timerId = setInterval(() => { | const timerId = setInterval(() => { | ||||
| requestExperimentPodsLog(); | requestExperimentPodsLog(); | ||||
| @@ -44,6 +63,24 @@ function LogGroup({ | |||||
| } | } | ||||
| }, []); | }, []); | ||||
| useEffect(() => { | |||||
| const mouseDown = () => { | |||||
| setIsMouseDown(true); | |||||
| }; | |||||
| const mouseUp = () => { | |||||
| setIsMouseDown(false); | |||||
| }; | |||||
| document.addEventListener('mousedown', mouseDown); | |||||
| document.addEventListener('mouseup', mouseUp); | |||||
| return () => { | |||||
| document.removeEventListener('mousedown', mouseDown); | |||||
| document.removeEventListener('mouseup', mouseUp); | |||||
| }; | |||||
| }, []); | |||||
| // 请求日志 | // 请求日志 | ||||
| const requestExperimentPodsLog = async () => { | const requestExperimentPodsLog = async () => { | ||||
| const list = logListRef.current; | const list = logListRef.current; | ||||
| @@ -54,9 +91,18 @@ function LogGroup({ | |||||
| }; | }; | ||||
| const res = await getExperimentPodsLog(params); | const res = await getExperimentPodsLog(params); | ||||
| const { log_detail } = res.data; | const { log_detail } = res.data; | ||||
| if (log_detail && log_detail.log_content) { | |||||
| if (log_detail) { | |||||
| setLogList((oldList) => oldList.concat(log_detail)); | setLogList((oldList) => oldList.concat(log_detail)); | ||||
| } else { | |||||
| if (!isMouseDownRef.current && log_detail.log_content) { | |||||
| setTimeout(() => { | |||||
| scrollToBottom(); | |||||
| }, 100); | |||||
| } | |||||
| } | |||||
| // 判断是否日志是否加载完成 | |||||
| if (!log_detail?.log_content) { | |||||
| setCompleted(true); | setCompleted(true); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -96,7 +142,15 @@ function LogGroup({ | |||||
| {collapse ? <DownOutlined /> : <UpOutlined />} | {collapse ? <DownOutlined /> : <UpOutlined />} | ||||
| </div> | </div> | ||||
| )} | )} | ||||
| {showLog && <div className={styles['log-group__detail']}>{logText}</div>} | |||||
| {showLog && ( | |||||
| <div | |||||
| className={classNames(styles['log-group__detail'], { | |||||
| [styles['log-group__detail--empty']]: !logText, | |||||
| })} | |||||
| > | |||||
| {logText ? logText : '暂无日志'} | |||||
| </div> | |||||
| )} | |||||
| <div className={styles['log-group__more-button']}> | <div className={styles['log-group__more-button']}> | ||||
| {showMoreBtn && ( | {showMoreBtn && ( | ||||
| <Button | <Button | ||||
| @@ -1,3 +1,13 @@ | |||||
| .log-list { | .log-list { | ||||
| padding: 8px; | padding: 8px; | ||||
| &__empty { | |||||
| padding: 15px; | |||||
| color: white; | |||||
| font-size: 14px; | |||||
| white-space: pre-line; | |||||
| text-align: center; | |||||
| word-break: break-all; | |||||
| background: #19253b; | |||||
| } | |||||
| } | } | ||||
| @@ -11,9 +11,11 @@ type LogListProps = { | |||||
| function LogList({ list = [], status }: LogListProps) { | function LogList({ list = [], status }: LogListProps) { | ||||
| return ( | return ( | ||||
| <div className={styles['log-list']}> | <div className={styles['log-list']}> | ||||
| {list.map((v) => ( | |||||
| <LogGroup key={v.pod_name} {...v} status={status} /> | |||||
| ))} | |||||
| {list.length > 0 ? ( | |||||
| list.map((v) => <LogGroup key={v.pod_name} {...v} status={status} />) | |||||
| ) : ( | |||||
| <div className={styles['log-list__empty']}>暂无日志</div> | |||||
| )} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -8,15 +8,15 @@ | |||||
| font-size: 15px; | font-size: 15px; | ||||
| &--running { | &--running { | ||||
| color: #6ac21d; | |||||
| color: @success-color; | |||||
| } | } | ||||
| &--failed { | &--failed { | ||||
| color: #df6d6d; | |||||
| color: @error-color; | |||||
| } | } | ||||
| } | } | ||||
| &__icon { | &__icon { | ||||
| width: 14px; | width: 14px; | ||||
| color: #6ac21d; | |||||
| color: @success-color; | |||||
| cursor: pointer; | cursor: pointer; | ||||
| & + & { | & + & { | ||||
| @@ -11,8 +11,8 @@ | |||||
| margin-bottom: 15px; | margin-bottom: 15px; | ||||
| &_label { | &_label { | ||||
| width: 120px; | |||||
| color: #1d1d20; | |||||
| width: 180px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | font-size: 15px; | ||||
| } | } | ||||
| &_value { | &_value { | ||||
| @@ -20,8 +20,8 @@ | |||||
| width: 100px; | width: 100px; | ||||
| margin-left: 15px; | margin-left: 15px; | ||||
| padding: 10px 20px; | padding: 10px 20px; | ||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| line-height: 20px; | line-height: 20px; | ||||
| background: #f6f6f6; | background: #f6f6f6; | ||||
| border: 1px solid #e0e0e1; | border: 1px solid #e0e0e1; | ||||
| @@ -18,7 +18,7 @@ import themes from '@/styles/theme.less'; | |||||
| import { elapsedTime, formatDate } from '@/utils/date'; | import { elapsedTime, formatDate } from '@/utils/date'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { App, Button, ConfigProvider, Space, Table } from 'antd'; | |||||
| import { App, Button, ConfigProvider, Space, Table, Tooltip } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| @@ -84,7 +84,6 @@ function Experiment() { | |||||
| // 获取实验实例 | // 获取实验实例 | ||||
| const getQueryByExperiment = (val) => { | const getQueryByExperiment = (val) => { | ||||
| getQueryByExperimentId(val).then((ret) => { | getQueryByExperimentId(val).then((ret) => { | ||||
| console.log(val); | |||||
| setExpandedRowKeys(val); | setExpandedRowKeys(val); | ||||
| if (ret && ret.data && ret.data.length > 0) { | if (ret && ret.data && ret.data.length > 0) { | ||||
| try { | try { | ||||
| @@ -162,7 +161,6 @@ function Experiment() { | |||||
| }; | }; | ||||
| const expandChange = (e, record) => { | const expandChange = (e, record) => { | ||||
| clearExperimentInTimers(); | clearExperimentInTimers(); | ||||
| console.log(e, record); | |||||
| if (record.id === expandedRowKeys) { | if (record.id === expandedRowKeys) { | ||||
| setExpandedRowKeys(null); | setExpandedRowKeys(null); | ||||
| } else { | } else { | ||||
| @@ -238,7 +236,6 @@ function Experiment() { | |||||
| }; | }; | ||||
| // 当前页面切换 | // 当前页面切换 | ||||
| const paginationChange = async (current, size) => { | const paginationChange = async (current, size) => { | ||||
| console.log('page', current, size); | |||||
| pageOption.current = { | pageOption.current = { | ||||
| page: current, | page: current, | ||||
| size: size, | size: size, | ||||
| @@ -279,14 +276,14 @@ function Experiment() { | |||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| render: (text) => <div>{text}</div>, | render: (text) => <div>{text}</div>, | ||||
| width: '20%', | |||||
| width: '16%', | |||||
| }, | }, | ||||
| { | { | ||||
| title: '关联流水线名称', | title: '关联流水线名称', | ||||
| dataIndex: 'workflow_name', | dataIndex: 'workflow_name', | ||||
| key: 'workflow_name', | key: 'workflow_name', | ||||
| render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>, | render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>, | ||||
| width: '20%', | |||||
| width: '16%', | |||||
| }, | }, | ||||
| { | { | ||||
| title: '实验描述', | title: '实验描述', | ||||
| @@ -443,13 +440,17 @@ function Experiment() { | |||||
| <div style={{ width: '50%' }}> | <div style={{ width: '50%' }}> | ||||
| {elapsedTime(item.create_time, item.finish_time)} | {elapsedTime(item.create_time, item.finish_time)} | ||||
| </div> | </div> | ||||
| <div style={{ width: '50%' }}>{formatDate(item.create_time)}</div> | |||||
| <div style={{ width: '50%' }} className={Styles.startTime}> | |||||
| <Tooltip title={formatDate(item.create_time)}> | |||||
| <span>{formatDate(item.create_time)}</span> | |||||
| </Tooltip> | |||||
| </div> | |||||
| </div> | </div> | ||||
| <div className={Styles.statusBox}> | <div className={Styles.statusBox}> | ||||
| <img | <img | ||||
| style={{ width: '17px', marginRight: '7px' }} | style={{ width: '17px', marginRight: '7px' }} | ||||
| src={experimentStatusInfo[item.status]?.icon} | src={experimentStatusInfo[item.status]?.icon} | ||||
| />{' '} | |||||
| /> | |||||
| <span | <span | ||||
| style={{ color: experimentStatusInfo[item.status]?.color }} | style={{ color: experimentStatusInfo[item.status]?.color }} | ||||
| className={Styles.statusIcon} | className={Styles.statusIcon} | ||||
| @@ -36,17 +36,21 @@ | |||||
| } | } | ||||
| .index { | .index { | ||||
| width: calc((100% + 32px + 33px) / 5); | |||||
| width: calc((100% + 32px + 33px) / 6.25); | |||||
| } | } | ||||
| .tensorBoard { | .tensorBoard { | ||||
| width: calc((100% + 32px + 33px) / 5); | |||||
| width: calc((100% + 32px + 33px) / 6.25); | |||||
| } | } | ||||
| .description { | .description { | ||||
| display: flex; | display: flex; | ||||
| flex: 1; | flex: 1; | ||||
| align-items: center; | align-items: center; | ||||
| .startTime { | |||||
| .singleLine(); | |||||
| } | |||||
| } | } | ||||
| .status { | .status { | ||||
| @@ -80,8 +84,9 @@ | |||||
| .statusBox:hover .statusIcon { | .statusBox:hover .statusIcon { | ||||
| visibility: visible; | visibility: visible; | ||||
| } | } | ||||
| .experimentBox { | .experimentBox { | ||||
| height: calc(100% - 20px); | |||||
| height: 100%; | |||||
| .experimentTable { | .experimentTable { | ||||
| height: calc(100% - 60px); | height: calc(100% - 60px); | ||||
| :global { | :global { | ||||
| @@ -1,3 +1,5 @@ | |||||
| import themes from '@/styles/theme.less'; | |||||
| export interface StatusInfo { | export interface StatusInfo { | ||||
| label: string; | label: string; | ||||
| color: string; | color: string; | ||||
| @@ -18,42 +20,42 @@ export enum ExperimentStatus { | |||||
| export const experimentStatusInfo: Record<ExperimentStatus, StatusInfo | undefined> = { | export const experimentStatusInfo: Record<ExperimentStatus, StatusInfo | undefined> = { | ||||
| Running: { | Running: { | ||||
| label: '运行中', | label: '运行中', | ||||
| color: '#1664ff', | |||||
| color: themes.primaryColor, | |||||
| icon: '/assets/images/running-icon.png', | icon: '/assets/images/running-icon.png', | ||||
| }, | }, | ||||
| Succeeded: { | Succeeded: { | ||||
| label: '成功', | label: '成功', | ||||
| color: '#63a728', | |||||
| color: themes.successColor, | |||||
| icon: '/assets/images/success-icon.png', | icon: '/assets/images/success-icon.png', | ||||
| }, | }, | ||||
| Pending: { | Pending: { | ||||
| label: '等待中', | label: '等待中', | ||||
| color: '#f981eb', | |||||
| color: themes.pendingColor, | |||||
| icon: '/assets/images/pending-icon.png', | icon: '/assets/images/pending-icon.png', | ||||
| }, | }, | ||||
| Failed: { | Failed: { | ||||
| label: '失败', | label: '失败', | ||||
| color: '#c73131', | |||||
| color: themes.errorColor, | |||||
| icon: '/assets/images/fail-icon.png', | icon: '/assets/images/fail-icon.png', | ||||
| }, | }, | ||||
| Error: { | Error: { | ||||
| label: '错误', | label: '错误', | ||||
| color: '#c73131', | |||||
| color: themes.errorColor, | |||||
| icon: '/assets/images/fail-icon.png', | icon: '/assets/images/fail-icon.png', | ||||
| }, | }, | ||||
| Terminated: { | Terminated: { | ||||
| label: '终止', | label: '终止', | ||||
| color: '#8a8a8a', | |||||
| color: themes.abortColor, | |||||
| icon: '/assets/images/omitted-icon.png', | icon: '/assets/images/omitted-icon.png', | ||||
| }, | }, | ||||
| Skipped: { | Skipped: { | ||||
| label: '未执行', | label: '未执行', | ||||
| color: '#8a8a8a', | |||||
| color: themes.abortColor, | |||||
| icon: '/assets/images/omitted-icon.png', | icon: '/assets/images/omitted-icon.png', | ||||
| }, | }, | ||||
| Omitted: { | Omitted: { | ||||
| label: '未执行', | label: '未执行', | ||||
| color: '#8a8a8a', | |||||
| color: themes.abortColor, | |||||
| icon: '/assets/images/omitted-icon.png', | icon: '/assets/images/omitted-icon.png', | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -1,12 +1,13 @@ | |||||
| import { useStateRef, useVisible } from '@/hooks'; | import { useStateRef, useVisible } from '@/hooks'; | ||||
| import { getExperimentIns } from '@/services/experiment/index.js'; | import { getExperimentIns } from '@/services/experiment/index.js'; | ||||
| import { getWorkflowById } from '@/services/pipeline/index.js'; | import { getWorkflowById } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | |||||
| import { fittingString } from '@/utils'; | |||||
| import { elapsedTime, formatDate } from '@/utils/date'; | import { elapsedTime, formatDate } from '@/utils/date'; | ||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| import { useEffect, useRef } from 'react'; | import { useEffect, useRef } from 'react'; | ||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||
| import { s8 } from '../../../utils'; | |||||
| import ParamsModal from '../components/ViewParamsModal'; | import ParamsModal from '../components/ViewParamsModal'; | ||||
| import { experimentStatusInfo } from '../status'; | import { experimentStatusInfo } from '../status'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -22,27 +23,22 @@ function ExperimentText() { | |||||
| const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); | const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); | ||||
| const graphRef = useRef(); | const graphRef = useRef(); | ||||
| const onDragEnd = (val) => { | |||||
| console.log(val, 'eee'); | |||||
| const _x = val.x; | |||||
| const _y = val.y; | |||||
| const point = graph.getPointByClient(_x, _y); | |||||
| let model = {}; | |||||
| // 元模型 | |||||
| model = { | |||||
| ...val, | |||||
| x: point.x, | |||||
| y: point.y, | |||||
| id: val.component_name + '-' + s8(), | |||||
| isCluster: false, | |||||
| }; | |||||
| graph.addItem('node', model, true); | |||||
| }; | |||||
| const handlerClick = (e) => { | |||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||||
| propsRef.current.showDrawer(e, locationParams.id, messageRef.current); | |||||
| } | |||||
| }; | |||||
| // const onDragEnd = (val) => { | |||||
| // console.log(val, 'eee'); | |||||
| // const _x = val.x; | |||||
| // const _y = val.y; | |||||
| // const point = graph.getPointByClient(_x, _y); | |||||
| // let model = {}; | |||||
| // // 元模型 | |||||
| // model = { | |||||
| // ...val, | |||||
| // x: point.x, | |||||
| // y: point.y, | |||||
| // id: val.component_name + '-' + s8(), | |||||
| // isCluster: false, | |||||
| // }; | |||||
| // graph.addItem('node', model, true); | |||||
| // }; | |||||
| const getGraphData = (data) => { | const getGraphData = (data) => { | ||||
| if (graph) { | if (graph) { | ||||
| graph.data(data); | graph.data(data); | ||||
| @@ -89,32 +85,6 @@ function ExperimentText() { | |||||
| }, []); | }, []); | ||||
| const initGraph = () => { | const initGraph = () => { | ||||
| const fittingString = (str, maxWidth, fontSize) => { | |||||
| const ellipsis = '...'; | |||||
| const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0]; | |||||
| let currentWidth = 0; | |||||
| let res = str; | |||||
| const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters | |||||
| str.split('').forEach((letter, i) => { | |||||
| if (currentWidth > maxWidth - ellipsisLength) return; | |||||
| if (pattern.test(letter)) { | |||||
| // Chinese charactors | |||||
| currentWidth += fontSize; | |||||
| } else { | |||||
| // get the width of single letter according to the fontSize | |||||
| currentWidth += G6.Util.getLetterWidth(letter, fontSize); | |||||
| } | |||||
| if (currentWidth > maxWidth - ellipsisLength) { | |||||
| res = `${str.substr(0, i)}${ellipsis}`; | |||||
| } | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| // 获取文本的长度 | |||||
| const getTextSize = (str, maxWidth, fontSize) => { | |||||
| let width = G6.Util.getTextSize(str, fontSize)[0]; | |||||
| return width > maxWidth ? maxWidth : width; | |||||
| }; | |||||
| G6.registerNode( | G6.registerNode( | ||||
| 'rect-node', | 'rect-node', | ||||
| { | { | ||||
| @@ -129,7 +99,6 @@ function ExperimentText() { | |||||
| ); | ); | ||||
| }, | }, | ||||
| afterDraw(cfg, group) { | afterDraw(cfg, group) { | ||||
| // console.log(group, cfg, 12312); | |||||
| const image = group.addShape('image', { | const image = group.addShape('image', { | ||||
| attrs: { | attrs: { | ||||
| x: -45, | x: -45, | ||||
| @@ -158,7 +127,6 @@ function ExperimentText() { | |||||
| } | } | ||||
| const bbox = group.getBBox(); | const bbox = group.getBBox(); | ||||
| const anchorPoints = this.getAnchorPoints(cfg); | const anchorPoints = this.getAnchorPoints(cfg); | ||||
| // console.log(anchorPoints); | |||||
| anchorPoints.forEach((anchorPos, i) => { | anchorPoints.forEach((anchorPos, i) => { | ||||
| group.addShape('circle', { | group.addShape('circle', { | ||||
| attrs: { | attrs: { | ||||
| @@ -179,19 +147,19 @@ function ExperimentText() { | |||||
| // response the state changes and show/hide the link-point circles | // response the state changes and show/hide the link-point circles | ||||
| setState(name, value, item) { | setState(name, value, item) { | ||||
| const anchorPoints = item | |||||
| .getContainer() | |||||
| .findAll((ele) => ele.get('name') === 'anchor-point'); | |||||
| anchorPoints.forEach((point) => { | |||||
| if (value || point.get('links') > 0) point.show(); | |||||
| else point.hide(); | |||||
| }); | |||||
| // } | |||||
| const group = item.getContainer(); | |||||
| const shape = group.get('children')[0]; | |||||
| if (name === 'hover') { | |||||
| if (value) { | |||||
| shape.attr('stroke', themes['primaryColor']); | |||||
| } else { | |||||
| shape.attr('stroke', '#fff'); | |||||
| } | |||||
| } | |||||
| }, | }, | ||||
| }, | }, | ||||
| 'rect', | 'rect', | ||||
| ); | ); | ||||
| console.log(graphRef, 'graphRef'); | |||||
| graph = new G6.Graph({ | graph = new G6.Graph({ | ||||
| container: graphRef.current, | container: graphRef.current, | ||||
| grid: true, | grid: true, | ||||
| @@ -209,10 +177,6 @@ function ExperimentText() { | |||||
| if (e.target.get('name') === 'anchor-point') return false; | if (e.target.get('name') === 'anchor-point') return false; | ||||
| return true; | return true; | ||||
| }, | }, | ||||
| // shouldEnd: e => { | |||||
| // console.log(e); | |||||
| // return false; | |||||
| // }, | |||||
| }, | }, | ||||
| // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles | // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles | ||||
| 'drag-canvas', | 'drag-canvas', | ||||
| @@ -237,7 +201,6 @@ function ExperimentText() { | |||||
| style: { | style: { | ||||
| fill: '#000', | fill: '#000', | ||||
| fontSize: 10, | fontSize: 10, | ||||
| cursor: 'pointer', | cursor: 'pointer', | ||||
| x: -20, | x: -20, | ||||
| y: 0, | y: 0, | ||||
| @@ -252,17 +215,6 @@ function ExperimentText() { | |||||
| lineWidth: 0.5, | lineWidth: 0.5, | ||||
| }, | }, | ||||
| }, | }, | ||||
| nodeStateStyles: { | |||||
| nodeSelected: { | |||||
| fill: 'red', | |||||
| shadowColor: 'red', | |||||
| stroke: 'red', | |||||
| 'text-shape': { | |||||
| fill: 'red', | |||||
| stroke: 'red', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| defaultEdge: { | defaultEdge: { | ||||
| // type: 'quadratic', | // type: 'quadratic', | ||||
| type: 'cubic-vertical', | type: 'cubic-vertical', | ||||
| @@ -308,15 +260,25 @@ function ExperimentText() { | |||||
| // linkCenter: true, | // linkCenter: true, | ||||
| fitView: true, | fitView: true, | ||||
| minZoom: 0.5, | minZoom: 0.5, | ||||
| maxZoom: 3, | |||||
| fitViewPadding: [320, 320, 220, 320], | |||||
| maxZoom: 5, | |||||
| fitViewPadding: 300, | |||||
| }); | |||||
| graph.on('node:click', (e) => { | |||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||||
| propsRef.current.showDrawer(e, locationParams.id, messageRef.current); | |||||
| } | |||||
| }); | |||||
| graph.on('node:mouseenter', (e) => { | |||||
| graph.setItemState(e.item, 'hover', true); | |||||
| }); | |||||
| graph.on('node:mouseleave', (e) => { | |||||
| graph.setItemState(e.item, 'hover', false); | |||||
| }); | }); | ||||
| graph.on('node:click', handlerClick); | |||||
| window.onresize = () => { | window.onresize = () => { | ||||
| if (!graph || graph.get('destroyed')) return; | if (!graph || graph.get('destroyed')) return; | ||||
| if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight) | |||||
| return; | |||||
| graph.changeSize(graphRef.current.scrollWidth, graphRef.current.scrollHeight - 20); | |||||
| if (!graphRef.current) return; | |||||
| graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); | |||||
| graph.fitView(); | |||||
| }; | }; | ||||
| }; | }; | ||||
| return ( | return ( | ||||
| @@ -109,7 +109,8 @@ const Props = forwardRef((_, ref) => { | |||||
| // 获取实验日志和实验结果 | // 获取实验日志和实验结果 | ||||
| setExperimentLogList([]); | setExperimentLogList([]); | ||||
| setExperimentResults([]); | setExperimentResults([]); | ||||
| if (e.item && e.item.getModel()) { | |||||
| // 如果已经运行到了 | |||||
| if (e.item?.getModel()?.component_id) { | |||||
| const model = e.item.getModel(); | const model = e.item.getModel(); | ||||
| const start_time = dayjs(model.experimentStartTime).valueOf() * 1.0e6; | const start_time = dayjs(model.experimentStartTime).valueOf() * 1.0e6; | ||||
| const params = { | const params = { | ||||
| @@ -20,7 +20,7 @@ import { formatDate } from '@/utils/date'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage'; | import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useNavigate, useParams, useSearchParams } from '@umijs/max'; | |||||
| import { useNavigate, useParams } from '@umijs/max'; | |||||
| import { | import { | ||||
| App, | App, | ||||
| Button, | Button, | ||||
| @@ -33,7 +33,7 @@ import { | |||||
| type TableProps, | type TableProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useEffect, useMemo, useState } from 'react'; | |||||
| import MirrorStatusCell from '../components/MirrorStatusCell'; | import MirrorStatusCell from '../components/MirrorStatusCell'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -42,6 +42,7 @@ type MirrorInfoData = { | |||||
| description?: string; | description?: string; | ||||
| version_count?: string; | version_count?: string; | ||||
| create_time?: string; | create_time?: string; | ||||
| image_type?: number; | |||||
| }; | }; | ||||
| type MirrorVersionData = { | type MirrorVersionData = { | ||||
| @@ -56,7 +57,6 @@ type MirrorVersionData = { | |||||
| function MirrorInfo() { | function MirrorInfo() { | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const urlParams = useParams(); | const urlParams = useParams(); | ||||
| const [searchParams] = useSearchParams(); | |||||
| const [cacheState, setCacheState] = useCacheState(); | const [cacheState, setCacheState] = useCacheState(); | ||||
| const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({}); | const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({}); | ||||
| const [tableData, setTableData] = useState<MirrorVersionData[]>([]); | const [tableData, setTableData] = useState<MirrorVersionData[]>([]); | ||||
| @@ -69,7 +69,7 @@ function MirrorInfo() { | |||||
| }, | }, | ||||
| ); | ); | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||||
| const isPublic = useMemo(() => mirrorInfo.image_type === 1, [mirrorInfo]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getMirrorInfo(); | getMirrorInfo(); | ||||
| @@ -84,14 +84,7 @@ function MirrorInfo() { | |||||
| const id = Number(urlParams.id); | const id = Number(urlParams.id); | ||||
| const [res] = await to(getMirrorInfoReq(id)); | const [res] = await to(getMirrorInfoReq(id)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { name = '', description = '', version_count = '', create_time: time } = res.data; | |||||
| const create_time = formatDate(time); | |||||
| setMirrorInfo({ | |||||
| name, | |||||
| description, | |||||
| version_count, | |||||
| create_time, | |||||
| }); | |||||
| setMirrorInfo(res.data); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -258,7 +251,7 @@ function MirrorInfo() { | |||||
| <Col span={10}> | <Col span={10}> | ||||
| <div className={styles['mirror-info__basic__item']}> | <div className={styles['mirror-info__basic__item']}> | ||||
| <div className={styles['label']}>创建时间:</div> | <div className={styles['label']}>创建时间:</div> | ||||
| <div className={styles['value']}>{mirrorInfo.create_time}</div> | |||||
| <div className={styles['value']}>{formatDate(mirrorInfo.create_time)}</div> | |||||
| </div> | </div> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -270,7 +263,7 @@ function MirrorInfo() { | |||||
| ></SubAreaTitle> | ></SubAreaTitle> | ||||
| {!isPublic && ( | {!isPublic && ( | ||||
| <Button | <Button | ||||
| style={{ marginRight: 0, marginLeft: 'auto' }} | |||||
| style={{ marginLeft: 'auto' }} | |||||
| type="default" | type="default" | ||||
| onClick={createMirrorVersion} | onClick={createMirrorVersion} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| @@ -279,7 +272,7 @@ function MirrorInfo() { | |||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| <Button | <Button | ||||
| style={{ marginLeft: '20px' }} | |||||
| style={{ marginLeft: isPublic ? 'auto' : '20px', marginRight: 0 }} | |||||
| type="default" | type="default" | ||||
| onClick={getMirrorVersionList} | onClick={getMirrorVersionList} | ||||
| icon={<KFIcon type="icon-shuaxin" />} | icon={<KFIcon type="icon-shuaxin" />} | ||||
| @@ -125,7 +125,7 @@ function MirrorList() { | |||||
| // 查看详情 | // 查看详情 | ||||
| const toDetail = (record: MirrorData) => { | const toDetail = (record: MirrorData) => { | ||||
| navigate(`/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`); | |||||
| navigate(`/dataset/mirror/${record.id}`); | |||||
| setCacheState({ | setCacheState({ | ||||
| activeTab, | activeTab, | ||||
| pagination, | pagination, | ||||
| @@ -0,0 +1,17 @@ | |||||
| .graph-legend { | |||||
| &__item { | |||||
| margin-right: 20px; | |||||
| color: @text-color; | |||||
| font-size: @font-size-content; | |||||
| &:last-child { | |||||
| margin-right: 0; | |||||
| } | |||||
| &__name { | |||||
| margin-left: 10px; | |||||
| color: @text-color-secondary; | |||||
| font-size: @font-size-content; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,55 @@ | |||||
| import { Flex } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| type GraphLegandData = { | |||||
| name: string; | |||||
| color: string; | |||||
| radius: number; | |||||
| fill: boolean; | |||||
| }; | |||||
| type GraphLegandProps = { | |||||
| style?: React.CSSProperties; | |||||
| }; | |||||
| function GraphLegand({ style }: GraphLegandProps) { | |||||
| const legends: GraphLegandData[] = [ | |||||
| { | |||||
| name: '父模型', | |||||
| color: '#76b1ff', | |||||
| radius: 2, | |||||
| fill: true, | |||||
| }, | |||||
| { | |||||
| name: '当前模型', | |||||
| color: '#1664ff', | |||||
| radius: 2, | |||||
| fill: true, | |||||
| }, | |||||
| { | |||||
| name: '衍生模型', | |||||
| color: '#b7cfff', | |||||
| radius: 2, | |||||
| fill: true, | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <Flex align="center" className={styles['graph-legend']} style={style}> | |||||
| {legends.map((item) => ( | |||||
| <Flex align="center" key={item.name} className={styles['graph-legend__item']}> | |||||
| <div | |||||
| style={{ | |||||
| width: '16px', | |||||
| height: '12px', | |||||
| borderRadius: item.radius, | |||||
| backgroundColor: item.color, | |||||
| }} | |||||
| ></div> | |||||
| <div className={styles['graph-legend__item__name']}>{item.name}</div> | |||||
| </Flex> | |||||
| ))} | |||||
| </Flex> | |||||
| ); | |||||
| } | |||||
| export default GraphLegand; | |||||
| @@ -0,0 +1,18 @@ | |||||
| .model-evolution { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| background-color: white; | |||||
| &__top { | |||||
| padding: 30px 0; | |||||
| color: @text-color; | |||||
| font-size: @font-size-content; | |||||
| } | |||||
| &__graph { | |||||
| height: calc(100% - 92px); | |||||
| background-color: @background-color; | |||||
| background-image: url(/assets/images/pipeline-canvas-back.png); | |||||
| background-size: 100% 100%; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,521 @@ | |||||
| import { useEffectWhen } from '@/hooks'; | |||||
| import { ResourceVersionData } from '@/pages/Dataset/config'; | |||||
| import { getModelAtlasReq } from '@/services/dataset/index.js'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { changePropertyName, fittingString } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import G6, { | |||||
| EdgeConfig, | |||||
| G6GraphEvent, | |||||
| Graph, | |||||
| GraphData, | |||||
| LayoutConfig, | |||||
| NodeConfig, | |||||
| TreeGraphData, | |||||
| Util, | |||||
| } from '@antv/g6'; | |||||
| // @ts-ignore | |||||
| import Hierarchy from '@antv/hierarchy'; | |||||
| import { Flex, Select } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import GraphLegand from '../GraphLegand'; | |||||
| import NodeTooltips from '../NodeTooltips'; | |||||
| import styles from './index.less'; | |||||
| const nodeWidth = 98; | |||||
| const nodeHeight = 58; | |||||
| const vGap = 58; | |||||
| const hGap = 58; | |||||
| enum NodeType { | |||||
| current = 'current', | |||||
| parent = 'parent', | |||||
| children = 'children', | |||||
| project = 'project', | |||||
| trainDataset = 'trainDataset', | |||||
| testDataset = 'testDataset', | |||||
| } | |||||
| type TrainTask = { | |||||
| ins_id: number; | |||||
| name: string; | |||||
| task_id: string; | |||||
| }; | |||||
| interface TrainDataset extends NodeConfig { | |||||
| dataset_id: number; | |||||
| dataset_name: string; | |||||
| dataset_version: string; | |||||
| model_type: NodeType; | |||||
| } | |||||
| interface ProjectDependency extends NodeConfig { | |||||
| url: string; | |||||
| name: string; | |||||
| branch: string; | |||||
| model_type: NodeType; | |||||
| } | |||||
| type ModalDetail = { | |||||
| name: string; | |||||
| available_range: number; | |||||
| file_name: string; | |||||
| file_size: string; | |||||
| description: string; | |||||
| model_type_name: string; | |||||
| model_tag_name: string; | |||||
| create_time: string; | |||||
| }; | |||||
| interface ModelDepsAPIData { | |||||
| current_model_id: number; | |||||
| version: string; | |||||
| exp_ins_id: number; | |||||
| model_type: NodeType; | |||||
| current_model_name: string; | |||||
| project_dependency: ProjectDependency; | |||||
| test_dataset: TrainDataset[]; | |||||
| train_dataset: TrainDataset[]; | |||||
| train_task: TrainTask; | |||||
| model_version_dependcy_vo: ModalDetail; | |||||
| children_models: ModelDepsAPIData[]; | |||||
| parent_models: ModelDepsAPIData[]; | |||||
| } | |||||
| export interface ModelDepsData extends Omit<ModelDepsAPIData, 'children_models'>, TreeGraphData { | |||||
| children: ModelDepsData[]; | |||||
| } | |||||
| // 规范化子数据 | |||||
| function normalizeChildren(data: ModelDepsData[]) { | |||||
| if (Array.isArray(data)) { | |||||
| data.forEach((item) => { | |||||
| item.model_type = NodeType.children; | |||||
| item.id = `$M_${item.current_model_id}_${item.version}`; | |||||
| item.label = getLabel(item); | |||||
| item.style = getStyle(NodeType.children); | |||||
| normalizeChildren(item.children); | |||||
| }); | |||||
| } | |||||
| } | |||||
| // 获取 label | |||||
| function getLabel(node: { current_model_name: string; version: string }) { | |||||
| return ( | |||||
| fittingString(`${node.current_model_name}`, 87, 8) + | |||||
| '\n' + | |||||
| fittingString(`${node.version}`, 87, 8) | |||||
| ); | |||||
| } | |||||
| // 获取 style | |||||
| function getStyle(model_type: NodeType) { | |||||
| let fill = ''; | |||||
| switch (model_type) { | |||||
| case NodeType.current: | |||||
| fill = '#1664ff'; | |||||
| break; | |||||
| case NodeType.parent: | |||||
| fill = '#76b1ff'; | |||||
| break; | |||||
| case NodeType.children: | |||||
| fill = '#b7cfff'; | |||||
| break; | |||||
| case NodeType.project: | |||||
| fill = '#FA8C16'; | |||||
| break; | |||||
| case NodeType.trainDataset: | |||||
| fill = '#ff0000'; | |||||
| break; | |||||
| case NodeType.testDataset: | |||||
| fill = '#ff00ff'; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| return { | |||||
| fill, | |||||
| }; | |||||
| } | |||||
| // 将后台返回的数据转换成树形数据 | |||||
| function normalizeTreeData(apiData: ModelDepsAPIData, currentNodeName: string): ModelDepsData { | |||||
| // 将 children_models 转换成 children | |||||
| let normalizedData = changePropertyName(apiData, { | |||||
| children_models: 'children', | |||||
| }) as ModelDepsData; | |||||
| // 设置当前模型的数据 | |||||
| normalizedData.model_type = NodeType.current; | |||||
| normalizedData.current_model_name = currentNodeName; | |||||
| normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`; | |||||
| normalizedData.label = getLabel(normalizedData); | |||||
| normalizedData.style = getStyle(NodeType.current); | |||||
| normalizeChildren(normalizedData.children as ModelDepsData[]); | |||||
| // 将 parent_models 转换成树形结构 | |||||
| let parent_models = normalizedData.parent_models || []; | |||||
| while (parent_models.length > 0) { | |||||
| const parent = parent_models[0]; | |||||
| normalizedData = { | |||||
| ...parent, | |||||
| model_type: NodeType.parent, | |||||
| id: `$M_${parent.current_model_id}_${parent.version}`, | |||||
| label: getLabel(parent), | |||||
| style: getStyle(NodeType.parent), | |||||
| children: [ | |||||
| { | |||||
| ...normalizedData, | |||||
| parent_models: [], | |||||
| }, | |||||
| ], | |||||
| }; | |||||
| parent_models = normalizedData.parent_models || []; | |||||
| } | |||||
| return normalizedData; | |||||
| } | |||||
| // 将树形数据,使用 Hierarchy 进行布局,计算出坐标,然后转换成 G6 的数据 | |||||
| function getGraphData(data: ModelDepsData): GraphData { | |||||
| const config = { | |||||
| direction: 'LR', | |||||
| getHeight: () => nodeHeight, | |||||
| getWidth: () => nodeWidth, | |||||
| getVGap: () => vGap / 2, | |||||
| getHGap: () => hGap / 2, | |||||
| }; | |||||
| // 树形布局计算出坐标 | |||||
| const treeLayoutData: LayoutConfig = Hierarchy['compactBox'](data, config); | |||||
| const nodes: NodeConfig[] = []; | |||||
| const edges: EdgeConfig[] = []; | |||||
| Util.traverseTree(treeLayoutData, (node: NodeConfig, parent: NodeConfig) => { | |||||
| const data = node.data as ModelDepsData; | |||||
| nodes.push({ | |||||
| ...data, | |||||
| x: node.x, | |||||
| y: node.y, | |||||
| }); | |||||
| if (parent) { | |||||
| edges.push({ | |||||
| source: parent.id, | |||||
| target: node.id, | |||||
| }); | |||||
| } | |||||
| // 当前模型显示数据集和项目 | |||||
| if (data.model_type === NodeType.current) { | |||||
| const { project_dependency, train_dataset, test_dataset } = data; | |||||
| train_dataset.forEach((item) => { | |||||
| item.id = `$DTrain_${item.dataset_id}`; | |||||
| item.model_type = NodeType.trainDataset; | |||||
| item.type = 'ellipse'; | |||||
| item.label = fittingString(`${item.dataset_name}`, 87, 8); | |||||
| item.style = getStyle(NodeType.trainDataset); | |||||
| }); | |||||
| test_dataset.forEach((item) => { | |||||
| item.id = `$DTest_${item.dataset_id}`; | |||||
| item.model_type = NodeType.testDataset; | |||||
| item.type = 'ellipse'; | |||||
| item.label = fittingString(item.dataset_name, 87, 8); | |||||
| item.style = getStyle(NodeType.testDataset); | |||||
| }); | |||||
| const len = train_dataset.length + test_dataset.length; | |||||
| [...train_dataset, ...test_dataset].forEach((item, index) => { | |||||
| const half = len / 2 - 0.5; | |||||
| item.x = node.x! - (half - index) * (nodeWidth + hGap); | |||||
| item.y = node.y! - nodeHeight - vGap; | |||||
| nodes.push(item); | |||||
| edges.push({ | |||||
| source: node.id, | |||||
| target: item.id, | |||||
| sourceAnchor: 2, | |||||
| targetAnchor: 3, | |||||
| type: 'cubic-vertical', | |||||
| }); | |||||
| }); | |||||
| if (project_dependency?.url) { | |||||
| project_dependency.id = `$P_${project_dependency.url}`; | |||||
| project_dependency.model_type = NodeType.project; | |||||
| project_dependency.type = 'rect'; | |||||
| project_dependency.size = [nodeHeight, nodeHeight]; | |||||
| project_dependency.label = fittingString(project_dependency.name, 48, 8); | |||||
| project_dependency.style = getStyle(NodeType.project); | |||||
| project_dependency.x = node.x; | |||||
| project_dependency.y = node.y! + nodeHeight + vGap; | |||||
| nodes.push(project_dependency); | |||||
| edges.push({ | |||||
| source: node.id, | |||||
| target: project_dependency.id, | |||||
| sourceAnchor: 3, | |||||
| targetAnchor: 2, | |||||
| type: 'cubic-vertical', | |||||
| }); | |||||
| } | |||||
| } | |||||
| }); | |||||
| return { nodes, edges }; | |||||
| } | |||||
| type modeModelEvolutionProps = { | |||||
| resourceId: number; | |||||
| resourceName: string; | |||||
| versionList: ResourceVersionData[]; | |||||
| version?: string; | |||||
| isActive: boolean; | |||||
| onVersionChange: (version: string) => void; | |||||
| }; | |||||
| let graph: Graph; | |||||
| function ModelEvolution({ | |||||
| resourceId, | |||||
| resourceName, | |||||
| versionList, | |||||
| version, | |||||
| isActive, | |||||
| onVersionChange, | |||||
| }: modeModelEvolutionProps) { | |||||
| const graphRef = useRef<HTMLDivElement>(null); | |||||
| const [showNodeTooltip, setShowNodeTooltip] = useState(false); | |||||
| const [enterTooltip, setEnterTooltip] = useState(false); | |||||
| const [nodeTooltipX, setNodeToolTipX] = useState(0); | |||||
| const [nodeTooltipY, setNodeToolTipY] = useState(0); | |||||
| const [hoverNodeData, setHoverNodeData] = useState<ModelDepsData | undefined>(undefined); | |||||
| useEffect(() => { | |||||
| initGraph(); | |||||
| const changSize = () => { | |||||
| if (!graph || graph.get('destroyed')) return; | |||||
| if (!graphRef.current) return; | |||||
| graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); | |||||
| graph.fitView(); | |||||
| }; | |||||
| window.addEventListener('resize', changSize); | |||||
| return () => { | |||||
| window.removeEventListener('resize', changSize); | |||||
| }; | |||||
| }, []); | |||||
| useEffectWhen( | |||||
| () => { | |||||
| if (version) { | |||||
| getModelAtlas(); | |||||
| } else { | |||||
| clearGraphData(); | |||||
| } | |||||
| }, | |||||
| [resourceId, version], | |||||
| isActive, | |||||
| ); | |||||
| // 初始化图 | |||||
| const initGraph = () => { | |||||
| graph = new G6.Graph({ | |||||
| container: graphRef.current!, | |||||
| width: graphRef.current!.clientWidth, | |||||
| height: graphRef.current!.clientHeight, | |||||
| fitView: true, | |||||
| fitViewPadding: [50, 100, 50, 100], | |||||
| minZoom: 0.5, | |||||
| maxZoom: 5, | |||||
| defaultNode: { | |||||
| type: 'rect', | |||||
| size: [nodeWidth, nodeHeight], | |||||
| anchorPoints: [ | |||||
| [0, 0.5], | |||||
| [1, 0.5], | |||||
| [0.5, 0], | |||||
| [0.5, 1], | |||||
| ], | |||||
| style: { | |||||
| fill: themes['primaryColor'], | |||||
| lineWidth: 0, | |||||
| radius: 6, | |||||
| cursor: 'pointer', | |||||
| }, | |||||
| labelCfg: { | |||||
| position: 'center', | |||||
| style: { | |||||
| fill: '#ffffff', | |||||
| fontSize: 8, | |||||
| textAlign: 'center', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| defaultEdge: { | |||||
| type: 'cubic-horizontal', | |||||
| labelCfg: { | |||||
| autoRotate: true, | |||||
| }, | |||||
| style: { | |||||
| stroke: '#a2c1ff', | |||||
| lineWidth: 1, | |||||
| }, | |||||
| }, | |||||
| modes: { | |||||
| default: [ | |||||
| 'drag-canvas', | |||||
| 'zoom-canvas', | |||||
| // { | |||||
| // type: 'collapse-expand', | |||||
| // onChange(item?: Item, collapsed?: boolean) { | |||||
| // const data = item!.getModel(); | |||||
| // data.collapsed = collapsed; | |||||
| // return true; | |||||
| // }, | |||||
| // }, | |||||
| ], | |||||
| }, | |||||
| }); | |||||
| bindEvents(); | |||||
| }; | |||||
| // 绑定事件 | |||||
| const bindEvents = () => { | |||||
| graph.on('node:mouseenter', (e: G6GraphEvent) => { | |||||
| const nodeItem = e.item; | |||||
| graph.setItemState(nodeItem, 'hover', true); | |||||
| const model = nodeItem.getModel() as ModelDepsData; | |||||
| const { x, y, model_type } = model; | |||||
| if ( | |||||
| model_type === NodeType.project || | |||||
| model_type === NodeType.trainDataset || | |||||
| model_type === NodeType.testDataset | |||||
| ) { | |||||
| return; | |||||
| } | |||||
| const point = graph.getCanvasByPoint(x!, y!); | |||||
| const canvasWidth = graphRef.current!.clientWidth; | |||||
| if (point.x + 300 > canvasWidth) { | |||||
| point.x = canvasWidth - 300; | |||||
| } | |||||
| const zoom = graph.getZoom(); | |||||
| // 更加缩放,调整 tooltip 位置 | |||||
| const offsetY = (nodeHeight * zoom) / 4; | |||||
| setHoverNodeData(model); | |||||
| setNodeToolTipX(point.x); | |||||
| // 92: 版本选择器的高度,296: tooltip的高度 | |||||
| setNodeToolTipY(point.y + 92 - 296 - offsetY); | |||||
| setShowNodeTooltip(true); | |||||
| }); | |||||
| graph.on('node:mouseleave', (e: G6GraphEvent) => { | |||||
| const nodeItem = e.item; | |||||
| graph.setItemState(nodeItem, 'hover', false); | |||||
| setShowNodeTooltip(false); | |||||
| }); | |||||
| graph.on('node:click', (e: G6GraphEvent) => { | |||||
| const nodeItem = e.item; | |||||
| const model = nodeItem.getModel(); | |||||
| const { model_type } = model; | |||||
| const { origin } = location; | |||||
| let url: string = ''; | |||||
| switch (model_type) { | |||||
| case NodeType.children: | |||||
| case NodeType.parent: { | |||||
| const { current_model_id, version } = model as ModelDepsData; | |||||
| url = `${origin}/dataset/model/${current_model_id}?tab=3&version=${version}`; | |||||
| break; | |||||
| } | |||||
| case NodeType.project: { | |||||
| const { url: projectUrl } = model as ProjectDependency; | |||||
| url = projectUrl; | |||||
| break; | |||||
| } | |||||
| case NodeType.trainDataset: | |||||
| case NodeType.testDataset: { | |||||
| const { dataset_id, dataset_version } = model as TrainDataset; | |||||
| url = `${origin}/dataset/dataset/${dataset_id}?tab=2&version=${dataset_version}`; | |||||
| break; | |||||
| } | |||||
| default: | |||||
| break; | |||||
| } | |||||
| if (url) { | |||||
| window.open(url, '_blank'); | |||||
| } | |||||
| }); | |||||
| // 鼠标滚轮缩放时,隐藏 tooltip | |||||
| graph.on('wheelzoom', () => { | |||||
| setShowNodeTooltip(false); | |||||
| setEnterTooltip(false); | |||||
| }); | |||||
| }; | |||||
| const handleTooltipsMouseEnter = () => { | |||||
| setEnterTooltip(true); | |||||
| }; | |||||
| const handleTooltipsMouseLeave = () => { | |||||
| setEnterTooltip(false); | |||||
| }; | |||||
| // 获取模型依赖 | |||||
| const getModelAtlas = async () => { | |||||
| const params = { | |||||
| model_id: resourceId, | |||||
| version, | |||||
| }; | |||||
| const [res] = await to(getModelAtlasReq(params)); | |||||
| if (res && res.data) { | |||||
| const data = normalizeTreeData(res.data, resourceName); | |||||
| const graphData = getGraphData(data); | |||||
| graph.data(graphData); | |||||
| graph.render(); | |||||
| graph.fitView(); | |||||
| } else { | |||||
| clearGraphData(); | |||||
| } | |||||
| }; | |||||
| // 请求失败或者版本不存在时,清除图形 | |||||
| function clearGraphData() { | |||||
| graph.data({ | |||||
| nodes: [], | |||||
| edges: [], | |||||
| }); | |||||
| graph.render(); | |||||
| graph.fitView(); | |||||
| } | |||||
| return ( | |||||
| <div className={styles['model-evolution']}> | |||||
| <Flex align="center" className={styles['model-evolution__top']}> | |||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ width: '160px', marginRight: '20px' }} | |||||
| value={version} | |||||
| allowClear | |||||
| onChange={onVersionChange} | |||||
| options={versionList} | |||||
| /> | |||||
| <GraphLegand style={{ marginRight: 0, marginLeft: 'auto' }}></GraphLegand> | |||||
| </Flex> | |||||
| <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | |||||
| {(showNodeTooltip || enterTooltip) && ( | |||||
| <NodeTooltips | |||||
| x={nodeTooltipX} | |||||
| y={nodeTooltipY} | |||||
| data={hoverNodeData!} | |||||
| onMouseEnter={handleTooltipsMouseEnter} | |||||
| onMouseLeave={handleTooltipsMouseLeave} | |||||
| /> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ModelEvolution; | |||||
| @@ -0,0 +1,56 @@ | |||||
| .node-tooltips { | |||||
| position: absolute; | |||||
| top: -100px; | |||||
| left: -300px; | |||||
| width: 300px; | |||||
| padding: 10px; | |||||
| background: white; | |||||
| border: 1px solid #eaeaea; | |||||
| border-radius: 4px; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| &__title { | |||||
| margin: 10px 0; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: @font-size-content; | |||||
| } | |||||
| &__row { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin: 4px 0; | |||||
| color: @text-color; | |||||
| font-size: 14px; | |||||
| &:first-child { | |||||
| margin-top: 0; | |||||
| } | |||||
| &:last-child { | |||||
| margin-bottom: 10px; | |||||
| } | |||||
| &__title { | |||||
| display: inline-block; | |||||
| width: 100px; | |||||
| color: @text-color-secondary; | |||||
| text-align: right; | |||||
| } | |||||
| &__value { | |||||
| flex: 1; | |||||
| min-width: 0; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| .singleLine(); | |||||
| } | |||||
| &__link { | |||||
| flex: 1; | |||||
| min-width: 0; | |||||
| font-weight: 500; | |||||
| .singleLine(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,78 @@ | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { ModelDepsData } from '../ModelEvolution'; | |||||
| import styles from './index.less'; | |||||
| type NodeTooltipsProps = { | |||||
| data: ModelDepsData; | |||||
| x: number; | |||||
| y: number; | |||||
| onMouseEnter?: () => void; | |||||
| onMouseLeave?: () => void; | |||||
| }; | |||||
| function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsProps) { | |||||
| const gotoExperimentPage = () => { | |||||
| if (data.train_task?.ins_id) { | |||||
| const { origin } = location; | |||||
| window.open(`${origin}/pipeline/experiment/144/${data.train_task.ins_id}`, '_blank'); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <div | |||||
| className={styles['node-tooltips']} | |||||
| style={{ left: `${x}px`, top: `${y}px` }} | |||||
| onMouseEnter={onMouseEnter} | |||||
| onMouseLeave={onMouseLeave} | |||||
| > | |||||
| <div className={styles['node-tooltips__title']}>模型信息</div> | |||||
| <div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>模型名称:</span> | |||||
| <span className={styles['node-tooltips__row__value']}>{data.current_model_name}</span> | |||||
| </div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>模型版本:</span> | |||||
| <span className={styles['node-tooltips__row__value']}>{data.version}</span> | |||||
| </div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>模型框架:</span> | |||||
| <span className={styles['node-tooltips__row__value']}> | |||||
| {data.model_version_dependcy_vo?.model_type_name || '--'} | |||||
| </span> | |||||
| </div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>模型大小:</span> | |||||
| <span className={styles['node-tooltips__row__value']}> | |||||
| {data.model_version_dependcy_vo?.file_size || '--'} | |||||
| </span> | |||||
| </div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>创建时间:</span> | |||||
| <span className={styles['node-tooltips__row__value']}> | |||||
| {formatDate(data.model_version_dependcy_vo?.create_time)} | |||||
| </span> | |||||
| </div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>模型权限:</span> | |||||
| <span className={styles['node-tooltips__row__value']}> | |||||
| {data.model_version_dependcy_vo?.available_range === 1 ? '公开' : '私有'} | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['node-tooltips__title']}>训练相关信息</div> | |||||
| <div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>训练任务:</span> | |||||
| {data.train_task?.name ? ( | |||||
| <a className={styles['node-tooltips__row__link']} onClick={gotoExperimentPage}> | |||||
| {data.train_task?.name} | |||||
| </a> | |||||
| ) : null} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default NodeTooltips; | |||||
| @@ -1,5 +1,5 @@ | |||||
| import ResourcePage from '@/pages/Dataset/components/ResourcePage'; | import ResourcePage from '@/pages/Dataset/components/ResourcePage'; | ||||
| import { ResourceType } from '@/pages/Dataset/types'; | |||||
| import { ResourceType } from '@/pages/Dataset/config'; | |||||
| const ModelPage = () => { | const ModelPage = () => { | ||||
| return <ResourcePage resourceType={ResourceType.Model} />; | return <ResourcePage resourceType={ResourceType.Model} />; | ||||
| @@ -1,262 +0,0 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | |||||
| import { ResourceType } from '@/pages/Dataset/types'; | |||||
| import { | |||||
| deleteModelVersion, | |||||
| getModelById, | |||||
| getModelVersionIdList, | |||||
| getModelVersionsById, | |||||
| } from '@/services/dataset/index.js'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { App, Button, Input, Select, Table, Tabs } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import Styles from './intro.less'; | |||||
| const { Search } = Input; | |||||
| const { TabPane } = Tabs; | |||||
| const Dataset = () => { | |||||
| const [formList, setFormList] = useState([]); | |||||
| const [datasetDetailObj, setDatasetDetailObj] = useState({}); | |||||
| const [version, setVersion] = useState(null); | |||||
| const [versionList, setVersionList] = useState([]); | |||||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||||
| const [searchParams] = useSearchParams(); | |||||
| const [wordList, setWordList] = useState([]); | |||||
| const { message } = App.useApp(); | |||||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||||
| const getModelByDetail = () => { | |||||
| getModelById(locationParams.id).then((ret) => { | |||||
| console.log(ret); | |||||
| setDatasetDetailObj(ret.data); | |||||
| }); | |||||
| }; | |||||
| const getModelVersionsList = () => { | |||||
| getModelVersionsById(locationParams.id).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret && ret.data && ret.data.length > 0) { | |||||
| setVersionList( | |||||
| ret.data.map((item) => { | |||||
| return { | |||||
| label: item, | |||||
| value: item, | |||||
| }; | |||||
| }), | |||||
| ); | |||||
| setVersion(ret.data[0]); | |||||
| getModelVersions({ version: ret.data[0], models_id: locationParams.id }); | |||||
| } else { | |||||
| setVersion(null); | |||||
| setWordList([]); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| getModelByDetail(); | |||||
| getModelVersionsList(); | |||||
| return () => {}; | |||||
| }, []); | |||||
| const showModal = () => { | |||||
| const { close } = openAntdModal(AddVersionModal, { | |||||
| resourceType: ResourceType.Model, | |||||
| resourceId: locationParams.id, | |||||
| initialName: datasetDetailObj.name, | |||||
| onOk: () => { | |||||
| getModelVersionsList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const deleteDataset = () => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| deleteModelVersion({ models_id: locationParams.id, version }).then((ret) => { | |||||
| getModelVersionsList(); | |||||
| message.success('删除成功'); | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const getModelVersions = (params) => { | |||||
| getModelVersionIdList(params).then((ret) => { | |||||
| setWordList(ret?.data?.content ?? []); | |||||
| }); | |||||
| }; | |||||
| const handleExport = async () => { | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/models/downloadAllFiles`, { models_id: locationParams.id, version }); | |||||
| }; | |||||
| const downloadAlone = (e, record) => { | |||||
| console.log(record); | |||||
| const hide = message.loading('正在下载'); | |||||
| hide(); | |||||
| downLoadZip(`/api/mmp/models/download_model/${record.id}`); | |||||
| }; | |||||
| const handleChange = (value) => { | |||||
| console.log(value); | |||||
| if (value) { | |||||
| getModelVersions({ version: value, models_id: locationParams.id }); | |||||
| setVersion(value); | |||||
| } else { | |||||
| setVersion(''); | |||||
| } | |||||
| }; | |||||
| const columns = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: 80, | |||||
| render(text, record, index) { | |||||
| return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>; | |||||
| }, | |||||
| // render: (text, record, index) => `${((curPage-1)*10)+(index+1)}`, | |||||
| }, | |||||
| { | |||||
| title: '文件名称', | |||||
| dataIndex: 'file_name', | |||||
| key: 'file_name', | |||||
| render: (text, record) => <a onClick={(e) => downloadAlone(e, record)}>{text}</a>, | |||||
| }, | |||||
| { | |||||
| title: '版本号', | |||||
| dataIndex: 'version', | |||||
| key: 'version', | |||||
| }, | |||||
| { | |||||
| title: '文件大小', | |||||
| dataIndex: 'file_size', | |||||
| key: 'file_size', | |||||
| }, | |||||
| { | |||||
| title: '更新时间', | |||||
| dataIndex: 'update_time', | |||||
| key: 'update_time', | |||||
| render: (text) => <span>{formatDate(text)}</span>, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'option', | |||||
| width: '100px', | |||||
| key: 'option', | |||||
| render: (_, record) => [ | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="download" | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| onClick={(e) => downloadAlone(e, record)} | |||||
| > | |||||
| 下载 | |||||
| </Button>, | |||||
| ], | |||||
| }, | |||||
| ]; | |||||
| const pageOption = useRef({ page: 1, size: 10 }); | |||||
| // 当前页面切换 | |||||
| const paginationChange = async (current, size) => { | |||||
| console.log('page', current, size); | |||||
| pageOption.current = { | |||||
| page: current, | |||||
| size: size, | |||||
| }; | |||||
| // getList() | |||||
| }; | |||||
| return ( | |||||
| <div className={Styles.datasetBox}> | |||||
| <div className={Styles.datasetIntroTopBox}> | |||||
| <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | |||||
| <div className={Styles.smallTagBox}> | |||||
| <div className={Styles.tagItem}>模型 id:{datasetDetailObj.id}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.model_type_name || '...'}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.model_tag_name || '...'}</div> | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.datasetIntroCneterBox}> | |||||
| <Tabs defaultActiveKey="1"> | |||||
| <TabPane tab="模型简介" key="1"> | |||||
| <div className={Styles.datasetIntroTitle}>简介</div> | |||||
| <div className={Styles.datasetIntroText}>{datasetDetailObj.description}</div> | |||||
| </TabPane> | |||||
| <TabPane tab="模型文件/版本" key="2"> | |||||
| <div className={Styles.dataListBox}> | |||||
| <div>模型列表</div> | |||||
| <div className={Styles.dataButtonList}> | |||||
| <div | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||||
| > | |||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ | |||||
| width: 160, | |||||
| }} | |||||
| value={version} | |||||
| allowClear | |||||
| onChange={handleChange} | |||||
| options={versionList} | |||||
| /> | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| onClick={showModal} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 创建新版本 | |||||
| </Button> | |||||
| </div> | |||||
| <div | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | |||||
| > | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={deleteDataset} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| )} | |||||
| <Button | |||||
| type="default" | |||||
| className={Styles.plusButton} | |||||
| disabled={!version} | |||||
| style={{ margin: '0 20px 0 0' }} | |||||
| onClick={handleExport} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| </div> | |||||
| </div> | |||||
| <div style={{ marginBottom: '10px', fontSize: '14px' }}> | |||||
| {wordList.length > 0 && wordList[0].description | |||||
| ? '版本描述:' + wordList[0].description | |||||
| : null} | |||||
| </div> | |||||
| <Table columns={columns} dataSource={wordList} pagination={false} rowKey="id" /> | |||||
| </div> | |||||
| </TabPane> | |||||
| </Tabs> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default Dataset; | |||||
| @@ -1,80 +0,0 @@ | |||||
| .datasetIntroTopBox { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 25px 30px; | |||||
| background-image: url(/assets/images/dataset-back.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| .smallTagBox { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #1664ff; | |||||
| font-size: 14px; | |||||
| .tagItem { | |||||
| margin-right: 20px; | |||||
| padding: 4px 10px; | |||||
| background: rgba(22, 100, 255, 0.1); | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .dataListBox { | |||||
| padding: 20px 30px; | |||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| .dataButtonList { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin: 24px 0 30px 0; | |||||
| color: #575757; | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| .datasetIntroCneterBox { | |||||
| height: 77vh; | |||||
| padding: 20px 30px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| .datasetIntroTitle { | |||||
| margin: 37px 0 10px 0; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| } | |||||
| .datasetIntroText { | |||||
| margin-bottom: 30px; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| } | |||||
| .datasetBox { | |||||
| font-family: 'Alibaba'; | |||||
| background: #f9fafb; | |||||
| :global { | |||||
| .ant-tabs-top > .ant-tabs-nav { | |||||
| margin: 0; | |||||
| } | |||||
| .ant-pagination { | |||||
| text-align: right; | |||||
| } | |||||
| } | |||||
| } | |||||
| .plusButton { | |||||
| margin: 0 18px 0 20px; | |||||
| } | |||||
| .tipContent { | |||||
| margin-top: 5px; | |||||
| color: #c73131; | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| import ResourceIntro from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import { ResourceType } from '@/pages/Dataset/config'; | |||||
| function ModelIntro() { | |||||
| return <ResourceIntro resourceType={ResourceType.Model} />; | |||||
| } | |||||
| export default ModelIntro; | |||||
| @@ -104,7 +104,7 @@ function ModelDeploymentCreate() { | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| form.setFieldValue(name, res); | |||||
| form.setFieldValue(name, res.path); | |||||
| } else { | } else { | ||||
| const response = res as ResourceSelectorResponse; | const response = res as ResourceSelectorResponse; | ||||
| const showValue = `${response.name}:${response.version}`; | const showValue = `${response.name}:${response.version}`; | ||||
| @@ -1,6 +1,16 @@ | |||||
| .model-deployment-info { | .model-deployment-info { | ||||
| height: 100%; | height: 100%; | ||||
| &__content { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 30px 30px 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| } | |||||
| &__basic { | &__basic { | ||||
| &__item { | &__item { | ||||
| display: flex; | display: flex; | ||||
| @@ -23,34 +33,13 @@ | |||||
| } | } | ||||
| } | } | ||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| &__guide { | |||||
| flex: 1; | |||||
| margin-top: 10px; | margin-top: 10px; | ||||
| padding: 30px 30px 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| } | |||||
| &__table { | |||||
| :global { | |||||
| .ant-table-wrapper { | |||||
| height: 100%; | |||||
| .ant-spin-nested-loading { | |||||
| height: 100%; | |||||
| } | |||||
| .ant-spin-container { | |||||
| height: 100%; | |||||
| } | |||||
| .ant-table { | |||||
| height: calc(100% - 74px); | |||||
| overflow: auto; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| padding: 10px; | |||||
| overflow-y: auto; | |||||
| color: white; | |||||
| white-space: pre-wrap; | |||||
| background-color: rgba(0, 0, 0, 0.85); | |||||
| } | } | ||||
| } | } | ||||
| @@ -8,48 +8,70 @@ import PageTitle from '@/components/PageTitle'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | import { useComputingResource } from '@/hooks/resource'; | ||||
| import { useSessionStorage } from '@/hooks/sessionStorage'; | import { useSessionStorage } from '@/hooks/sessionStorage'; | ||||
| import { getModelDeploymentDocsReq } from '@/services/modelDeployment'; | |||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { to } from '@/utils/promise'; | |||||
| import { modelDeploymentInfoKey } from '@/utils/sessionStorage'; | import { modelDeploymentInfoKey } from '@/utils/sessionStorage'; | ||||
| import { Col, Row, Tabs, type TabsProps } from 'antd'; | import { Col, Row, Tabs, type TabsProps } from 'antd'; | ||||
| import { pick } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell'; | import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell'; | ||||
| import { ModelDeploymentData } from '../types'; | import { ModelDeploymentData } from '../types'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export enum ModelDeploymentTabKey { | |||||
| Predict = 'Predict', | |||||
| Guide = 'Guide', | |||||
| Log = 'Log', | |||||
| } | |||||
| const tabItems = [ | const tabItems = [ | ||||
| { | { | ||||
| key: '1', | |||||
| key: ModelDeploymentTabKey.Predict, | |||||
| label: '预测', | label: '预测', | ||||
| icon: <KFIcon type="icon-yuce" />, | icon: <KFIcon type="icon-yuce" />, | ||||
| }, | }, | ||||
| { | { | ||||
| key: '2', | |||||
| key: ModelDeploymentTabKey.Guide, | |||||
| label: '调用指南', | label: '调用指南', | ||||
| icon: <KFIcon type="icon-tiaoyongzhinan" />, | icon: <KFIcon type="icon-tiaoyongzhinan" />, | ||||
| }, | }, | ||||
| { | { | ||||
| key: '3', | |||||
| key: ModelDeploymentTabKey.Log, | |||||
| label: '服务日志', | label: '服务日志', | ||||
| icon: <KFIcon type="icon-fuwurizhi" />, | icon: <KFIcon type="icon-fuwurizhi" />, | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| function ModelDeploymentInfo() { | function ModelDeploymentInfo() { | ||||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||||
| const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict); | |||||
| const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>( | const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>( | ||||
| modelDeploymentInfoKey, | modelDeploymentInfoKey, | ||||
| true, | true, | ||||
| undefined, | undefined, | ||||
| ); | ); | ||||
| const getResourceDescription = useComputingResource()[2]; | const getResourceDescription = useComputingResource()[2]; | ||||
| const [docs, setDocs] = useState(''); | |||||
| useEffect(() => {}, []); | |||||
| useEffect(() => { | |||||
| getModelDeploymentDocs(); | |||||
| }, [modelDeployementInfo]); | |||||
| // 获取模型部署文档 | |||||
| const getModelDeploymentDocs = async () => { | |||||
| const params = pick(modelDeployementInfo, ['service_id', 'service_ins_id']); | |||||
| const [res] = await to(getModelDeploymentDocsReq(params)); | |||||
| if (res && res.data && res.data.docs) { | |||||
| setDocs(JSON.stringify(res.data.docs, null, 2)); | |||||
| } | |||||
| }; | |||||
| // 切换 Tab,重置数据 | // 切换 Tab,重置数据 | ||||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | const hanleTabChange: TabsProps['onChange'] = (value) => { | ||||
| setActiveTab(value); | setActiveTab(value); | ||||
| }; | }; | ||||
| // 格式化环境变量 | |||||
| const formatEnvText = () => { | const formatEnvText = () => { | ||||
| if (!modelDeployementInfo?.env) { | if (!modelDeployementInfo?.env) { | ||||
| return '--'; | return '--'; | ||||
| @@ -64,128 +86,130 @@ function ModelDeploymentInfo() { | |||||
| <div className={styles['model-deployment-info']}> | <div className={styles['model-deployment-info']}> | ||||
| <PageTitle title="服务详情"></PageTitle> | <PageTitle title="服务详情"></PageTitle> | ||||
| <div className={styles['model-deployment-info__content']}> | <div className={styles['model-deployment-info__content']}> | ||||
| <div> | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['model-deployment-info__basic']}> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>服务名称:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.service_name ?? '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>镜 像:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.image ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>状 态:</div> | |||||
| <div className={styles['value']}> | |||||
| {ModelDeploymentStatusCell(modelDeployementInfo?.status)} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>模 型:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.model?.show_value ?? '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>创建人:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.created_by ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>挂载路径:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.model_path ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>API URL:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.url ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>副本数量:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.replicas ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>创建时间:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.create_time | |||||
| ? formatDate(modelDeployementInfo.create_time) | |||||
| : '--'} | |||||
| </div> | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['model-deployment-info__basic']}> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>服务名称:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.service_name ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>镜 像:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.image ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>状 态:</div> | |||||
| <div className={styles['value']}> | |||||
| {ModelDeploymentStatusCell(modelDeployementInfo?.status)} | |||||
| </div> | </div> | ||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>更新时间:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.update_time | |||||
| ? formatDate(modelDeployementInfo.update_time) | |||||
| : '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>模 型:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.model?.show_value ?? '--'} | |||||
| </div> | </div> | ||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>环境变量:</div> | |||||
| <div className={styles['value']}>{formatEnvText()}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>创建人:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.created_by ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>挂载路径:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.model_path ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>API URL:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.url ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>副本数量:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.replicas ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>创建时间:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.create_time | |||||
| ? formatDate(modelDeployementInfo.create_time) | |||||
| : '--'} | |||||
| </div> | </div> | ||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>资源规格:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.resource | |||||
| ? getResourceDescription(modelDeployementInfo.resource) | |||||
| : '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>更新时间:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.update_time | |||||
| ? formatDate(modelDeployementInfo.update_time) | |||||
| : '--'} | |||||
| </div> | </div> | ||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40}> | |||||
| <Col span={18}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>描 述:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.description ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>环境变量:</div> | |||||
| <div className={styles['value']}>{formatEnvText()}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>资源规格:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.resource | |||||
| ? getResourceDescription(modelDeployementInfo.resource) | |||||
| : '--'} | |||||
| </div> | </div> | ||||
| </Col> | |||||
| </Row> | |||||
| </div> | |||||
| <div style={{ marginTop: '20px' }}> | |||||
| <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40}> | |||||
| <Col span={18}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>描 述:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.description ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| </div> | </div> | ||||
| <Tabs | |||||
| activeKey={activeTab} | |||||
| style={{ marginTop: '20px' }} | |||||
| items={tabItems} | |||||
| onChange={hanleTabChange} | |||||
| /> | |||||
| {activeTab === ModelDeploymentTabKey.Guide && ( | |||||
| <div className={styles['model-deployment-info__guide']}>{docs}</div> | |||||
| )} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -1,4 +1,5 @@ | |||||
| .collapse { | .collapse { | ||||
| flex: none; | |||||
| width: 250px; | width: 250px; | ||||
| height: 100%; | height: 100%; | ||||
| @@ -35,14 +36,15 @@ | |||||
| align-items: center; | align-items: center; | ||||
| height: 40px; | height: 40px; | ||||
| padding: 0 16px; | padding: 0 16px; | ||||
| color: #575757; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | font-size: 14px; | ||||
| border-radius: 4px; | border-radius: 4px; | ||||
| cursor: pointer; | cursor: pointer; | ||||
| } | |||||
| .collapseItem:hover { | |||||
| color: #1664ff; | |||||
| background: rgba(22, 100, 255, 0.08); | |||||
| &:hover { | |||||
| color: @primary-color; | |||||
| background: rgba(22, 100, 255, 0.08); | |||||
| } | |||||
| } | } | ||||
| .modelMenusTitle { | .modelMenusTitle { | ||||
| margin-bottom: 10px; | margin-bottom: 10px; | ||||
| @@ -0,0 +1,91 @@ | |||||
| 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> | |||||
| {/* 这样 defaultActiveKey 才能生效 */} | |||||
| {modelMenusList.length > 0 ? ( | |||||
| <Collapse | |||||
| collapsible="header" | |||||
| expandIconPosition="end" | |||||
| defaultActiveKey={defaultActiveKey} | |||||
| items={collapseItems} | |||||
| ></Collapse> | |||||
| ) : null} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default ModelMenu; | |||||
| @@ -20,9 +20,9 @@ export enum ResourceSelectorType { | |||||
| } | } | ||||
| export type MirrorVersion = { | export type MirrorVersion = { | ||||
| id: number; // 镜像版本id | |||||
| id: number; // 镜像版本 id | |||||
| status: MirrorVersionStatus; // 镜像版本状态 | status: MirrorVersionStatus; // 镜像版本状态 | ||||
| tag_name: string; // 镜像版本 | |||||
| tag_name: string; // 镜像版本 name | |||||
| url: string; // 镜像版本路径 | url: string; // 镜像版本路径 | ||||
| }; | }; | ||||
| @@ -39,12 +39,13 @@ export type SelectorTypeInfo = { | |||||
| tabItems: TabsProps['items']; | tabItems: TabsProps['items']; | ||||
| }; | }; | ||||
| // 获取镜像列表,为了兼容数据集和模型 | |||||
| // 获取镜像文件列表,为了兼容数据集和模型 | |||||
| const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => { | const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => { | ||||
| const index = version.indexOf('-'); | const index = version.indexOf('-'); | ||||
| const url = version.slice(index + 1); | const url = version.slice(index + 1); | ||||
| return Promise.resolve({ | return Promise.resolve({ | ||||
| data: { | data: { | ||||
| path: url, | |||||
| content: [ | content: [ | ||||
| { | { | ||||
| id: `${id}-${version}`, | id: `${id}-${version}`, | ||||
| @@ -15,26 +15,18 @@ import { MirrorVersion, ResourceSelectorType, selectorTypeConfig } from './confi | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export { ResourceSelectorType, selectorTypeConfig }; | export { ResourceSelectorType, selectorTypeConfig }; | ||||
| // 选择数据集和模型的返回类型 | |||||
| // 选择数据集\模型\镜像的返回类型 | |||||
| export type ResourceSelectorResponse = { | export type ResourceSelectorResponse = { | ||||
| id: number; // 数据集或者模型 id | |||||
| name: string; // 数据集或者模型 name | |||||
| version: string; // 数据集或者模型版本 | |||||
| path: string; // 数据集或者模型版本路径 | |||||
| id: number; // 数据集\模型\镜像 id | |||||
| name: string; // 数据集\模型\镜像 name | |||||
| version: string; // 数据集\模型\镜像版本 | |||||
| path: string; // 数据集\模型\镜像版本路径 | |||||
| activeTab: CommonTabKeys; // 是我的还是公开的 | activeTab: CommonTabKeys; // 是我的还是公开的 | ||||
| }; | }; | ||||
| export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| type: ResourceSelectorType; // 模型 | 数据集 | |||||
| defaultExpandedKeys?: React.Key[]; | |||||
| defaultCheckedKeys?: React.Key[]; | |||||
| defaultActiveTab?: CommonTabKeys; | |||||
| onOk?: (params: ResourceSelectorResponse | string | null) => void; | |||||
| } | |||||
| type ResourceGroup = { | type ResourceGroup = { | ||||
| id: number; // 数据集或者模型 id | |||||
| name: string; // 数据集或者模型 id | |||||
| id: number; // 数据集\模型\镜像 id | |||||
| name: string; // 数据集\模型\镜像 name | |||||
| }; | }; | ||||
| type ResourceFile = { | type ResourceFile = { | ||||
| @@ -42,9 +34,17 @@ type ResourceFile = { | |||||
| file_name: string; // 文件 name | file_name: string; // 文件 name | ||||
| }; | }; | ||||
| export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| type: ResourceSelectorType; // 数据集\模型\镜像 | |||||
| defaultExpandedKeys?: React.Key[]; | |||||
| defaultCheckedKeys?: React.Key[]; | |||||
| defaultActiveTab?: CommonTabKeys; | |||||
| onOk?: (params: ResourceSelectorResponse | null) => void; | |||||
| } | |||||
| type TreeRef = GetRef<typeof Tree<TreeDataNode>>; | type TreeRef = GetRef<typeof Tree<TreeDataNode>>; | ||||
| // list 转成 treeData | |||||
| // list 数据转成 treeData | |||||
| const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { | const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { | ||||
| return list.map((v) => ({ | return list.map((v) => ({ | ||||
| title: v.name, | title: v.name, | ||||
| @@ -54,7 +54,7 @@ const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { | |||||
| })); | })); | ||||
| }; | }; | ||||
| // 版本转成 treeData | |||||
| // 版本数据转成 treeData | |||||
| const convertVersionToTreeData = (parentId: number) => { | const convertVersionToTreeData = (parentId: number) => { | ||||
| return (item: string | MirrorVersion): TreeDataNode => { | return (item: string | MirrorVersion): TreeDataNode => { | ||||
| if (typeof item === 'string') { | if (typeof item === 'string') { | ||||
| @@ -88,7 +88,7 @@ const updateChildren = (parentId: number, children: TreeDataNode[]) => { | |||||
| }; | }; | ||||
| }; | }; | ||||
| // 得到数据集或者模型 id 和下属版本号 | |||||
| // 得到数据集\模型\镜像 id 和下属版本号 | |||||
| const getIdAndVersion = (versionKey: string) => { | const getIdAndVersion = (versionKey: string) => { | ||||
| const index = versionKey.indexOf('-'); | const index = versionKey.indexOf('-'); | ||||
| const id = Number(versionKey.slice(0, index)); | const id = Number(versionKey.slice(0, index)); | ||||
| @@ -137,12 +137,12 @@ function ResourceSelectorModal({ | |||||
| [originTreeData, searchText], | [originTreeData, searchText], | ||||
| ); | ); | ||||
| // 获取数据集或模型列表 | |||||
| // 获取数据集\模型\镜像列表 | |||||
| const getTreeData = async () => { | const getTreeData = async () => { | ||||
| const available_range = activeTab === CommonTabKeys.Private ? 0 : 1; | const available_range = activeTab === CommonTabKeys.Private ? 0 : 1; | ||||
| const params = { | const params = { | ||||
| page: 0, | page: 0, | ||||
| size: 200, | |||||
| size: 1000, | |||||
| [selectorTypeConfig[type].litReqParamKey]: available_range, | [selectorTypeConfig[type].litReqParamKey]: available_range, | ||||
| }; | }; | ||||
| const getListReq = selectorTypeConfig[type].getList; | const getListReq = selectorTypeConfig[type].getList; | ||||
| @@ -159,7 +159,7 @@ function ResourceSelectorModal({ | |||||
| } | } | ||||
| }; | }; | ||||
| // 获取数据集或模型版本列表 | |||||
| // 获取数据集\模型\镜像版本列表 | |||||
| const getVersions = async (parentId: number) => { | const getVersions = async (parentId: number) => { | ||||
| const getVersionsReq = selectorTypeConfig[type].getVersions; | const getVersionsReq = selectorTypeConfig[type].getVersions; | ||||
| const [res, error] = await to(getVersionsReq(parentId)); | const [res, error] = await to(getVersionsReq(parentId)); | ||||
| @@ -266,21 +266,17 @@ function ResourceSelectorModal({ | |||||
| // 提交 | // 提交 | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| if (checkedKeys.length > 0) { | if (checkedKeys.length > 0) { | ||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| onOk?.(files[0].file_name); | |||||
| } else { | |||||
| const last = checkedKeys[0] as string; | |||||
| const { id, version } = getIdAndVersion(last); | |||||
| const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string; | |||||
| const res = { | |||||
| id, | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| activeTab: activeTab as CommonTabKeys, | |||||
| }; | |||||
| onOk?.(res); | |||||
| } | |||||
| const last = checkedKeys[0] as string; | |||||
| const { id, version } = getIdAndVersion(last); | |||||
| const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string; | |||||
| const res = { | |||||
| id, | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| activeTab: activeTab as CommonTabKeys, | |||||
| }; | |||||
| onOk?.(res); | |||||
| } else { | } else { | ||||
| onOk?.(null); | onOk?.(null); | ||||
| } | } | ||||
| @@ -1,6 +1,8 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { useStateRef, useVisible } from '@/hooks'; | import { useStateRef, useVisible } from '@/hooks'; | ||||
| import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | |||||
| import { fittingString } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| import { App, Button } from 'antd'; | import { App, Button } from 'antd'; | ||||
| @@ -8,8 +10,8 @@ import { useEffect, useRef } from 'react'; | |||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||
| import { s8 } from '../../../utils'; | import { s8 } from '../../../utils'; | ||||
| import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | ||||
| import ModelMenu from '../components/ModelMenu'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import ModelMenus from './modelMenus'; | |||||
| import Props from './props'; | import Props from './props'; | ||||
| import { findAllParentNodes, findFirstDuplicate } from './utils'; | import { findAllParentNodes, findFirstDuplicate } from './utils'; | ||||
| @@ -27,6 +29,11 @@ const EditPipeline = () => { | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| let sourceAnchorIdx, targetAnchorIdx; | let sourceAnchorIdx, targetAnchorIdx; | ||||
| useEffect(() => { | |||||
| initMenu(); | |||||
| getFirstWorkflow(locationParams.id); | |||||
| }, []); | |||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| console.log(val); | console.log(val); | ||||
| const _x = val.x; | const _x = val.x; | ||||
| @@ -51,8 +58,16 @@ const EditPipeline = () => { | |||||
| return item.id === val.id; | return item.id === val.id; | ||||
| }); | }); | ||||
| data.nodes[index] = val; | data.nodes[index] = val; | ||||
| const zoom = graph.getZoom(); | |||||
| // 在拉取新数据重新渲染页面之前先获取点(0, 0)在画布上的位置 | |||||
| const lastPoint = graph.getCanvasByPoint(0, 0); | |||||
| graph.changeData(data); | graph.changeData(data); | ||||
| graph.render(); | graph.render(); | ||||
| graph.zoomTo(zoom); | |||||
| // 获取重新渲染之后点(0, 0)在画布的位置 | |||||
| const newPoint = graph.getCanvasByPoint(0, 0); | |||||
| // 移动画布相对位移; | |||||
| graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y); | |||||
| } | } | ||||
| }; | }; | ||||
| const savePipeline = async (val) => { | const savePipeline = async (val) => { | ||||
| @@ -89,26 +104,14 @@ const EditPipeline = () => { | |||||
| closeParamsDrawer(); | closeParamsDrawer(); | ||||
| setTimeout(() => { | setTimeout(() => { | ||||
| if (val) { | if (val) { | ||||
| navgite({ pathname: `/pipeline` }); | |||||
| navgite({ pathname: `/pipeline/template` }); | |||||
| } | } | ||||
| }, 500); | }, 500); | ||||
| }); | }); | ||||
| }, 500); | }, 500); | ||||
| }; | }; | ||||
| const handlerClick = (e) => { | |||||
| e.stopPropagation(); | |||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||||
| graph.setItemState(e.item, 'nodeClicked', true); | |||||
| const parentNodes = findAllParentNodes(graph, e.item); | |||||
| // 如果没有打开过全局参数抽屉,获取不到全局参数 | |||||
| const globalParams = | |||||
| paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; | |||||
| propsRef.current.showDrawer(e, globalParams, parentNodes); | |||||
| } | |||||
| }; | |||||
| const getGraphData = (data) => { | const getGraphData = (data) => { | ||||
| if (graph) { | if (graph) { | ||||
| console.log(data); | |||||
| graph.data(data); | graph.data(data); | ||||
| graph.render(); | graph.render(); | ||||
| } else { | } else { | ||||
| @@ -304,49 +307,8 @@ const EditPipeline = () => { | |||||
| initGraph(); | initGraph(); | ||||
| }; | }; | ||||
| useEffect(() => { | |||||
| initMenu(); | |||||
| getFirstWorkflow(locationParams.id); | |||||
| return () => { | |||||
| graph.off('node:mouseenter', (e) => { | |||||
| graph.setItemState(e.item, 'showAnchors', true); | |||||
| graph.setItemState(e.item, 'nodeSelected', true); | |||||
| }); | |||||
| graph.off('node:mouseleave', (e) => { | |||||
| // this.graph.setItemState(e.item, 'showAnchors', false); | |||||
| graph.setItemState(e.item, 'nodeSelected', false); | |||||
| }); | |||||
| // graph.off('dblclick', handlerClick); | |||||
| }; | |||||
| }, []); | |||||
| const initGraph = () => { | const initGraph = () => { | ||||
| const fittingString = (str, maxWidth, fontSize) => { | |||||
| const ellipsis = '...'; | |||||
| const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0]; | |||||
| let currentWidth = 0; | |||||
| let res = str; | |||||
| const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters | |||||
| str.split('').forEach((letter, i) => { | |||||
| if (currentWidth > maxWidth - ellipsisLength) return; | |||||
| if (pattern.test(letter)) { | |||||
| // Chinese charactors | |||||
| currentWidth += fontSize; | |||||
| } else { | |||||
| // get the width of single letter according to the fontSize | |||||
| currentWidth += G6.Util.getLetterWidth(letter, fontSize); | |||||
| } | |||||
| if (currentWidth > maxWidth - ellipsisLength) { | |||||
| res = `${str.substr(0, i)}${ellipsis}`; | |||||
| } | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| // 获取文本的长度 | |||||
| const getTextSize = (str, maxWidth, fontSize) => { | |||||
| let width = G6.Util.getTextSize(str, fontSize)[0]; | |||||
| return width > maxWidth ? maxWidth : width; | |||||
| }; | |||||
| G6.registerNode( | G6.registerNode( | ||||
| 'rect-node', | 'rect-node', | ||||
| { | { | ||||
| @@ -399,6 +361,7 @@ const EditPipeline = () => { | |||||
| y: bbox.y + bbox.height * anchorPos[1], | y: bbox.y + bbox.height * anchorPos[1], | ||||
| fill: '#fff', | fill: '#fff', | ||||
| stroke: '#a4a4a5', | stroke: '#a4a4a5', | ||||
| cursor: 'crosshair', | |||||
| }, | }, | ||||
| name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') | name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') | ||||
| anchorPointIdx: i, // flag the idx of the anchor-point circle | anchorPointIdx: i, // flag the idx of the anchor-point circle | ||||
| @@ -412,14 +375,30 @@ const EditPipeline = () => { | |||||
| // response the state changes and show/hide the link-point circles | // response the state changes and show/hide the link-point circles | ||||
| setState(name, value, item) { | setState(name, value, item) { | ||||
| const anchorPoints = item | |||||
| .getContainer() | |||||
| .findAll((ele) => ele.get('name') === 'anchor-point'); | |||||
| anchorPoints.forEach((point) => { | |||||
| if (value || point.get('links') > 0) point.show(); | |||||
| else point.hide(); | |||||
| }); | |||||
| // } | |||||
| // const anchorPoints = item | |||||
| // .getContainer() | |||||
| // .findAll((ele) => ele.get('name') === 'anchor-point'); | |||||
| // anchorPoints.forEach((point) => { | |||||
| // if (value || point.get('links') > 0) point.show(); | |||||
| // else point.hide(); | |||||
| // }); | |||||
| const group = item.getContainer(); | |||||
| const shape = group.get('children')[0]; | |||||
| const anchorPoints = group.findAll((ele) => ele.get('name') === 'anchor-point'); | |||||
| if (name === 'hover') { | |||||
| if (value) { | |||||
| shape.attr('stroke', themes['primaryColor']); | |||||
| anchorPoints.forEach((point) => { | |||||
| point.show(); | |||||
| }); | |||||
| } else { | |||||
| shape.attr('stroke', '#fff'); | |||||
| anchorPoints.forEach((point) => { | |||||
| point.hide(); | |||||
| }); | |||||
| } | |||||
| } | |||||
| }, | }, | ||||
| }, | }, | ||||
| 'rect', | 'rect', | ||||
| @@ -427,12 +406,11 @@ const EditPipeline = () => { | |||||
| graph = new G6.Graph({ | graph = new G6.Graph({ | ||||
| container: graphRef.current, | container: graphRef.current, | ||||
| grid: true, | |||||
| width: graphRef.current.clientWidth || 500, | width: graphRef.current.clientWidth || 500, | ||||
| height: graphRef.current.clientHeight || '100%', | height: graphRef.current.clientHeight || '100%', | ||||
| animate: false, | animate: false, | ||||
| groupByTypes: false, | groupByTypes: false, | ||||
| fitView: false, | |||||
| fitView: true, | |||||
| plugins: [contextMenu], | plugins: [contextMenu], | ||||
| enabledStack: true, | enabledStack: true, | ||||
| modes: { | modes: { | ||||
| @@ -511,20 +489,8 @@ const EditPipeline = () => { | |||||
| lineWidth: 0.5, | lineWidth: 0.5, | ||||
| }, | }, | ||||
| }, | }, | ||||
| nodeStateStyles: { | |||||
| nodeSelected: { | |||||
| fill: 'red', | |||||
| shadowColor: 'red', | |||||
| stroke: 'red', | |||||
| 'text-shape': { | |||||
| fill: 'red', | |||||
| stroke: 'red', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| defaultEdge: { | defaultEdge: { | ||||
| // type: 'quadratic', | |||||
| // type: 'cubic-vertical', | |||||
| //type: 'cubic-vertical', | |||||
| style: { | style: { | ||||
| endArrow: { | endArrow: { | ||||
| @@ -567,17 +533,20 @@ const EditPipeline = () => { | |||||
| // linkCenter: true, | // linkCenter: true, | ||||
| fitView: true, | fitView: true, | ||||
| minZoom: 0.5, | minZoom: 0.5, | ||||
| maxZoom: 3, | |||||
| fitViewPadding: [320, 320, 220, 320], | |||||
| maxZoom: 5, | |||||
| fitViewPadding: 300, | |||||
| }); | |||||
| graph.on('node:click', (e) => { | |||||
| e.stopPropagation(); | |||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||||
| // graph.setItemState(e.item, 'nodeClicked', true); | |||||
| const parentNodes = findAllParentNodes(graph, e.item); | |||||
| // 如果没有打开过全局参数抽屉,获取不到全局参数 | |||||
| const globalParams = | |||||
| paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; | |||||
| propsRef.current.showDrawer(e, globalParams, parentNodes); | |||||
| } | |||||
| }); | }); | ||||
| // graph.on('dblclick', (e) => { | |||||
| // console.log(e.item); | |||||
| // if (e.item) { | |||||
| // graph.setItemState(e.item, 'nodeClicked', true); | |||||
| // handlerClick(e); | |||||
| // } | |||||
| // }); | |||||
| graph.on('node:click', handlerClick); | |||||
| graph.on('aftercreateedge', (e) => { | graph.on('aftercreateedge', (e) => { | ||||
| // update the sourceAnchor and targetAnchor for the newly added edge | // update the sourceAnchor and targetAnchor for the newly added edge | ||||
| graph.updateItem(e.edge, { | graph.updateItem(e.edge, { | ||||
| @@ -595,59 +564,6 @@ const EditPipeline = () => { | |||||
| }); | }); | ||||
| }); | }); | ||||
| }); | }); | ||||
| graph.on('node:mouseenter', (e) => { | |||||
| // this.graph.setItemState(e.item, 'showAnchors', true); | |||||
| graph.setItemState(e.item, 'nodeSelected', true); | |||||
| graph.updateItem(e.item, { | |||||
| // 节点的样式 | |||||
| style: { | |||||
| stroke: '#1664ff', | |||||
| }, | |||||
| }); | |||||
| }); | |||||
| graph.on('node:mouseleave', (e) => { | |||||
| // this.graph.setItemState(e.item, 'showAnchors', false); | |||||
| graph.setItemState(e.item, 'nodeSelected', false); | |||||
| graph.updateItem(e.item, { | |||||
| // 节点的样式 | |||||
| style: { | |||||
| stroke: 'transparent', | |||||
| }, | |||||
| }); | |||||
| }); | |||||
| graph.on('node:dragenter', (e) => { | |||||
| console.log(e.target.get('name')); | |||||
| console.log('node:dragenter'); | |||||
| graph.setItemState(e.item, 'nodeSelected', true); | |||||
| graph.updateItem(e.item, { | |||||
| // 节点的样式 | |||||
| style: { | |||||
| stroke: '#1664ff', | |||||
| }, | |||||
| }); | |||||
| }); | |||||
| graph.on('node:dragleave', (e) => { | |||||
| console.log(e.target.get('name')); | |||||
| console.log('node:dragleave'); | |||||
| graph.setItemState(e.item, 'nodeSelected', false); | |||||
| graph.updateItem(e.item, { | |||||
| // 节点的样式 | |||||
| style: { | |||||
| stroke: 'transparent', | |||||
| }, | |||||
| }); | |||||
| }); | |||||
| graph.on('node:dragstart', (e) => { | |||||
| console.log('node:dragstart'); | |||||
| graph.setItemState(e.item, 'nodeSelected', true); | |||||
| graph.updateItem(e.item, { | |||||
| // 节点的样式 | |||||
| style: { | |||||
| stroke: '#1664ff', | |||||
| }, | |||||
| }); | |||||
| }); | |||||
| graph.on('afterremoveitem', (e) => { | graph.on('afterremoveitem', (e) => { | ||||
| if (e.item && e.item.source && e.item.target) { | if (e.item && e.item.source && e.item.target) { | ||||
| const sourceNode = graph.findById(e.item.source); | const sourceNode = graph.findById(e.item.source); | ||||
| @@ -673,7 +589,6 @@ const EditPipeline = () => { | |||||
| } | } | ||||
| } | } | ||||
| }); | }); | ||||
| // after clicking on the first node, the edge is created, update the sourceAnchor | // after clicking on the first node, the edge is created, update the sourceAnchor | ||||
| graph.on('afteradditem', (e) => { | graph.on('afteradditem', (e) => { | ||||
| if (e.item && e.item.getType() === 'edge') { | if (e.item && e.item.getType() === 'edge') { | ||||
| @@ -682,16 +597,34 @@ const EditPipeline = () => { | |||||
| }); | }); | ||||
| } | } | ||||
| }); | }); | ||||
| graph.on('node:mouseenter', (e) => { | |||||
| graph.setItemState(e.item, 'hover', true); | |||||
| }); | |||||
| graph.on('node:mouseleave', (e) => { | |||||
| graph.setItemState(e.item, 'hover', false); | |||||
| }); | |||||
| graph.on('node:dragenter', (e) => { | |||||
| graph.setItemState(e.item, 'hover', true); | |||||
| }); | |||||
| graph.on('node:dragleave', (e) => { | |||||
| graph.setItemState(e.item, 'hover', false); | |||||
| }); | |||||
| graph.on('node:dragstart', (e) => { | |||||
| graph.setItemState(e.item, 'hover', true); | |||||
| }); | |||||
| graph.on('node:drag', (e) => { | |||||
| graph.setItemState(e.item, 'hover', true); | |||||
| }); | |||||
| window.onresize = () => { | window.onresize = () => { | ||||
| if (!graph || graph.get('destroyed')) return; | if (!graph || graph.get('destroyed')) return; | ||||
| if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight) | |||||
| return; | |||||
| graph.changeSize(graphRef.current.scrollWidth, graphRef.current.scrollHeight - 20); | |||||
| if (!graphRef.current) return; | |||||
| graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); | |||||
| graph.fitView(); | |||||
| }; | }; | ||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={styles['pipeline-container']}> | <div className={styles['pipeline-container']}> | ||||
| <ModelMenus onParDragEnd={onDragEnd}></ModelMenus> | |||||
| <ModelMenu onComponentDragEnd={onDragEnd}></ModelMenu> | |||||
| <div className={styles['pipeline-container__workflow']}> | <div className={styles['pipeline-container__workflow']}> | ||||
| <div className={styles['pipeline-container__workflow__top']}> | <div className={styles['pipeline-container__workflow__top']}> | ||||
| <Button | <Button | ||||
| @@ -4,7 +4,8 @@ | |||||
| background-color: #fff; | background-color: #fff; | ||||
| &__workflow { | &__workflow { | ||||
| flex: 1; | |||||
| flex: 1 1 0; | |||||
| min-width: 0; | |||||
| height: 100%; | height: 100%; | ||||
| &__top { | &__top { | ||||
| @@ -12,15 +13,15 @@ | |||||
| align-items: center; | align-items: center; | ||||
| justify-content: end; | justify-content: end; | ||||
| width: 100%; | width: 100%; | ||||
| height: 45px; | |||||
| padding: 0 30px; | |||||
| height: 52px; | |||||
| padding: 0 20px; | |||||
| background: #ffffff; | background: #ffffff; | ||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | ||||
| } | } | ||||
| &__graph { | &__graph { | ||||
| width: 100%; | width: 100%; | ||||
| height: calc(100% - 45px); | |||||
| height: calc(100% - 52px); | |||||
| background-color: @background-color; | background-color: @background-color; | ||||
| background-image: url(/assets/images/pipeline-canvas-back.png); | background-image: url(/assets/images/pipeline-canvas-back.png); | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| @@ -1,67 +0,0 @@ | |||||
| import { getComponentAll } from '@/services/pipeline/index.js'; | |||||
| import { Collapse } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import Styles from './modelMenus.less'; | |||||
| const ModelMenus = ({ onParDragEnd }) => { | |||||
| const [modelMenusList, setModelMenusList] = useState([]); | |||||
| useEffect(() => { | |||||
| getComponentAll().then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code === 200) { | |||||
| setModelMenusList(ret.data); | |||||
| } | |||||
| }); | |||||
| }, []); | |||||
| const dragEnd = (e, data) => { | |||||
| console.log(e, data); | |||||
| onParDragEnd({ | |||||
| ...data, | |||||
| x: e.clientX, | |||||
| y: e.clientY, | |||||
| label: data.component_label, | |||||
| img: `/assets/images/${data.icon_path}.png`, | |||||
| }); | |||||
| }; | |||||
| const { Panel } = Collapse; | |||||
| return ( | |||||
| <div className={Styles.collapse}> | |||||
| <div className={Styles.modelMenusTitle}>组件库</div> | |||||
| {modelMenusList && modelMenusList.length > 0 ? ( | |||||
| <Collapse | |||||
| collapsible="header" | |||||
| defaultActiveKey={modelMenusList.map((item) => item.key + '')} | |||||
| expandIconPosition="end" | |||||
| > | |||||
| {modelMenusList && modelMenusList.length > 0 | |||||
| ? modelMenusList.map((item) => ( | |||||
| <Panel header={<div>{item.name}</div>} key={item.key}> | |||||
| {item.value && item.value.length > 0 | |||||
| ? item.value.map((ele) => ( | |||||
| <div | |||||
| key={ele.id} | |||||
| draggable="true" | |||||
| onDragEnd={(e) => { | |||||
| dragEnd(e, ele); | |||||
| }} | |||||
| className={Styles.collapseItem} | |||||
| > | |||||
| {ele.icon_path && ( | |||||
| <img | |||||
| style={{ height: '16px', marginRight: '15px' }} | |||||
| src={`/assets/images/${ele.icon_path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| )} | |||||
| {ele.component_label} | |||||
| </div> | |||||
| )) | |||||
| : ''} | |||||
| </Panel> | |||||
| )) | |||||
| : ''} | |||||
| </Collapse> | |||||
| ) : null} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default ModelMenus; | |||||
| @@ -1,11 +1,19 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import ParameterInput from '@/components/ParameterInput'; | import ParameterInput from '@/components/ParameterInput'; | ||||
| import ParameterSelect from '@/components/ParameterSelect'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | import { useComputingResource } from '@/hooks/resource'; | ||||
| import { | |||||
| PipelineGlobalParam, | |||||
| PipelineNodeModelParameter, | |||||
| PipelineNodeModelSerialize, | |||||
| } from '@/types'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Button, Drawer, Form, Input, Select } from 'antd'; | |||||
| import { pick } from 'lodash'; | |||||
| import { INode } from '@antv/g6'; | |||||
| import { Button, Drawer, Form, Input, MenuProps, Select } from 'antd'; | |||||
| import { NamePath } from 'antd/es/form/interface'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | import { forwardRef, useImperativeHandle, useState } from 'react'; | ||||
| import PropsLabel from '../components/PropsLabel'; | import PropsLabel from '../components/PropsLabel'; | ||||
| import ResourceSelectorModal, { | import ResourceSelectorModal, { | ||||
| @@ -16,18 +24,22 @@ import styles from './props.less'; | |||||
| import { canInput, createMenuItems } from './utils'; | import { canInput, createMenuItems } from './utils'; | ||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| type PipelineNodeParameterProps = { | |||||
| onParentChange: (data: PipelineNodeModelSerialize) => void; | |||||
| }; | |||||
| const PipelineNodeParameter = forwardRef(({ onParentChange }: PipelineNodeParameterProps, ref) => { | |||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [stagingItem, setStagingItem] = useState({}); | |||||
| const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>( | |||||
| {} as PipelineNodeModelSerialize, | |||||
| ); | |||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const [selectedModel, setSelectedModel] = useState(undefined); // 选择的模型,为了再次打开时恢复原来的选择 | |||||
| const [selectedDataset, setSelectedDataset] = useState(undefined); // 选择的数据集,为了再次打开时恢复原来的选择 | |||||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); // 资源规模 | const [resourceStandardList, filterResourceStandard] = useComputingResource(); // 资源规模 | ||||
| const [menuItems, setMenuItems] = useState([]); | |||||
| const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | |||||
| const afterOpenChange = () => { | const afterOpenChange = () => { | ||||
| if (!open) { | if (!open) { | ||||
| console.log('zzzzz', form.getFieldsValue()); | |||||
| console.log('getFieldsValue', form.getFieldsValue()); | |||||
| const control_strategy = form.getFieldValue('control_strategy'); | const control_strategy = form.getFieldValue('control_strategy'); | ||||
| const in_parameters = form.getFieldValue('in_parameters'); | const in_parameters = form.getFieldValue('in_parameters'); | ||||
| const out_parameters = form.getFieldValue('out_parameters'); | const out_parameters = form.getFieldValue('out_parameters'); | ||||
| @@ -54,7 +66,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| return Promise.reject(propsError); | return Promise.reject(propsError); | ||||
| } | } | ||||
| }, | }, | ||||
| showDrawer(e, params, parentNodes) { | |||||
| showDrawer(e: any, params: PipelineGlobalParam[], parentNodes: INode[]) { | |||||
| if (e.item && e.item.getModel()) { | if (e.item && e.item.getModel()) { | ||||
| form.resetFields(); | form.resetFields(); | ||||
| const model = e.item.getModel(); | const model = e.item.getModel(); | ||||
| @@ -75,8 +87,6 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| } catch (error) { | } catch (error) { | ||||
| console.log(error); | console.log(error); | ||||
| } | } | ||||
| setSelectedModel(undefined); | |||||
| setSelectedDataset(undefined); | |||||
| setOpen(true); | setOpen(true); | ||||
| // 参数下拉菜单 | // 参数下拉菜单 | ||||
| @@ -89,50 +99,70 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| })); | })); | ||||
| // 选择数据集、模型、镜像 | // 选择数据集、模型、镜像 | ||||
| const selectResource = (name, item) => { | |||||
| let type; | |||||
| let resource; | |||||
| const selectResource = ( | |||||
| formItemName: NamePath, | |||||
| item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | |||||
| ) => { | |||||
| let type: ResourceSelectorType; | |||||
| switch (item.item_type) { | switch (item.item_type) { | ||||
| case 'dataset': | case 'dataset': | ||||
| type = ResourceSelectorType.Dataset; | type = ResourceSelectorType.Dataset; | ||||
| resource = selectedDataset; | |||||
| break; | break; | ||||
| case 'model': | case 'model': | ||||
| type = ResourceSelectorType.Model; | type = ResourceSelectorType.Model; | ||||
| resource = selectedModel; | |||||
| break; | break; | ||||
| default: | default: | ||||
| type = ResourceSelectorType.Mirror; | type = ResourceSelectorType.Mirror; | ||||
| break; | break; | ||||
| } | } | ||||
| const fieldValue = form.getFieldValue(formItemName); | |||||
| const activeTab = fieldValue?.activeTab as CommonTabKeys | undefined; | |||||
| const expandedKeys = Array.isArray(fieldValue?.expandedKeys) ? fieldValue?.expandedKeys : []; | |||||
| const checkedKeys = Array.isArray(fieldValue?.checkedKeys) ? fieldValue?.checkedKeys : []; | |||||
| const { close } = openAntdModal(ResourceSelectorModal, { | const { close } = openAntdModal(ResourceSelectorModal, { | ||||
| type, | type, | ||||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||||
| defaultActiveTab: resource?.activeTab, | |||||
| defaultExpandedKeys: expandedKeys, | |||||
| defaultCheckedKeys: checkedKeys, | |||||
| defaultActiveTab: activeTab, | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| form.setFieldValue(name, res); | |||||
| const { activeTab, id, version, path } = res; | |||||
| if (formItemName === 'image') { | |||||
| form.setFieldValue(formItemName, path); | |||||
| } else { | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: path, | |||||
| showValue: path, | |||||
| fromSelect: true, | |||||
| activeTab, | |||||
| expandedKeys: [id], | |||||
| checkedKeys: [`${id}-${version}`], | |||||
| }); | |||||
| } | |||||
| } else { | } else { | ||||
| const jsonObj = pick(res, ['id', 'version', 'path']); | |||||
| const { activeTab, id, name, version, path } = res; | |||||
| const jsonObj = { | |||||
| id, | |||||
| version, | |||||
| path, | |||||
| }; | |||||
| const value = JSON.stringify(jsonObj); | const value = JSON.stringify(jsonObj); | ||||
| const showValue = `${res.name}:${res.version}`; | |||||
| form.setFieldValue(name, { ...item, value, showValue, fromSelect: true }); | |||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(res); | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(res); | |||||
| } | |||||
| const showValue = `${name}:${version}`; | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value, | |||||
| showValue, | |||||
| fromSelect: true, | |||||
| activeTab, | |||||
| expandedKeys: [id], | |||||
| checkedKeys: [`${id}-${version}`], | |||||
| }); | |||||
| } | } | ||||
| } else { | } else { | ||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(undefined); | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(undefined); | |||||
| } | |||||
| form.setFieldValue(name, ''); | |||||
| form.setFieldValue(formItemName, ''); | |||||
| } | } | ||||
| close(); | close(); | ||||
| }, | }, | ||||
| @@ -140,9 +170,9 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| }; | }; | ||||
| // 获取选择数据集、模型后面按钮 icon | // 获取选择数据集、模型后面按钮 icon | ||||
| const getSelectBtnIcon = (item) => { | |||||
| const getSelectBtnIcon = (item: { item_type: string }) => { | |||||
| const type = item.item_type; | const type = item.item_type; | ||||
| let selectorType; | |||||
| let selectorType: ResourceSelectorType; | |||||
| if (type === 'dataset') { | if (type === 'dataset') { | ||||
| selectorType = ResourceSelectorType.Dataset; | selectorType = ResourceSelectorType.Dataset; | ||||
| } else if (type === 'model') { | } else if (type === 'model') { | ||||
| @@ -155,10 +185,33 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| }; | }; | ||||
| // 参数回填 | // 参数回填 | ||||
| const handleParameterClick = (name, value) => { | |||||
| const handleParameterClick = (name: NamePath, value: any) => { | |||||
| form.setFieldValue(name, value); | form.setFieldValue(name, value); | ||||
| }; | }; | ||||
| // form item label | |||||
| const getLabel = ( | |||||
| item: { key: string; value: PipelineNodeModelParameter }, | |||||
| namePrefix: string, | |||||
| ) => { | |||||
| return item.value.type === 'select' ? ( | |||||
| item.value.label + '(' + item.key + ')' | |||||
| ) : ( | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick([namePrefix, item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| // 控制策略 | // 控制策略 | ||||
| const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( | const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( | ||||
| ([key, value]) => ({ key, value }), | ([key, value]) => ({ key, value }), | ||||
| @@ -290,7 +343,6 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| ]} | ]} | ||||
| > | > | ||||
| <Select | <Select | ||||
| showSearch | |||||
| placeholder="请选择资源规格" | placeholder="请选择资源规格" | ||||
| filterOption={filterResourceStandard} | filterOption={filterResourceStandard} | ||||
| options={resourceStandardList} | options={resourceStandardList} | ||||
| @@ -298,6 +350,8 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| label: 'description', | label: 'description', | ||||
| value: 'standard', | value: 'standard', | ||||
| }} | }} | ||||
| showSearch | |||||
| allowClear | |||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| @@ -332,31 +386,9 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| <Form.Item | <Form.Item | ||||
| key={item.key} | key={item.key} | ||||
| name={['control_strategy', item.key]} | name={['control_strategy', item.key]} | ||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['control_strategy', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| // getValueProps={(e) => { | |||||
| // return { value: e.value }; | |||||
| // }} | |||||
| // getValueFromEvent={(e) => { | |||||
| // return { | |||||
| // ...item.value, | |||||
| // value: e.target.value, | |||||
| // }; | |||||
| // }} | |||||
| label={getLabel(item, 'control_strategy')} | |||||
| > | > | ||||
| <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> | |||||
| <ParameterInput allowClear></ParameterInput> | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| <div className={styles['pipeline-drawer__title']}> | <div className={styles['pipeline-drawer__title']}> | ||||
| @@ -365,20 +397,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| {inParametersList.map((item) => ( | {inParametersList.map((item) => ( | ||||
| <Form.Item | <Form.Item | ||||
| key={item.key} | key={item.key} | ||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['in_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| label={getLabel(item, 'in_parameters')} | |||||
| required={item.value.require ? true : false} | required={item.value.require ? true : false} | ||||
| > | > | ||||
| <div className={styles['pipeline-drawer__ref-row']}> | <div className={styles['pipeline-drawer__ref-row']}> | ||||
| @@ -387,11 +406,11 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| noStyle | noStyle | ||||
| rules={[{ required: item.value.require ? true : false }]} | rules={[{ required: item.value.require ? true : false }]} | ||||
| > | > | ||||
| <ParameterInput | |||||
| placeholder={item.value.placeholder} | |||||
| canInput={canInput(item.value)} | |||||
| allowClear | |||||
| ></ParameterInput> | |||||
| {item.value.type === 'select' ? ( | |||||
| <ParameterSelect /> | |||||
| ) : ( | |||||
| <ParameterInput canInput={canInput(item.value)} allowClear></ParameterInput> | |||||
| )} | |||||
| </Form.Item> | </Form.Item> | ||||
| {item.value.type === 'ref' && ( | {item.value.type === 'ref' && ( | ||||
| <Form.Item noStyle> | <Form.Item noStyle> | ||||
| @@ -416,32 +435,10 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| <Form.Item | <Form.Item | ||||
| key={item.key} | key={item.key} | ||||
| name={['out_parameters', item.key]} | name={['out_parameters', item.key]} | ||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['out_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| label={getLabel(item, 'out_parameters')} | |||||
| rules={[{ required: item.value.require ? true : false }]} | rules={[{ required: item.value.require ? true : false }]} | ||||
| // getValueProps={(e) => { | |||||
| // return { value: e.value }; | |||||
| // }} | |||||
| // getValueFromEvent={(e) => { | |||||
| // return { | |||||
| // ...item.value, | |||||
| // value: e.target.value, | |||||
| // }; | |||||
| // }} | |||||
| > | > | ||||
| <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> | |||||
| <ParameterInput allowClear></ParameterInput> | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| </Form> | </Form> | ||||
| @@ -449,4 +446,4 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| ); | ); | ||||
| }); | }); | ||||
| export default Props; | |||||
| export default PipelineNodeParameter; | |||||
| @@ -77,6 +77,7 @@ export function getInParameterComponent( | |||||
| return null; | return null; | ||||
| } | } | ||||
| // 判断是否允许输入 | |||||
| export function canInput(parameter: PipelineNodeModelParameter) { | export function canInput(parameter: PipelineNodeModelParameter) { | ||||
| const { type, item_type } = parameter; | const { type, item_type } = parameter; | ||||
| return !( | return !( | ||||
| @@ -101,9 +101,8 @@ const Pipeline = () => { | |||||
| page: pageOption.current.page - 1, | page: pageOption.current.page - 1, | ||||
| size: pageOption.current.size, | size: pageOption.current.size, | ||||
| }; | }; | ||||
| console.log(params, pageOption); | |||||
| getWorkflow(params).then((ret) => { | getWorkflow(params).then((ret) => { | ||||
| if (ret.code == 200) { | |||||
| if (ret.code === 200) { | |||||
| setPipeList(ret.data.content); | setPipeList(ret.data.content); | ||||
| setTotal(ret.data.totalElements); | setTotal(ret.data.totalElements); | ||||
| @@ -13,7 +13,7 @@ | |||||
| } | } | ||||
| .PipelineBox { | .PipelineBox { | ||||
| height: calc(100% - 20px); | |||||
| height: 100%; | |||||
| .PipelineTable { | .PipelineTable { | ||||
| height: calc(100% - 60px); | height: calc(100% - 60px); | ||||
| :global { | :global { | ||||
| @@ -148,14 +148,14 @@ function QuickStart() { | |||||
| x={left + 2 * (192 + space) + 56} | x={left + 2 * (192 + space) + 56} | ||||
| y={139} | y={139} | ||||
| width={taskLeftArrowWidth} | width={taskLeftArrowWidth} | ||||
| height={125} | |||||
| height={120} | |||||
| arrowLeft={taskLeftArrowWidth} | arrowLeft={taskLeftArrowWidth} | ||||
| arrorwTop={-4} | arrorwTop={-4} | ||||
| borderLeft={1} | borderLeft={1} | ||||
| borderTop={1} | borderTop={1} | ||||
| /> | /> | ||||
| <WorkArrow | <WorkArrow | ||||
| x={left + 2 * (192 + space) + 56 + taskLeftArrowWidth + 16 + 131 + 6} | |||||
| x={left + 2 * (192 + space) + 56 + taskLeftArrowWidth + 16 + 131 + 4} | |||||
| y={127} | y={127} | ||||
| width={taskRightArrowWidth} | width={taskRightArrowWidth} | ||||
| height={156} | height={156} | ||||
| @@ -42,6 +42,7 @@ export const requestConfig: RequestConfig = { | |||||
| message.error('请重新登录'); | message.error('请重新登录'); | ||||
| return Promise.reject(response); | return Promise.reject(response); | ||||
| } else { | } else { | ||||
| console.log(message, data); | |||||
| message.error(data?.msg ?? '请求失败'); | message.error(data?.msg ?? '请求失败'); | ||||
| return Promise.reject(response); | return Promise.reject(response); | ||||
| } | } | ||||
| @@ -130,3 +130,11 @@ export function deleteDataset(id) { | |||||
| method: 'DELETE', | method: 'DELETE', | ||||
| }); | }); | ||||
| } | } | ||||
| // 获取模型依赖 | |||||
| export function getModelAtlasReq(data) { | |||||
| return request(`/api/mmp/modelDependency/queryModelAtlas`, { | |||||
| method: 'POST', | |||||
| data | |||||
| }); | |||||
| } | |||||
| @@ -59,3 +59,11 @@ export function updateModelDeploymentReq(data: any) { | |||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 获取模型部署操作指南 | |||||
| export function getModelDeploymentDocsReq(data: any) { | |||||
| return request(`/api/v1/model/getDocs`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| @@ -4,7 +4,7 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: flex-start; | justify-content: flex-start; | ||||
| font-size: 16px; | |||||
| font-size: @font-size-content; | |||||
| .anticon.kf-menu-item__default-icon { | .anticon.kf-menu-item__default-icon { | ||||
| display: inline !important; | display: inline !important; | ||||
| @@ -54,6 +54,6 @@ | |||||
| .ant-menu-submenu { | .ant-menu-submenu { | ||||
| .ant-menu-submenu-title:hover { | .ant-menu-submenu-title:hover { | ||||
| color: #1664ff !important; | |||||
| color: @primary-color !important; | |||||
| } | } | ||||
| } | } | ||||
| @@ -11,10 +11,11 @@ | |||||
| @background-color: #f9fafb; // 页面背景颜色 | @background-color: #f9fafb; // 页面背景颜色 | ||||
| @text-color: #1d1d20; | @text-color: #1d1d20; | ||||
| @text-color-secondary: #575757; | @text-color-secondary: #575757; | ||||
| @success-color: #1ace62; | |||||
| @success-color: #6ac21d; | |||||
| @error-color: #c73131; | @error-color: #c73131; | ||||
| @warning-color: #f98e1b; | @warning-color: #f98e1b; | ||||
| @abort-color: #8a8a8a; | @abort-color: #8a8a8a; | ||||
| @pending-color: #ecb934; | |||||
| @border-color: rgba(22, 100, 255, 0.3); | @border-color: rgba(22, 100, 255, 0.3); | ||||
| @border-color-secondary: rgba(22, 100, 255, 0.1); | @border-color-secondary: rgba(22, 100, 255, 0.1); | ||||
| @@ -78,4 +79,6 @@ | |||||
| fontSizeInput: @font-size-input; | fontSizeInput: @font-size-input; | ||||
| fontSizeInputLg: @font-size-input-lg; | fontSizeInputLg: @font-size-input-lg; | ||||
| siderBGColor: @sider-background-color; | siderBGColor: @sider-background-color; | ||||
| abortColor: @abort-color; | |||||
| pendingColor: @pending-color; | |||||
| } | } | ||||
| @@ -41,23 +41,33 @@ export type PipelineNodeModel = { | |||||
| control_strategy: string; | control_strategy: string; | ||||
| in_parameters: string; | in_parameters: string; | ||||
| out_parameters: string; | out_parameters: string; | ||||
| component_label: string; | |||||
| icon_path: string; | |||||
| }; | }; | ||||
| // 流水线 | |||||
| // 流水线节点模型数据 | |||||
| export type PipelineNodeModelParameter = { | export type PipelineNodeModelParameter = { | ||||
| label: string; | |||||
| value: any; | |||||
| require: number; | |||||
| type: string; | type: string; | ||||
| item_type: string; | item_type: string; | ||||
| label: string; | |||||
| value: any; | |||||
| require?: number; | |||||
| placeholder?: string; | placeholder?: string; | ||||
| describe?: string; | describe?: string; | ||||
| fromSelect?: boolean; | fromSelect?: boolean; | ||||
| showValue?: any; | showValue?: any; | ||||
| editable: number; | |||||
| editable?: number; | |||||
| activeTab?: string; // ResourceSelectorModal tab | |||||
| expandedKeys?: string[]; // ResourceSelectorModal expandedKeys | |||||
| checkedKeys?: string[]; // ResourceSelectorModal checkedKeys | |||||
| }; | }; | ||||
| // type ChangePropertyType<T, K extends keyof T, NewType> = Omit<T, K> & { [P in K]: NewType } | |||||
| // 修改属性类型 | |||||
| export type ChangePropertyType<T, K extends keyof T, NewType> = Omit<T, K> & { [P in K]: NewType }; | |||||
| // export type PascalCaseType<T> = { | |||||
| // [K in keyof T as `${Capitalize<string & K>}`]: T[K]; | |||||
| // } | |||||
| // 序列化后的流水线节点 | // 序列化后的流水线节点 | ||||
| export type PipelineNodeModelSerialize = Omit< | export type PipelineNodeModelSerialize = Omit< | ||||
| @@ -29,7 +29,7 @@ export function resolveBlob(res: any, mimeType: string) { | |||||
| document.body.removeChild(aLink); | document.body.removeChild(aLink); | ||||
| } | } | ||||
| export function downLoadZip(url: string, params: any) { | |||||
| export function downLoadZip(url: string, params?: any) { | |||||
| request(url, { | request(url, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| @@ -4,6 +4,8 @@ | |||||
| * @Description: 工具类 | * @Description: 工具类 | ||||
| */ | */ | ||||
| import G6 from '@antv/g6'; | |||||
| // 生成 8 位随机数 | // 生成 8 位随机数 | ||||
| export function s8() { | export function s8() { | ||||
| return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); | return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); | ||||
| @@ -29,8 +31,22 @@ export function parseJsonText(text?: string | null): any | null { | |||||
| } | } | ||||
| } | } | ||||
| // underscore-to-camelCase | |||||
| // 判断是否为对象 | |||||
| function isPlainObject(value: any) { | |||||
| if (value === null || typeof value !== 'object') return false; | |||||
| let proto = Object.getPrototypeOf(value); | |||||
| while (proto !== null) { | |||||
| if (proto.constructor && proto.constructor !== Object) return false; | |||||
| proto = Object.getPrototypeOf(proto); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| // underscore to camelCase | |||||
| export function underscoreToCamelCase(obj: Record<string, any>) { | export function underscoreToCamelCase(obj: Record<string, any>) { | ||||
| if (!isPlainObject(obj)) { | |||||
| return obj; | |||||
| } | |||||
| const newObj: Record<string, any> = {}; | const newObj: Record<string, any> = {}; | ||||
| for (const key in obj) { | for (const key in obj) { | ||||
| if (obj.hasOwnProperty(key)) { | if (obj.hasOwnProperty(key)) { | ||||
| @@ -38,7 +54,9 @@ export function underscoreToCamelCase(obj: Record<string, any>) { | |||||
| return $1.toUpperCase().replace('[-_]', '').replace('_', ''); | return $1.toUpperCase().replace('[-_]', '').replace('_', ''); | ||||
| }); | }); | ||||
| let value = obj[key]; | let value = obj[key]; | ||||
| if (typeof value === 'object' && value !== null) { | |||||
| if (Array.isArray(value)) { | |||||
| value = value.map((item) => underscoreToCamelCase(item)); | |||||
| } else if (isPlainObject(value)) { | |||||
| value = underscoreToCamelCase(value); | value = underscoreToCamelCase(value); | ||||
| } | } | ||||
| newObj[newKey] = value; | newObj[newKey] = value; | ||||
| @@ -47,14 +65,19 @@ export function underscoreToCamelCase(obj: Record<string, any>) { | |||||
| return newObj; | return newObj; | ||||
| } | } | ||||
| // camelCase-to-underscore | |||||
| // camelCase to underscore | |||||
| export function camelCaseToUnderscore(obj: Record<string, any>) { | export function camelCaseToUnderscore(obj: Record<string, any>) { | ||||
| if (!isPlainObject(obj)) { | |||||
| return obj; | |||||
| } | |||||
| const newObj: Record<string, any> = {}; | const newObj: Record<string, any> = {}; | ||||
| for (const key in obj) { | for (const key in obj) { | ||||
| if (obj.hasOwnProperty(key)) { | if (obj.hasOwnProperty(key)) { | ||||
| const newKey = key.replace(/([A-Z])/g, '_$1').toLowerCase(); | const newKey = key.replace(/([A-Z])/g, '_$1').toLowerCase(); | ||||
| let value = obj[key]; | let value = obj[key]; | ||||
| if (typeof value === 'object' && value !== null) { | |||||
| if (Array.isArray(value)) { | |||||
| value = value.map((item) => camelCaseToUnderscore(item)); | |||||
| } else if (isPlainObject(value)) { | |||||
| value = camelCaseToUnderscore(value); | value = camelCaseToUnderscore(value); | ||||
| } | } | ||||
| newObj[newKey] = value; | newObj[newKey] = value; | ||||
| @@ -63,15 +86,20 @@ export function camelCaseToUnderscore(obj: Record<string, any>) { | |||||
| return newObj; | return newObj; | ||||
| } | } | ||||
| // null 转 undefined | |||||
| // null to undefined | |||||
| export function nullToUndefined(obj: Record<string, any>) { | export function nullToUndefined(obj: Record<string, any>) { | ||||
| if (!isPlainObject(obj)) { | |||||
| return obj; | |||||
| } | |||||
| const newObj: Record<string, any> = {}; | const newObj: Record<string, any> = {}; | ||||
| for (const key in obj) { | for (const key in obj) { | ||||
| if (obj.hasOwnProperty(key)) { | if (obj.hasOwnProperty(key)) { | ||||
| const value = obj[key]; | const value = obj[key]; | ||||
| if (value === null) { | if (value === null) { | ||||
| newObj[key] = undefined; | newObj[key] = undefined; | ||||
| } else if (typeof value === 'object' && value !== null) { | |||||
| } else if (Array.isArray(value)) { | |||||
| newObj[key] = value.map((item) => nullToUndefined(item)); | |||||
| } else if (isPlainObject(value)) { | |||||
| newObj[key] = nullToUndefined(value); | newObj[key] = nullToUndefined(value); | ||||
| } else { | } else { | ||||
| newObj[key] = value; | newObj[key] = value; | ||||
| @@ -80,3 +108,62 @@ export function nullToUndefined(obj: Record<string, any>) { | |||||
| } | } | ||||
| return newObj; | return newObj; | ||||
| } | } | ||||
| /** | |||||
| * Changes the property names of an object based on a mapping provided. | |||||
| * | |||||
| * @param obj - The object whose property names need to be changed. | |||||
| * @param mapping - The mapping of old property names to new property names. | |||||
| * @return The object with the changed property names. | |||||
| */ | |||||
| export function changePropertyName(obj: Record<string, any>, mapping: Record<string, string>) { | |||||
| if (!isPlainObject(obj)) { | |||||
| return obj; | |||||
| } | |||||
| const newObj: Record<string, any> = {}; | |||||
| for (const key in obj) { | |||||
| if (obj.hasOwnProperty(key)) { | |||||
| let value = obj[key]; | |||||
| const newKey = mapping.hasOwnProperty(key) ? mapping[key] : key; | |||||
| if (Array.isArray(value)) { | |||||
| value = value.map((item) => changePropertyName(item, mapping)); | |||||
| } else if (isPlainObject(value)) { | |||||
| value = changePropertyName(value, mapping); | |||||
| } | |||||
| newObj[newKey] = value; | |||||
| } | |||||
| } | |||||
| return newObj; | |||||
| } | |||||
| /** | |||||
| * 计算显示的字符串 | |||||
| * @param tr 要裁剪的字符串 | |||||
| * @param maxWidth 最大宽度 | |||||
| * @param fontSize 字体大小 | |||||
| * @return 处理后的字符串 | |||||
| */ | |||||
| export const fittingString = (str: string, maxWidth: number, fontSize: number) => { | |||||
| if (!str) { | |||||
| return ''; | |||||
| } | |||||
| const ellipsis = '...'; | |||||
| const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0]; | |||||
| let currentWidth = 0; | |||||
| let res = str; | |||||
| const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters | |||||
| str.split('').forEach((letter, i) => { | |||||
| if (currentWidth > maxWidth - ellipsisLength) return; | |||||
| if (pattern.test(letter)) { | |||||
| // Chinese charactors | |||||
| currentWidth += fontSize; | |||||
| } else { | |||||
| // get the width of single letter according to the fontSize | |||||
| currentWidth += G6.Util.getLetterWidth(letter, fontSize); | |||||
| } | |||||
| if (currentWidth > maxWidth - ellipsisLength) { | |||||
| res = `${str.substring(0, i)}${ellipsis}`; | |||||
| } | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| @@ -4,6 +4,7 @@ | |||||
| * @Description: UI 公共方法 | * @Description: UI 公共方法 | ||||
| */ | */ | ||||
| import { PageEnum } from '@/enums/pagesEnums'; | import { PageEnum } from '@/enums/pagesEnums'; | ||||
| import { removeAllPageCacheState } from '@/hooks/pageCacheState'; | |||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { history } from '@umijs/max'; | import { history } from '@umijs/max'; | ||||
| import { Modal, message, type ModalFuncProps, type UploadFile } from 'antd'; | import { Modal, message, type ModalFuncProps, type UploadFile } from 'antd'; | ||||
| @@ -49,17 +50,20 @@ export const getFileListFromEvent = (e: any) => { | |||||
| }); | }); | ||||
| }; | }; | ||||
| // 去登录页面 | |||||
| /** | |||||
| * 跳转到登录页 | |||||
| * @param toHome 是否跳转到首页 | |||||
| */ | |||||
| export const gotoLoginPage = (toHome: boolean = true) => { | export const gotoLoginPage = (toHome: boolean = true) => { | ||||
| const { pathname, search } = window.location; | |||||
| const { pathname, search } = location; | |||||
| const urlParams = new URLSearchParams(); | const urlParams = new URLSearchParams(); | ||||
| urlParams.append('redirect', pathname + search); | urlParams.append('redirect', pathname + search); | ||||
| const newSearch = | |||||
| toHome && pathname !== PageEnum.LOGIN && pathname !== '/' ? '' : urlParams.toString(); | |||||
| console.log('pathname', pathname); | |||||
| console.log('search', search); | |||||
| if (window.location.pathname !== PageEnum.LOGIN) { | |||||
| const newSearch = toHome && pathname !== '/' ? '' : urlParams.toString(); | |||||
| // console.log('pathname', pathname); | |||||
| // console.log('search', search); | |||||
| if (pathname !== PageEnum.LOGIN) { | |||||
| closeAllModals(); | closeAllModals(); | ||||
| removeAllPageCacheState(); | |||||
| history.replace({ | history.replace({ | ||||
| pathname: PageEnum.LOGIN, | pathname: PageEnum.LOGIN, | ||||
| search: newSearch, | search: newSearch, | ||||
| @@ -0,0 +1,90 @@ | |||||
| package com.ruoyi.platform.controller.devEnvironment; | |||||
| import com.ruoyi.common.core.web.controller.BaseController; | |||||
| import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | |||||
| import com.ruoyi.platform.domain.DevEnvironment; | |||||
| import com.ruoyi.platform.service.DevEnvironmentService; | |||||
| import io.swagger.annotations.Api; | |||||
| import org.springframework.data.domain.Page; | |||||
| import org.springframework.data.domain.PageRequest; | |||||
| import org.springframework.http.ResponseEntity; | |||||
| import org.springframework.web.bind.annotation.*; | |||||
| import javax.annotation.Resource; | |||||
| /** | |||||
| * (DevEnvironment)表控制层 | |||||
| * | |||||
| * @author Xidaray | |||||
| * @since 2024-06-03 15:17:37 | |||||
| */ | |||||
| @RestController | |||||
| @RequestMapping("devEnvironment") | |||||
| @Api("开发环境管理") | |||||
| public class DevEnvironmentController extends BaseController { | |||||
| /** | |||||
| * 服务对象 | |||||
| */ | |||||
| @Resource | |||||
| private DevEnvironmentService devEnvironmentService; | |||||
| /** | |||||
| * 分页查询 | |||||
| * | |||||
| * @param devEnvironment 筛选条件 | |||||
| * @param page 页数 | |||||
| * @param size 每页大小 | |||||
| * @return 查询结果 | |||||
| */ | |||||
| @GetMapping | |||||
| public GenericsAjaxResult<Page<DevEnvironment>> queryByPage(DevEnvironment devEnvironment, int page, int size) { | |||||
| PageRequest pageRequest = PageRequest.of(page,size); | |||||
| return genericsSuccess(this.devEnvironmentService.queryByPage(devEnvironment, pageRequest)); | |||||
| } | |||||
| /** | |||||
| * 通过主键查询单条数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 单条数据 | |||||
| */ | |||||
| @GetMapping("{id}") | |||||
| public ResponseEntity<DevEnvironment> queryById(@PathVariable("id") Integer id) { | |||||
| return ResponseEntity.ok(this.devEnvironmentService.queryById(id)); | |||||
| } | |||||
| /** | |||||
| * 新增数据 | |||||
| * | |||||
| * @param devEnvironment 实体 | |||||
| * @return 新增结果 | |||||
| */ | |||||
| @PostMapping | |||||
| public ResponseEntity<DevEnvironment> add(@RequestBody DevEnvironment devEnvironment) { | |||||
| return ResponseEntity.ok(this.devEnvironmentService.insert(devEnvironment)); | |||||
| } | |||||
| /** | |||||
| * 编辑数据 | |||||
| * | |||||
| * @param devEnvironment 实体 | |||||
| * @return 编辑结果 | |||||
| */ | |||||
| @PutMapping | |||||
| public ResponseEntity<DevEnvironment> edit(@RequestBody DevEnvironment devEnvironment) { | |||||
| return ResponseEntity.ok(this.devEnvironmentService.update(devEnvironment)); | |||||
| } | |||||
| /** | |||||
| * 删除数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 删除是否成功 | |||||
| */ | |||||
| @DeleteMapping("{id}") | |||||
| public ResponseEntity<String> deleteById(@PathVariable("id") Integer id) { | |||||
| return ResponseEntity.ok(this.devEnvironmentService.removeById(id)); | |||||
| } | |||||
| } | |||||
| @@ -91,7 +91,7 @@ public class ExperimentController extends BaseController { | |||||
| */ | */ | ||||
| @PutMapping | @PutMapping | ||||
| @ApiOperation("编辑实验") | @ApiOperation("编辑实验") | ||||
| public GenericsAjaxResult<Experiment> edit(@RequestBody Experiment experiment) throws IOException { | |||||
| public GenericsAjaxResult<Experiment> edit(@RequestBody Experiment experiment) throws Exception { | |||||
| return genericsSuccess(this.experimentService.update(experiment)); | return genericsSuccess(this.experimentService.update(experiment)); | ||||
| } | } | ||||
| @@ -4,11 +4,11 @@ import com.ruoyi.common.core.web.controller.BaseController; | |||||
| import com.ruoyi.common.core.web.domain.AjaxResult; | import com.ruoyi.common.core.web.domain.AjaxResult; | ||||
| import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | ||||
| import com.ruoyi.platform.service.JupyterService; | import com.ruoyi.platform.service.JupyterService; | ||||
| import com.ruoyi.platform.vo.FrameLogPathVo; | |||||
| import io.swagger.annotations.Api; | import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiOperation; | import io.swagger.annotations.ApiOperation; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | |||||
| import org.springframework.web.bind.annotation.RequestMapping; | |||||
| import org.springframework.web.bind.annotation.RestController; | |||||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | |||||
| import org.springframework.web.bind.annotation.*; | |||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||
| import java.io.File; | import java.io.File; | ||||
| @@ -28,6 +28,35 @@ public class JupyterController extends BaseController { | |||||
| return genericsSuccess(jupyterService.getJupyterServiceUrl()); | return genericsSuccess(jupyterService.getJupyterServiceUrl()); | ||||
| } | } | ||||
| /** | |||||
| * 启动jupyter容器接口 | |||||
| * | |||||
| * @param id 开发环境配置id | |||||
| * @return url | |||||
| */ | |||||
| @PostMapping("/run/{id}") | |||||
| @ApiOperation("根据开发环境id启动jupyter pod") | |||||
| @ApiResponse | |||||
| public GenericsAjaxResult<String> runJupyter(@PathVariable("id") Integer id) throws Exception { | |||||
| return genericsSuccess(this.jupyterService.runJupyterService(id)); | |||||
| } | |||||
| /** | |||||
| * 停止jupyter容器接口 | |||||
| * | |||||
| * @param id 开发环境配置id | |||||
| * @return 操作结果 | |||||
| */ | |||||
| @DeleteMapping("/stop/{id}") | |||||
| @ApiOperation("根据开发环境id停止jupyter pod") | |||||
| @ApiResponse | |||||
| public GenericsAjaxResult<String> stopJupyter(@PathVariable("id") Integer id) throws Exception { | |||||
| return genericsSuccess(this.jupyterService.stopJupyterService(id)); | |||||
| } | |||||
| @GetMapping(value = "/upload") | @GetMapping(value = "/upload") | ||||
| public AjaxResult upload() throws Exception { | public AjaxResult upload() throws Exception { | ||||
| File file = new File("D://nexus-deploy.yaml"); | File file = new File("D://nexus-deploy.yaml"); | ||||
| @@ -0,0 +1,115 @@ | |||||
| package com.ruoyi.platform.controller.model; | |||||
| import com.ruoyi.common.core.web.controller.BaseController; | |||||
| import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | |||||
| import com.ruoyi.platform.domain.ModelDependency; | |||||
| import com.ruoyi.platform.domain.ModelsVersion; | |||||
| import com.ruoyi.platform.service.ModelDependencyService; | |||||
| import com.ruoyi.platform.vo.ModelDependcyTreeVo; | |||||
| import io.swagger.annotations.ApiOperation; | |||||
| import org.springframework.data.domain.Page; | |||||
| import org.springframework.data.domain.PageRequest; | |||||
| import org.springframework.http.ResponseEntity; | |||||
| import org.springframework.web.bind.annotation.*; | |||||
| import javax.annotation.Resource; | |||||
| import java.io.IOException; | |||||
| import java.util.List; | |||||
| /** | |||||
| * (ModelDependency)表控制层 | |||||
| * | |||||
| * @author Xidaray | |||||
| * @since 2024-05-29 13:51:23 | |||||
| */ | |||||
| @RestController | |||||
| @RequestMapping("modelDependency") | |||||
| public class ModelDependencyController extends BaseController { | |||||
| /** | |||||
| * 服务对象 | |||||
| */ | |||||
| @Resource | |||||
| private ModelDependencyService modelDependencyService; | |||||
| /** | |||||
| * 分页查询 | |||||
| * | |||||
| * @param modelDependency 筛选条件 | |||||
| * @param page 分页对象 | |||||
| * @param size 分页对象 | |||||
| * @return 查询结果 | |||||
| */ | |||||
| @GetMapping | |||||
| @ApiOperation("分页查询") | |||||
| public GenericsAjaxResult<Page<ModelDependency>> queryByPage(ModelDependency modelDependency, int page ,int size) { | |||||
| PageRequest pageRequest = PageRequest.of(page,size); | |||||
| return genericsSuccess(this.modelDependencyService.queryByPage(modelDependency, pageRequest)); | |||||
| } | |||||
| /** | |||||
| * 根据对象查询 | |||||
| * | |||||
| * @param modelDependency 筛选条件 | |||||
| * @return 查询结果 | |||||
| */ | |||||
| @GetMapping("/queryModelDependency") | |||||
| @ApiOperation("根据对象查询") | |||||
| public GenericsAjaxResult<List<ModelDependency>> queryByModelDependency(@RequestBody ModelDependency modelDependency) throws IOException { | |||||
| modelDependency.setState(1); | |||||
| return genericsSuccess(this.modelDependencyService.queryByModelDependency(modelDependency)); | |||||
| } | |||||
| /** | |||||
| * 通过主键查询单条数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 单条数据 | |||||
| */ | |||||
| @GetMapping("{id}") | |||||
| @ApiOperation("根据id查询") | |||||
| public GenericsAjaxResult<ModelDependency> queryById(@PathVariable("id") Integer id) { | |||||
| return genericsSuccess(this.modelDependencyService.queryById(id)); | |||||
| } | |||||
| /** | |||||
| * 新增数据 | |||||
| * | |||||
| * @param modelDependency 实体 | |||||
| * @return 新增结果 | |||||
| */ | |||||
| @PostMapping | |||||
| public GenericsAjaxResult<ModelDependency> add(@RequestBody ModelDependency modelDependency) { | |||||
| return genericsSuccess(this.modelDependencyService.insert(modelDependency)); | |||||
| } | |||||
| /** | |||||
| * 编辑数据 | |||||
| * | |||||
| * @param modelDependency 实体 | |||||
| * @return 编辑结果 | |||||
| */ | |||||
| @PutMapping | |||||
| public GenericsAjaxResult<ModelDependency> edit(@RequestBody ModelDependency modelDependency) { | |||||
| return genericsSuccess(this.modelDependencyService.update(modelDependency)); | |||||
| } | |||||
| /** | |||||
| * 删除数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 删除是否成功 | |||||
| */ | |||||
| @DeleteMapping("{id}") | |||||
| @ApiOperation("删除模型依赖") | |||||
| public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) { | |||||
| return genericsSuccess(this.modelDependencyService.removeById(id)); | |||||
| } | |||||
| @PostMapping("/queryModelAtlas") | |||||
| @ApiOperation("根据模型id与版本两个属性得到模型的演化图谱") | |||||
| public GenericsAjaxResult<ModelDependcyTreeVo> queryModelAtlas(@RequestBody ModelDependency modelDependency) throws Exception { | |||||
| return genericsSuccess(this.modelDependencyService.getModelDependencyTree(modelDependency)); | |||||
| } | |||||
| } | |||||
| @@ -11,6 +11,7 @@ import org.springframework.data.domain.PageRequest; | |||||
| import org.springframework.web.bind.annotation.*; | import org.springframework.web.bind.annotation.*; | ||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||
| import java.io.IOException; | |||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.Map; | import java.util.Map; | ||||
| @@ -129,7 +130,7 @@ public class ModelsVersionController extends BaseController { | |||||
| @DeleteMapping("/deleteVersion") | @DeleteMapping("/deleteVersion") | ||||
| @ApiOperation(value = "逻辑删除模型版本", notes = "根据模型ID和版本逻辑删除模型版本记录。") | @ApiOperation(value = "逻辑删除模型版本", notes = "根据模型ID和版本逻辑删除模型版本记录。") | ||||
| public GenericsAjaxResult<Map<Integer, String>> deleteModelsVersion(@RequestParam("models_id") Integer modelsId, | public GenericsAjaxResult<Map<Integer, String>> deleteModelsVersion(@RequestParam("models_id") Integer modelsId, | ||||
| @RequestParam("version") String version) { | |||||
| @RequestParam("version") String version) throws IOException { | |||||
| return genericsSuccess(this.modelsVersionService.deleteModelsVersion(modelsId, version)); | return genericsSuccess(this.modelsVersionService.deleteModelsVersion(modelsId, version)); | ||||
| } | } | ||||
| @@ -0,0 +1,213 @@ | |||||
| package com.ruoyi.platform.domain; | |||||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||||
| import java.util.Date; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * (DevEnvironment)实体类 | |||||
| * | |||||
| * @author Xidaray | |||||
| * @since 2024-06-03 15:17:37 | |||||
| */ | |||||
| @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) | |||||
| public class DevEnvironment implements Serializable { | |||||
| private static final long serialVersionUID = 936999018935545992L; | |||||
| /** | |||||
| * 主键 | |||||
| */ | |||||
| private Integer id; | |||||
| /** | |||||
| * 编辑器名称 | |||||
| */ | |||||
| private String name; | |||||
| /** | |||||
| * 状态 | |||||
| */ | |||||
| private String status; | |||||
| /** | |||||
| * 计算资源 | |||||
| */ | |||||
| private String computingResource; | |||||
| /** | |||||
| * 资源规格 | |||||
| */ | |||||
| private String standard; | |||||
| /** | |||||
| * 环境变量 | |||||
| */ | |||||
| private String envVariable; | |||||
| /** | |||||
| * 所用镜像 | |||||
| */ | |||||
| private String image; | |||||
| /** | |||||
| * 对应数据集 | |||||
| */ | |||||
| private String dataset; | |||||
| /** | |||||
| * 对应模型 | |||||
| */ | |||||
| private String model; | |||||
| /** | |||||
| * 备用字段1 | |||||
| */ | |||||
| private String altField1; | |||||
| /** | |||||
| * 备用字段2 | |||||
| */ | |||||
| private String altField2; | |||||
| /** | |||||
| * 创建者 | |||||
| */ | |||||
| private String createBy; | |||||
| /** | |||||
| * 创建时间 | |||||
| */ | |||||
| private Date createTime; | |||||
| /** | |||||
| * 更新者 | |||||
| */ | |||||
| private String updateBy; | |||||
| /** | |||||
| * 更新时间 | |||||
| */ | |||||
| private Date updateTime; | |||||
| /** | |||||
| * 状态,0失效1生效 | |||||
| */ | |||||
| private Integer state; | |||||
| public Integer getId() { | |||||
| return id; | |||||
| } | |||||
| public void setId(Integer id) { | |||||
| this.id = id; | |||||
| } | |||||
| public String getName() { | |||||
| return name; | |||||
| } | |||||
| public void setName(String name) { | |||||
| this.name = name; | |||||
| } | |||||
| public String getStatus() { | |||||
| return status; | |||||
| } | |||||
| public void setStatus(String status) { | |||||
| this.status = status; | |||||
| } | |||||
| public String getComputingResource() { | |||||
| return computingResource; | |||||
| } | |||||
| public void setComputingResource(String computingResource) { | |||||
| this.computingResource = computingResource; | |||||
| } | |||||
| public String getStandard() { | |||||
| return standard; | |||||
| } | |||||
| public void setStandard(String standard) { | |||||
| this.standard = standard; | |||||
| } | |||||
| public String getEnvVariable() { | |||||
| return envVariable; | |||||
| } | |||||
| public void setEnvVariable(String envVariable) { | |||||
| this.envVariable = envVariable; | |||||
| } | |||||
| public String getImage() { | |||||
| return image; | |||||
| } | |||||
| public void setImage(String image) { | |||||
| this.image = image; | |||||
| } | |||||
| public String getDataset() { | |||||
| return dataset; | |||||
| } | |||||
| public void setDataset(String dataset) { | |||||
| this.dataset = dataset; | |||||
| } | |||||
| public String getModel() { | |||||
| return model; | |||||
| } | |||||
| public void setModel(String model) { | |||||
| this.model = model; | |||||
| } | |||||
| public String getAltField1() { | |||||
| return altField1; | |||||
| } | |||||
| public void setAltField1(String altField1) { | |||||
| this.altField1 = altField1; | |||||
| } | |||||
| public String getAltField2() { | |||||
| return altField2; | |||||
| } | |||||
| public void setAltField2(String altField2) { | |||||
| this.altField2 = altField2; | |||||
| } | |||||
| public String getCreateBy() { | |||||
| return createBy; | |||||
| } | |||||
| public void setCreateBy(String createBy) { | |||||
| this.createBy = createBy; | |||||
| } | |||||
| public Date getCreateTime() { | |||||
| return createTime; | |||||
| } | |||||
| public void setCreateTime(Date createTime) { | |||||
| this.createTime = createTime; | |||||
| } | |||||
| public String getUpdateBy() { | |||||
| return updateBy; | |||||
| } | |||||
| public void setUpdateBy(String updateBy) { | |||||
| this.updateBy = updateBy; | |||||
| } | |||||
| public Date getUpdateTime() { | |||||
| return updateTime; | |||||
| } | |||||
| public void setUpdateTime(Date updateTime) { | |||||
| this.updateTime = updateTime; | |||||
| } | |||||
| public Integer getState() { | |||||
| return state; | |||||
| } | |||||
| public void setState(Integer state) { | |||||
| this.state = state; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,247 @@ | |||||
| package com.ruoyi.platform.domain; | |||||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||||
| import io.swagger.annotations.ApiModelProperty; | |||||
| import java.util.Date; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * (ModelDependency)实体类 | |||||
| * | |||||
| * @author Xidaray | |||||
| * @since 2024-05-29 13:51:23 | |||||
| */ | |||||
| @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) | |||||
| public class ModelDependency implements Serializable { | |||||
| private static final long serialVersionUID = -86753423714028539L; | |||||
| /** | |||||
| * 主键 | |||||
| */ | |||||
| private Integer id; | |||||
| /** | |||||
| * 当前模型id | |||||
| */ | |||||
| private Integer currentModelId; | |||||
| /** | |||||
| * 实验实例id | |||||
| */ | |||||
| private Integer expInsId; | |||||
| /** | |||||
| * 父模型 | |||||
| */ | |||||
| private String parentModels; | |||||
| /** | |||||
| * 引用项目 | |||||
| */ | |||||
| private String refItem; | |||||
| /** | |||||
| * 训练任务 | |||||
| */ | |||||
| private String trainTask; | |||||
| /** | |||||
| * 训练数据集 | |||||
| */ | |||||
| private String trainDataset; | |||||
| /** | |||||
| * 训练参数 | |||||
| */ | |||||
| private String trainParams; | |||||
| /** | |||||
| * 训练镜像 | |||||
| */ | |||||
| private String trainImage; | |||||
| /** | |||||
| * 测试数据集 | |||||
| */ | |||||
| private String testDataset; | |||||
| /** | |||||
| * 依赖项目 | |||||
| */ | |||||
| private String projectDependency; | |||||
| /** | |||||
| * 版本 | |||||
| */ | |||||
| private String version; | |||||
| /** | |||||
| * 创建者 | |||||
| */ | |||||
| private String createBy; | |||||
| /** | |||||
| * 创建时间 | |||||
| */ | |||||
| private Date createTime; | |||||
| /** | |||||
| * 更新者 | |||||
| */ | |||||
| private String updateBy; | |||||
| /** | |||||
| * 更新时间 | |||||
| */ | |||||
| private Date updateTime; | |||||
| /** | |||||
| * 状态,0失效1生效 | |||||
| */ | |||||
| private Integer state; | |||||
| private Long workflowId; | |||||
| private Models models; | |||||
| public Integer getId() { | |||||
| return id; | |||||
| } | |||||
| public void setId(Integer id) { | |||||
| this.id = id; | |||||
| } | |||||
| public Integer getCurrentModelId() { | |||||
| return currentModelId; | |||||
| } | |||||
| public void setCurrentModelId(Integer currentModelId) { | |||||
| this.currentModelId = currentModelId; | |||||
| } | |||||
| public Integer getExpInsId() { | |||||
| return expInsId; | |||||
| } | |||||
| public void setExpInsId(Integer expInsId) { | |||||
| this.expInsId = expInsId; | |||||
| } | |||||
| public String getParentModels() { | |||||
| return parentModels; | |||||
| } | |||||
| public void setParentModels(String parentModels) { | |||||
| this.parentModels = parentModels; | |||||
| } | |||||
| public String getRefItem() { | |||||
| return refItem; | |||||
| } | |||||
| public void setRefItem(String refItem) { | |||||
| this.refItem = refItem; | |||||
| } | |||||
| public String getTrainTask() { | |||||
| return trainTask; | |||||
| } | |||||
| public void setTrainTask(String trainTask) { | |||||
| this.trainTask = trainTask; | |||||
| } | |||||
| public String getTrainDataset() { | |||||
| return trainDataset; | |||||
| } | |||||
| public void setTrainDataset(String trainDataset) { | |||||
| this.trainDataset = trainDataset; | |||||
| } | |||||
| public String getTrainParams() { | |||||
| return trainParams; | |||||
| } | |||||
| public void setTrainParams(String trainParams) { | |||||
| this.trainParams = trainParams; | |||||
| } | |||||
| public String getTrainImage() { | |||||
| return trainImage; | |||||
| } | |||||
| public void setTrainImage(String trainImage) { | |||||
| this.trainImage = trainImage; | |||||
| } | |||||
| public String getTestDataset() { | |||||
| return testDataset; | |||||
| } | |||||
| public void setTestDataset(String testDataset) { | |||||
| this.testDataset = testDataset; | |||||
| } | |||||
| public String getProjectDependency() { | |||||
| return projectDependency; | |||||
| } | |||||
| public void setProjectDependency(String projectDependency) { | |||||
| this.projectDependency = projectDependency; | |||||
| } | |||||
| public String getVersion() { | |||||
| return version; | |||||
| } | |||||
| public void setVersion(String version) { | |||||
| this.version = version; | |||||
| } | |||||
| public String getCreateBy() { | |||||
| return createBy; | |||||
| } | |||||
| public void setCreateBy(String createBy) { | |||||
| this.createBy = createBy; | |||||
| } | |||||
| public Date getCreateTime() { | |||||
| return createTime; | |||||
| } | |||||
| public void setCreateTime(Date createTime) { | |||||
| this.createTime = createTime; | |||||
| } | |||||
| public String getUpdateBy() { | |||||
| return updateBy; | |||||
| } | |||||
| public void setUpdateBy(String updateBy) { | |||||
| this.updateBy = updateBy; | |||||
| } | |||||
| public Date getUpdateTime() { | |||||
| return updateTime; | |||||
| } | |||||
| public void setUpdateTime(Date updateTime) { | |||||
| this.updateTime = updateTime; | |||||
| } | |||||
| public Integer getState() { | |||||
| return state; | |||||
| } | |||||
| public void setState(Integer state) { | |||||
| this.state = state; | |||||
| } | |||||
| public Long getWorkflowId() { | |||||
| return workflowId; | |||||
| } | |||||
| public void setWorkflowId(Long workflowId) { | |||||
| this.workflowId = workflowId; | |||||
| } | |||||
| public Models getModels() { | |||||
| return models; | |||||
| } | |||||
| public void setModels(Models models) { | |||||
| this.models = models; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| package com.ruoyi.platform.domain.dependencydomain; | |||||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||||
| import lombok.Data; | |||||
| import java.io.Serializable; | |||||
| @Data | |||||
| @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) | |||||
| public class ProjectDepency implements Serializable { | |||||
| private String url; | |||||
| private String name; | |||||
| private String branch; | |||||
| } | |||||
| @@ -0,0 +1,17 @@ | |||||
| package com.ruoyi.platform.domain.dependencydomain; | |||||
| import com.fasterxml.jackson.databind.PropertyNamingStrategy; | |||||
| import com.fasterxml.jackson.databind.annotation.JsonNaming; | |||||
| import lombok.Data; | |||||
| import java.io.Serializable; | |||||
| @Data | |||||
| @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) | |||||
| public class TrainTaskDepency implements Serializable { | |||||
| //训练任务名 | |||||
| private String name; | |||||
| //实例id | |||||
| private Integer insId; | |||||
| //节点Id | |||||
| private String taskId; | |||||
| } | |||||
| @@ -0,0 +1,84 @@ | |||||
| package com.ruoyi.platform.mapper; | |||||
| import com.ruoyi.platform.domain.DevEnvironment; | |||||
| import org.apache.ibatis.annotations.Param; | |||||
| import org.springframework.data.domain.Pageable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * (DevEnvironment)表数据库访问层 | |||||
| * | |||||
| * @author Xidaray | |||||
| * @since 2024-06-03 15:17:37 | |||||
| */ | |||||
| public interface DevEnvironmentDao { | |||||
| /** | |||||
| * 通过ID查询单条数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| DevEnvironment queryById(Integer id); | |||||
| /** | |||||
| * 查询指定行数据 | |||||
| * | |||||
| * @param devEnvironment 查询条件 | |||||
| * @param pageable 分页对象 | |||||
| * @return 对象列表 | |||||
| */ | |||||
| List<DevEnvironment> queryAllByLimit(@Param("devEnvironment") DevEnvironment devEnvironment, @Param("pageable") Pageable pageable); | |||||
| /** | |||||
| * 统计总行数 | |||||
| * | |||||
| * @param devEnvironment 查询条件 | |||||
| * @return 总行数 | |||||
| */ | |||||
| long count(@Param("devEnvironment") DevEnvironment devEnvironment); | |||||
| /** | |||||
| * 新增数据 | |||||
| * | |||||
| * @param devEnvironment 实例对象 | |||||
| * @return 影响行数 | |||||
| */ | |||||
| int insert(@Param("devEnvironment") DevEnvironment devEnvironment); | |||||
| /** | |||||
| * 批量新增数据(MyBatis原生foreach方法) | |||||
| * | |||||
| * @param entities List<DevEnvironment> 实例对象列表 | |||||
| * @return 影响行数 | |||||
| */ | |||||
| int insertBatch(@Param("entities") List<DevEnvironment> entities); | |||||
| /** | |||||
| * 批量新增或按主键更新数据(MyBatis原生foreach方法) | |||||
| * | |||||
| * @param entities List<DevEnvironment> 实例对象列表 | |||||
| * | |||||
| * @return 影响行数 | |||||
| * @throws org.springframework.jdbc.BadSqlGrammarException 入参是空List的时候会抛SQL语句错误的异常,请自行校验入参 | |||||
| */ | |||||
| int insertOrUpdateBatch(@Param("entities") List<DevEnvironment> entities); | |||||
| /** | |||||
| * 修改数据 | |||||
| * | |||||
| * @param devEnvironment 实例对象 | |||||
| * @return 影响行数 | |||||
| */ | |||||
| int update(@Param("devEnvironment") DevEnvironment devEnvironment); | |||||
| /** | |||||
| * 通过主键删除数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 影响行数 | |||||
| */ | |||||
| int deleteById(Integer id); | |||||
| } | |||||
| @@ -0,0 +1,88 @@ | |||||
| package com.ruoyi.platform.mapper; | |||||
| import com.ruoyi.platform.domain.ModelDependency; | |||||
| import org.apache.ibatis.annotations.Param; | |||||
| import org.springframework.data.domain.Pageable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * (ModelDependency)表数据库访问层 | |||||
| * | |||||
| * @author Xidaray | |||||
| * @since 2024-05-29 13:51:23 | |||||
| */ | |||||
| public interface ModelDependencyDao { | |||||
| /** | |||||
| * 通过ID查询单条数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| ModelDependency queryById(Integer id); | |||||
| /** | |||||
| * 查询指定行数据 | |||||
| * | |||||
| * @param modelDependency 查询条件 | |||||
| * @param pageable 分页对象 | |||||
| * @return 对象列表 | |||||
| */ | |||||
| List<ModelDependency> queryAllByLimit(ModelDependency modelDependency, @Param("pageable") Pageable pageable); | |||||
| /** | |||||
| * 统计总行数 | |||||
| * | |||||
| * @param modelDependency 查询条件 | |||||
| * @return 总行数 | |||||
| */ | |||||
| long count(ModelDependency modelDependency); | |||||
| /** | |||||
| * 新增数据 | |||||
| * | |||||
| * @param modelDependency 实例对象 | |||||
| * @return 影响行数 | |||||
| */ | |||||
| int insert(ModelDependency modelDependency); | |||||
| /** | |||||
| * 批量新增数据(MyBatis原生foreach方法) | |||||
| * | |||||
| * @param entities List<ModelDependency> 实例对象列表 | |||||
| * @return 影响行数 | |||||
| */ | |||||
| int insertBatch(@Param("entities") List<ModelDependency> entities); | |||||
| /** | |||||
| * 批量新增或按主键更新数据(MyBatis原生foreach方法) | |||||
| * | |||||
| * @param entities List<ModelDependency> 实例对象列表 | |||||
| * @return 影响行数 | |||||
| * @throws org.springframework.jdbc.BadSqlGrammarException 入参是空List的时候会抛SQL语句错误的异常,请自行校验入参 | |||||
| */ | |||||
| int insertOrUpdateBatch(@Param("entities") List<ModelDependency> entities); | |||||
| /** | |||||
| * 修改数据 | |||||
| * | |||||
| * @param modelDependency 实例对象 | |||||
| * @return 影响行数 | |||||
| */ | |||||
| int update(ModelDependency modelDependency); | |||||
| /** | |||||
| * 通过主键删除数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 影响行数 | |||||
| */ | |||||
| int deleteById(Integer id); | |||||
| List<ModelDependency> queryByModelDependency(@Param("modelDependency") ModelDependency modelDependency); | |||||
| List<ModelDependency> queryChildrenByVersionId(@Param("model_id")String modelId, @Param("version")String version); | |||||
| } | |||||
| @@ -1,24 +1,26 @@ | |||||
| package com.ruoyi.platform.scheduling; | package com.ruoyi.platform.scheduling; | ||||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||||
| import com.ruoyi.platform.domain.Experiment; | import com.ruoyi.platform.domain.Experiment; | ||||
| import com.ruoyi.platform.domain.ExperimentIns; | import com.ruoyi.platform.domain.ExperimentIns; | ||||
| import com.ruoyi.platform.domain.ModelDependency; | |||||
| import com.ruoyi.platform.mapper.ExperimentDao; | import com.ruoyi.platform.mapper.ExperimentDao; | ||||
| import com.ruoyi.platform.mapper.ExperimentInsDao; | import com.ruoyi.platform.mapper.ExperimentInsDao; | ||||
| import com.ruoyi.platform.mapper.ModelDependencyDao; | |||||
| import com.ruoyi.platform.service.ExperimentInsService; | import com.ruoyi.platform.service.ExperimentInsService; | ||||
| import com.ruoyi.platform.service.ExperimentService; | |||||
| import com.ruoyi.platform.utils.JsonUtils; | |||||
| import com.ruoyi.system.api.model.LoginUser; | |||||
| import io.swagger.models.auth.In; | |||||
| import com.ruoyi.platform.service.ModelDependencyService; | |||||
| import com.ruoyi.platform.utils.JacksonUtil; | |||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.data.domain.Page; | |||||
| import org.springframework.scheduling.annotation.Scheduled; | import org.springframework.scheduling.annotation.Scheduled; | ||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.util.*; | |||||
| import java.util.stream.Collectors; | |||||
| import java.util.ArrayList; | |||||
| import java.util.Date; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| @Component() | @Component() | ||||
| public class ExperimentInstanceStatusTask { | public class ExperimentInstanceStatusTask { | ||||
| @@ -28,7 +30,8 @@ public class ExperimentInstanceStatusTask { | |||||
| private ExperimentDao experimentDao; | private ExperimentDao experimentDao; | ||||
| @Resource | @Resource | ||||
| private ExperimentInsDao experimentInsDao; | private ExperimentInsDao experimentInsDao; | ||||
| @Resource | |||||
| private ModelDependencyDao modelDependencyDao; | |||||
| private List<Integer> experimentIds = new ArrayList<>(); | private List<Integer> experimentIds = new ArrayList<>(); | ||||
| @Scheduled(cron = "0/30 * * * * ?") // 每30S执行一次 | @Scheduled(cron = "0/30 * * * * ?") // 每30S执行一次 | ||||
| @@ -55,12 +58,50 @@ public class ExperimentInstanceStatusTask { | |||||
| updateList.add(experimentIns); | updateList.add(experimentIns); | ||||
| } | } | ||||
| experimentInsDao.update(experimentIns); | |||||
| // experimentInsDao.update(experimentIns); | |||||
| } | } | ||||
| } | } | ||||
| if (updateList.size() > 0){ | if (updateList.size() > 0){ | ||||
| experimentInsDao.insertOrUpdateBatch(updateList); | experimentInsDao.insertOrUpdateBatch(updateList); | ||||
| //遍历模型关系表,找到 | |||||
| List<ModelDependency> modelDependencyList = new ArrayList<ModelDependency>(); | |||||
| for (ExperimentIns experimentIns : updateList){ | |||||
| ModelDependency modelDependencyquery = new ModelDependency(); | |||||
| modelDependencyquery.setExpInsId(experimentIns.getId()); | |||||
| modelDependencyquery.setState(2); | |||||
| List<ModelDependency> modelDependencyListquery = modelDependencyDao.queryByModelDependency(modelDependencyquery); | |||||
| if (modelDependencyListquery==null||modelDependencyListquery.size()==0){ | |||||
| continue; | |||||
| } | |||||
| ModelDependency modelDependency = modelDependencyListquery.get(0); | |||||
| //查看状态, | |||||
| if (StringUtils.equals("Failed",experimentIns.getStatus())){ | |||||
| //取出节点状态 | |||||
| String trainTask = modelDependency.getTrainTask(); | |||||
| Map<String, Object> trainMap = JacksonUtil.parseJSONStr2Map(trainTask); | |||||
| String task_id = (String) trainMap.get("task_id"); | |||||
| if (StringUtils.isEmpty(task_id)){ | |||||
| continue; | |||||
| } | |||||
| String nodesStatus = experimentIns.getNodesStatus(); | |||||
| Map<String, Object> nodeMaps = JacksonUtil.parseJSONStr2Map(nodesStatus); | |||||
| Map<String, Object> nodeMap = JacksonUtil.parseJSONStr2Map(JacksonUtil.toJSONString(nodeMaps.get(task_id))); | |||||
| if (nodeMap==null){ | |||||
| continue; | |||||
| } | |||||
| if (!StringUtils.equals("Succeeded",(String)nodeMap.get("phase"))){ | |||||
| modelDependency.setState(0); | |||||
| modelDependencyList.add(modelDependency); | |||||
| } | |||||
| } | |||||
| } | |||||
| if (modelDependencyList.size()>0) { | |||||
| modelDependencyDao.insertOrUpdateBatch(modelDependencyList); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,57 @@ | |||||
| package com.ruoyi.platform.service; | |||||
| import com.ruoyi.platform.domain.DevEnvironment; | |||||
| import org.springframework.data.domain.Page; | |||||
| import org.springframework.data.domain.PageRequest; | |||||
| /** | |||||
| * (DevEnvironment)表服务接口 | |||||
| * | |||||
| * @author Xidaray | |||||
| * @since 2024-06-03 15:17:37 | |||||
| */ | |||||
| public interface DevEnvironmentService { | |||||
| /** | |||||
| * 通过ID查询单条数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| DevEnvironment queryById(Integer id); | |||||
| /** | |||||
| * 分页查询 | |||||
| * | |||||
| * @param devEnvironment 筛选条件 | |||||
| * @param pageRequest 分页对象 | |||||
| * @return 查询结果 | |||||
| */ | |||||
| Page<DevEnvironment> queryByPage(DevEnvironment devEnvironment, PageRequest pageRequest); | |||||
| /** | |||||
| * 新增数据 | |||||
| * | |||||
| * @param devEnvironment 实例对象 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| DevEnvironment insert(DevEnvironment devEnvironment); | |||||
| /** | |||||
| * 修改数据 | |||||
| * | |||||
| * @param devEnvironment 实例对象 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| DevEnvironment update(DevEnvironment devEnvironment); | |||||
| /** | |||||
| * 通过主键删除数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 是否成功 | |||||
| */ | |||||
| boolean deleteById(Integer id); | |||||
| String removeById(Integer id); | |||||
| } | |||||
| @@ -100,7 +100,7 @@ public interface ExperimentInsService { | |||||
| /** | /** | ||||
| * 查询非终止态的实例 | * 查询非终止态的实例 | ||||
| * @return | |||||
| * | |||||
| */ | */ | ||||
| List<ExperimentIns> queryByExperimentIsNotTerminated(); | List<ExperimentIns> queryByExperimentIsNotTerminated(); | ||||
| @@ -48,7 +48,7 @@ public interface ExperimentService { | |||||
| * @param experiment 实例对象 | * @param experiment 实例对象 | ||||
| * @return 实例对象 | * @return 实例对象 | ||||
| */ | */ | ||||
| Experiment update(Experiment experiment) throws IOException; | |||||
| Experiment update(Experiment experiment) throws Exception; | |||||
| /** | /** | ||||
| * 通过主键删除数据 | * 通过主键删除数据 | ||||
| @@ -1,5 +1,7 @@ | |||||
| package com.ruoyi.platform.service; | package com.ruoyi.platform.service; | ||||
| import com.ruoyi.platform.vo.FrameLogPathVo; | |||||
| import java.io.InputStream; | import java.io.InputStream; | ||||
| public interface JupyterService { | public interface JupyterService { | ||||
| @@ -8,4 +10,8 @@ public interface JupyterService { | |||||
| void upload(InputStream inputStream); | void upload(InputStream inputStream); | ||||
| void mlflow(); | void mlflow(); | ||||
| String runJupyterService(Integer id); | |||||
| String stopJupyterService(Integer id) throws Exception; | |||||
| } | } | ||||
| @@ -0,0 +1,65 @@ | |||||
| package com.ruoyi.platform.service; | |||||
| import com.ruoyi.platform.domain.ModelDependency; | |||||
| import com.ruoyi.platform.vo.ModelDependcyTreeVo; | |||||
| import org.springframework.data.domain.Page; | |||||
| import org.springframework.data.domain.PageRequest; | |||||
| import java.io.IOException; | |||||
| import java.util.List; | |||||
| /** | |||||
| * (ModelDependency)表服务接口 | |||||
| * | |||||
| * @author Xidaray | |||||
| * @since 2024-05-29 13:51:23 | |||||
| */ | |||||
| public interface ModelDependencyService { | |||||
| /** | |||||
| * 通过ID查询单条数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| ModelDependency queryById(Integer id); | |||||
| /** | |||||
| * 分页查询 | |||||
| * | |||||
| * @param modelDependency 筛选条件 | |||||
| * @param pageRequest 分页对象 | |||||
| * @return 查询结果 | |||||
| */ | |||||
| Page<ModelDependency> queryByPage(ModelDependency modelDependency, PageRequest pageRequest); | |||||
| /** | |||||
| * 修改数据 | |||||
| * | |||||
| * @param modelDependency 实例对象 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| ModelDependency update(ModelDependency modelDependency); | |||||
| /** | |||||
| * 新增数据 | |||||
| * | |||||
| * @param modelDependency 实例对象 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| ModelDependency insert(ModelDependency modelDependency); | |||||
| /** | |||||
| * 通过主键删除数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 是否成功 | |||||
| */ | |||||
| boolean deleteById(Integer id); | |||||
| String removeById(Integer id); | |||||
| List<ModelDependency> queryByModelDependency(ModelDependency modelDependency) throws IOException; | |||||
| ModelDependcyTreeVo getModelDependencyTree(ModelDependency modelDependency) throws Exception; | |||||
| } | |||||
| @@ -6,6 +6,7 @@ import com.ruoyi.platform.domain.ModelsVersion; | |||||
| import org.springframework.data.domain.Page; | import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.PageRequest; | import org.springframework.data.domain.PageRequest; | ||||
| import java.io.IOException; | |||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.Map; | import java.util.Map; | ||||
| @@ -68,7 +69,7 @@ public interface ModelsVersionService { | |||||
| Map<String,Object> queryByModelsIdAndVersion(Integer modelsId, String version); | Map<String,Object> queryByModelsIdAndVersion(Integer modelsId, String version); | ||||
| Map<Integer, String> deleteModelsVersion(Integer modelsId, String version); | |||||
| Map<Integer, String> deleteModelsVersion(Integer modelsId, String version) throws IOException; | |||||
| String addModelVersions(List<ModelsVersion> modelsVersions) throws Exception; | String addModelVersions(List<ModelsVersion> modelsVersions) throws Exception; | ||||
| @@ -353,22 +353,15 @@ public class DatasetServiceImpl implements DatasetService { | |||||
| public void checkDeclaredName(Dataset insert) throws Exception { | public void checkDeclaredName(Dataset insert) throws Exception { | ||||
| Dataset existingDataset = datasetDao.findByName(insert.getName()); | Dataset existingDataset = datasetDao.findByName(insert.getName()); | ||||
| if (existingDataset != null) { | if (existingDataset != null) { | ||||
| // Check if the found dataset is not the same as the one being inserted | |||||
| // This is important if you are using this method for both insert and update operations | |||||
| // You may need an identifier check here, e.g., if 'insert' has an ID and it's the same as 'existingDataset' | |||||
| if (insert.getId() != null && insert.getId().equals(existingDataset.getId())) { | if (insert.getId() != null && insert.getId().equals(existingDataset.getId())) { | ||||
| // This is the same dataset, no duplicate name issue for update operation | |||||
| // 相同数据集,无需判断 | |||||
| return; | return; | ||||
| } | } | ||||
| // Now we know there's another dataset with the same name | |||||
| Field[] fields = Dataset.class.getDeclaredFields(); | Field[] fields = Dataset.class.getDeclaredFields(); | ||||
| for (Field field : fields) { | for (Field field : fields) { | ||||
| field.setAccessible(true); // Make private fields accessible | |||||
| field.setAccessible(true); | |||||
| if ("name".equals(field.getName()) && field.isAnnotationPresent(CheckDuplicate.class)) { | if ("name".equals(field.getName()) && field.isAnnotationPresent(CheckDuplicate.class)) { | ||||
| // If the field is 'name' and is marked with CheckDuplicate annotation | |||||
| CheckDuplicate annotation = field.getAnnotation(CheckDuplicate.class); | CheckDuplicate annotation = field.getAnnotation(CheckDuplicate.class); | ||||
| throw new Exception("重复的数据集名称: " + insert.getName() + ". " + annotation.message()); | throw new Exception("重复的数据集名称: " + insert.getName() + ". " + annotation.message()); | ||||
| } | } | ||||
| @@ -0,0 +1,125 @@ | |||||
| package com.ruoyi.platform.service.impl; | |||||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||||
| import com.ruoyi.platform.domain.DevEnvironment; | |||||
| import com.ruoyi.platform.mapper.DevEnvironmentDao; | |||||
| import com.ruoyi.platform.service.DevEnvironmentService; | |||||
| import com.ruoyi.platform.service.JupyterService; | |||||
| import com.ruoyi.platform.utils.JacksonUtil; | |||||
| import com.ruoyi.system.api.model.LoginUser; | |||||
| import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.springframework.stereotype.Service; | |||||
| import org.springframework.data.domain.Page; | |||||
| import org.springframework.data.domain.PageImpl; | |||||
| import org.springframework.data.domain.PageRequest; | |||||
| import javax.annotation.Resource; | |||||
| import java.util.Date; | |||||
| import java.util.Map; | |||||
| /** | |||||
| * (DevEnvironment)表服务实现类 | |||||
| * | |||||
| * @author Xidaray | |||||
| * @since 2024-06-03 15:17:37 | |||||
| */ | |||||
| @Service("devEnvironmentService") | |||||
| public class DevEnvironmentServiceImpl implements DevEnvironmentService { | |||||
| @Resource | |||||
| private DevEnvironmentDao devEnvironmentDao; | |||||
| @Resource | |||||
| private JupyterService jupyterService; | |||||
| /** | |||||
| * 通过ID查询单条数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| @Override | |||||
| public DevEnvironment queryById(Integer id) { | |||||
| return this.devEnvironmentDao.queryById(id); | |||||
| } | |||||
| /** | |||||
| * 分页查询 | |||||
| * | |||||
| * @param devEnvironment 筛选条件 | |||||
| * @param pageRequest 分页对象 | |||||
| * @return 查询结果 | |||||
| */ | |||||
| @Override | |||||
| public Page<DevEnvironment> queryByPage(DevEnvironment devEnvironment, PageRequest pageRequest) { | |||||
| long total = this.devEnvironmentDao.count(devEnvironment); | |||||
| return new PageImpl<>(this.devEnvironmentDao.queryAllByLimit(devEnvironment, pageRequest), pageRequest, total); | |||||
| } | |||||
| /** | |||||
| * 新增数据 | |||||
| * | |||||
| * @param devEnvironment 实例对象 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| @Override | |||||
| public DevEnvironment insert(DevEnvironment devEnvironment) { | |||||
| //插入预备,此时不需要判断版本重复 | |||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||||
| devEnvironment.setCreateBy(loginUser.getUsername()); | |||||
| devEnvironment.setUpdateBy(loginUser.getUsername()); | |||||
| devEnvironment.setUpdateTime(new Date()); | |||||
| devEnvironment.setCreateTime(new Date()); | |||||
| this.devEnvironmentDao.insert(devEnvironment); | |||||
| return devEnvironment; | |||||
| } | |||||
| /** | |||||
| * 修改数据 | |||||
| * | |||||
| * @param devEnvironment 实例对象 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| @Override | |||||
| public DevEnvironment update(DevEnvironment devEnvironment) { | |||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||||
| devEnvironment.setUpdateBy(loginUser.getUsername()); | |||||
| devEnvironment.setUpdateTime(new Date()); | |||||
| this.devEnvironmentDao.update(devEnvironment); | |||||
| return this.queryById(devEnvironment.getId()); | |||||
| } | |||||
| /** | |||||
| * 通过主键删除数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 是否成功 | |||||
| */ | |||||
| @Override | |||||
| public boolean deleteById(Integer id) { | |||||
| return this.devEnvironmentDao.deleteById(id) > 0; | |||||
| } | |||||
| @Override | |||||
| public String removeById(Integer id) { | |||||
| DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); | |||||
| if (devEnvironment == null){ | |||||
| return "开发环境信息不存在"; | |||||
| } | |||||
| //判断权限,只有admin和创建者本身可以删除该数据集 | |||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||||
| String username = loginUser.getUsername(); | |||||
| String createdBy = devEnvironment.getCreateBy(); | |||||
| if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){ | |||||
| return "无权限删除该开发环境"; | |||||
| } | |||||
| devEnvironment.setState(0); | |||||
| return this.devEnvironmentDao.update(devEnvironment)>0?"删除成功":"删除失败"; | |||||
| } | |||||
| } | |||||
| @@ -427,7 +427,6 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { | |||||
| throw new RuntimeException("日志为空。"); | throw new RuntimeException("日志为空。"); | ||||
| } | } | ||||
| //返回日志内容 | //返回日志内容 | ||||
| return experimentInsLog; | return experimentInsLog; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| throw new RuntimeException("查询实验日志失败: " + e.getMessage(), e); | throw new RuntimeException("查询实验日志失败: " + e.getMessage(), e); | ||||
| @@ -554,7 +553,7 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { | |||||
| // 查询具有相同状态的实例数量 | // 查询具有相同状态的实例数量 | ||||
| Long count = experimentInsDao.count(experimentIns); | Long count = experimentInsDao.count(experimentIns); | ||||
| // 将状态及其对应的实例数量放入映射中 | |||||
| // 将状态及其对应的实例数量放入map中 | |||||
| statusCountMap.put(status.toString(), count); | statusCountMap.put(status.toString(), count); | ||||
| } | } | ||||
| @@ -580,8 +579,9 @@ public class ExperimentInsServiceImpl implements ExperimentInsService { | |||||
| flag = StringUtils.equals("Terminated", (String) workflowMap.get("phase")); | flag = StringUtils.equals("Terminated", (String) workflowMap.get("phase")); | ||||
| } | } | ||||
| } | } | ||||
| return flag; | return flag; | ||||
| } | } | ||||
| } | } | ||||
| @@ -2,14 +2,12 @@ package com.ruoyi.platform.service.impl; | |||||
| import com.ruoyi.common.security.utils.SecurityUtils; | import com.ruoyi.common.security.utils.SecurityUtils; | ||||
| import com.ruoyi.platform.annotations.CheckDuplicate; | import com.ruoyi.platform.annotations.CheckDuplicate; | ||||
| import com.ruoyi.platform.domain.Experiment; | |||||
| import com.ruoyi.platform.domain.ExperimentIns; | |||||
| import com.ruoyi.platform.domain.Workflow; | |||||
| import com.ruoyi.platform.domain.*; | |||||
| import com.ruoyi.platform.domain.dependencydomain.ProjectDepency; | |||||
| import com.ruoyi.platform.domain.dependencydomain.TrainTaskDepency; | |||||
| import com.ruoyi.platform.mapper.ExperimentDao; | import com.ruoyi.platform.mapper.ExperimentDao; | ||||
| import com.ruoyi.platform.mapper.ExperimentInsDao; | import com.ruoyi.platform.mapper.ExperimentInsDao; | ||||
| import com.ruoyi.platform.service.ExperimentInsService; | |||||
| import com.ruoyi.platform.service.ExperimentService; | |||||
| import com.ruoyi.platform.service.WorkflowService; | |||||
| import com.ruoyi.platform.service.*; | |||||
| import com.ruoyi.platform.utils.HttpUtils; | import com.ruoyi.platform.utils.HttpUtils; | ||||
| import com.ruoyi.platform.utils.JacksonUtil; | import com.ruoyi.platform.utils.JacksonUtil; | ||||
| import com.ruoyi.platform.utils.JsonUtils; | import com.ruoyi.platform.utils.JsonUtils; | ||||
| @@ -42,8 +40,12 @@ public class ExperimentServiceImpl implements ExperimentService { | |||||
| @Resource | @Resource | ||||
| private ExperimentInsDao experimentInsDao; | private ExperimentInsDao experimentInsDao; | ||||
| @Resource | |||||
| private ModelsService modelsService; | |||||
| @Resource | |||||
| private DatasetService datasetService; | |||||
| @Resource | |||||
| private ModelDependencyService modelDependencyService; | |||||
| @Resource | @Resource | ||||
| @Lazy | @Lazy | ||||
| @@ -151,8 +153,9 @@ public class ExperimentServiceImpl implements ExperimentService { | |||||
| * @return 实例对象 | * @return 实例对象 | ||||
| */ | */ | ||||
| @Override | @Override | ||||
| public Experiment update(Experiment experiment) throws IOException { | |||||
| public Experiment update(Experiment experiment) throws Exception { | |||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | LoginUser loginUser = SecurityUtils.getLoginUser(); | ||||
| checkDeclaredName(experiment); | |||||
| experiment.setUpdateBy(loginUser.getUsername()); | experiment.setUpdateBy(loginUser.getUsername()); | ||||
| experiment.setUpdateTime(new Date()); | experiment.setUpdateTime(new Date()); | ||||
| this.experimentDao.update(experiment); | this.experimentDao.update(experiment); | ||||
| @@ -207,12 +210,9 @@ public class ExperimentServiceImpl implements ExperimentService { | |||||
| public Experiment runExperiment(Integer id) throws Exception { | public Experiment runExperiment(Integer id) throws Exception { | ||||
| //先查出实验记录 | //先查出实验记录 | ||||
| Experiment experiment = this.queryById(id); | Experiment experiment = this.queryById(id); | ||||
| if (experiment == null) { | if (experiment == null) { | ||||
| System.out.println("No experiment"); | System.out.println("No experiment"); | ||||
| } | } | ||||
| Workflow workflow = workflowService.queryById(experiment.getWorkflowId()); | Workflow workflow = workflowService.queryById(experiment.getWorkflowId()); | ||||
| if(workflow == null) { | if(workflow == null) { | ||||
| throw new RuntimeException("流水线不存在,请先创建流水线"); | throw new RuntimeException("流水线不存在,请先创建流水线"); | ||||
| @@ -226,13 +226,14 @@ public class ExperimentServiceImpl implements ExperimentService { | |||||
| throw new RuntimeException("转换流水线失败"); | throw new RuntimeException("转换流水线失败"); | ||||
| } | } | ||||
| Map<String, Object> converMap = JsonUtils.jsonToMap(convertRes); | Map<String, Object> converMap = JsonUtils.jsonToMap(convertRes); | ||||
| // 组装运行接口json | // 组装运行接口json | ||||
| Map<String, Object> runReqMap = new HashMap<>(); | Map<String, Object> runReqMap = new HashMap<>(); | ||||
| runReqMap.put("data", converMap.get("data")); | runReqMap.put("data", converMap.get("data")); | ||||
| //这里全局参数是一个json数组,需要转换成一个list<Map> | //这里全局参数是一个json数组,需要转换成一个list<Map> | ||||
| List<Map<String, Object>> params = JacksonUtil.parseJSONStr2MapList(StringUtils.isEmpty(experiment.getGlobalParam()) ? "[]" : experiment.getGlobalParam()); | List<Map<String, Object>> params = JacksonUtil.parseJSONStr2MapList(StringUtils.isEmpty(experiment.getGlobalParam()) ? "[]" : experiment.getGlobalParam()); | ||||
| runReqMap.put("params", params); | runReqMap.put("params", params); | ||||
| //// 实验字段的Map,不要写成一行!否则会返回null | |||||
| // 实验字段的Map,不要写成一行!否则会返回null | |||||
| Map<String, Object> experimentMap = new HashMap<>(); | Map<String, Object> experimentMap = new HashMap<>(); | ||||
| experimentMap.put("name", "experiment-"+experiment.getId()); | experimentMap.put("name", "experiment-"+experiment.getId()); | ||||
| runReqMap.put("experiment", experimentMap); | runReqMap.put("experiment", experimentMap); | ||||
| @@ -246,14 +247,11 @@ public class ExperimentServiceImpl implements ExperimentService { | |||||
| } | } | ||||
| Map<String, Object> runResMap = JsonUtils.jsonToMap(runRes); | Map<String, Object> runResMap = JsonUtils.jsonToMap(runRes); | ||||
| Map<String, Object> data = (Map<String, Object>) runResMap.get("data"); | Map<String, Object> data = (Map<String, Object>) runResMap.get("data"); | ||||
| //判断data为空 | //判断data为空 | ||||
| if (data == null || MapUtils.isEmpty(data)) { | if (data == null || MapUtils.isEmpty(data)) { | ||||
| throw new RuntimeException("Failed to run workflow."); | throw new RuntimeException("Failed to run workflow."); | ||||
| } | } | ||||
| Map<String, Object> metadata = (Map<String, Object>) data.get("metadata"); | Map<String, Object> metadata = (Map<String, Object>) data.get("metadata"); | ||||
| // 插入记录到实验实例表 | // 插入记录到实验实例表 | ||||
| ExperimentIns experimentIns = new ExperimentIns(); | ExperimentIns experimentIns = new ExperimentIns(); | ||||
| experimentIns.setExperimentId(experiment.getId()); | experimentIns.setExperimentId(experiment.getId()); | ||||
| @@ -262,24 +260,237 @@ public class ExperimentServiceImpl implements ExperimentService { | |||||
| experimentIns.setStatus("Pending"); | experimentIns.setStatus("Pending"); | ||||
| //传入实验全局参数 | //传入实验全局参数 | ||||
| experimentIns.setGlobalParam(experiment.getGlobalParam()); | experimentIns.setGlobalParam(experiment.getGlobalParam()); | ||||
| //替换argoInsName | //替换argoInsName | ||||
| String outputString = JsonUtils.mapToJson(output); | String outputString = JsonUtils.mapToJson(output); | ||||
| experimentIns.setNodesResult(outputString.replace("{{workflow.name}}", (String) metadata.get("name"))); | experimentIns.setNodesResult(outputString.replace("{{workflow.name}}", (String) metadata.get("name"))); | ||||
| //插入ExperimentIns表中 | //插入ExperimentIns表中 | ||||
| experimentInsService.insert(experimentIns); | |||||
| ExperimentIns insert = experimentInsService.insert(experimentIns); | |||||
| //插入到模型依赖关系表 | |||||
| //得到dependendcy | |||||
| Map<String, Object> converMap2 = JsonUtils.jsonToMap(JacksonUtil.replaceInAarry(convertRes, params)); | |||||
| Map<String ,Object> dependendcy = (Map<String, Object>)converMap2.get("model_dependency"); | |||||
| Map<String ,Object> trainInfo = (Map<String, Object>)converMap2.get("component_info"); | |||||
| insertModelDependency(dependendcy,trainInfo,insert.getId(),experiment.getName()); | |||||
| }catch (Exception e){ | }catch (Exception e){ | ||||
| throw new RuntimeException(e); | throw new RuntimeException(e); | ||||
| } | } | ||||
| List<ExperimentIns> updatedExperimentInsList = experimentInsService.getByExperimentId(id); | List<ExperimentIns> updatedExperimentInsList = experimentInsService.getByExperimentId(id); | ||||
| experiment.setExperimentInsList(updatedExperimentInsList); | experiment.setExperimentInsList(updatedExperimentInsList); | ||||
| return experiment; | return experiment; | ||||
| } | } | ||||
| private void insertModelDependency(Map<String ,Object> dependendcy,Map<String ,Object> trainInfo, Integer experimentInsId, String experimentName) throws Exception { | |||||
| Iterator<Map.Entry<String, Object>> dependendcyIterator = dependendcy.entrySet().iterator(); | |||||
| Map<String, Object> modelTrain = (Map<String, Object>) trainInfo.get("model_train"); | |||||
| Map<String, Object> modelEvaluate = (Map<String, Object>) trainInfo.get("model_evaluate"); | |||||
| Map<String, Object> modelExport = (Map<String, Object>) trainInfo.get("model_export"); | |||||
| while (dependendcyIterator.hasNext()) { | |||||
| ModelDependency modelDependency = new ModelDependency(); | |||||
| Map.Entry<String, Object> entry = dependendcyIterator.next(); | |||||
| Map<String, Object> modelDel = (Map<String, Object>) entry.getValue(); | |||||
| Map<String, Object> source = (Map<String, Object>) modelDel.get("source"); | |||||
| List<Map<String, Object>> test = (List<Map<String, Object>>) modelDel.get("test"); | |||||
| List<Map<String, Object>> target = (List<Map<String, Object>>) modelDel.get("target"); | |||||
| String sourceTaskId = (String) source.get("task_id"); | |||||
| Map<String, Object> modelTrainMap = (Map<String, Object>)modelTrain.get(sourceTaskId); | |||||
| //处理project数据 | |||||
| Map<String, String> projectMap = (Map<String, String>) modelTrainMap.get("project"); | |||||
| ProjectDepency projectDepency = new ProjectDepency(); | |||||
| projectDepency.setBranch(projectMap.get("branch")); | |||||
| String projectUrl = projectMap.get("url"); | |||||
| projectDepency.setUrl(projectUrl); | |||||
| projectDepency.setName(projectUrl.substring(projectUrl.lastIndexOf('/') + 1, projectUrl.length() - 4)); | |||||
| //依赖项目 | |||||
| modelDependency.setProjectDependency(JsonUtils.objectToJson(projectDepency)); | |||||
| //处理镜像 | |||||
| Map<String, String> imagesMap = (Map<String, String>) modelTrainMap.get("image"); | |||||
| modelDependency.setTrainImage(imagesMap.get("name")); | |||||
| List<Map<String, Object>> trainParamList = (List<Map<String, Object>>) modelTrainMap.get("params"); | |||||
| modelDependency.setTrainParams(JsonUtils.objectToJson(trainParamList)); | |||||
| //处理source数据 | |||||
| List<Map<String, Object>> modelsList = (List<Map<String, Object>>) modelTrainMap.get("models"); | |||||
| if (modelsList != null) { | |||||
| for (int i = 0; i < modelsList.size(); i++) { | |||||
| Map<String, Object> model = modelsList.get(i); | |||||
| Models models = modelsService.queryById((Integer) model.get("model_id")); | |||||
| if (models == null) { | |||||
| throw new Exception("源模型不存在"); | |||||
| } | |||||
| model.put("model_name", models.getName()); | |||||
| } | |||||
| //父模型 | |||||
| modelDependency.setParentModels(JsonUtils.objectToJson(modelsList)); | |||||
| } | |||||
| List<Map<String, Object>> datasetsList = (List<Map<String, Object>>) modelTrainMap.get("datasets"); | |||||
| if (datasetsList != null) { | |||||
| for (int i = 0; i < datasetsList.size(); i++) { | |||||
| Map<String, Object> datasets = datasetsList.get(i); | |||||
| Dataset dataset = datasetService.queryById((Integer) datasets.get("dataset_id")); | |||||
| if (dataset == null) { | |||||
| throw new Exception("源数据集不存在"); | |||||
| } | |||||
| datasets.put("dataset_name", dataset.getName()); | |||||
| } | |||||
| } | |||||
| //训练数据集 | |||||
| modelDependency.setTrainDataset(JsonUtils.objectToJson(datasetsList)); | |||||
| TrainTaskDepency trainTaskDepency = new TrainTaskDepency(); | |||||
| trainTaskDepency.setTaskId(sourceTaskId); | |||||
| trainTaskDepency.setInsId(experimentInsId); | |||||
| trainTaskDepency.setName(experimentName); | |||||
| //训练任务 | |||||
| modelDependency.setTrainTask(JsonUtils.objectToJson(trainTaskDepency)); | |||||
| modelDependency.setExpInsId(experimentInsId); | |||||
| List<Map<String, Object>> resultTestDatasets = new ArrayList<Map<String, Object>>(); | |||||
| //处理test数据 | |||||
| if (test != null) { | |||||
| for(int i=0;i<test.size();i++){ | |||||
| Map<String, Object> testMap = test.get(i); | |||||
| String testTaskId = (String) testMap.get("task_id"); | |||||
| Map<String, Object> evaluateMap = (Map<String, Object>) modelEvaluate.get(testTaskId); | |||||
| List<Map<String, Object>> realDataSetList = (List<Map<String, Object>>) evaluateMap.get("datasets"); | |||||
| for(int j=0;j<realDataSetList.size();j++){ | |||||
| Map<String, Object> realDataSet = realDataSetList.get(j); | |||||
| Dataset dataset = datasetService.queryById((Integer) realDataSet.get("dataset_id")); | |||||
| if (dataset == null){ | |||||
| throw new Exception("源数据集不存在"); | |||||
| } | |||||
| realDataSet.put("dataset_name", dataset.getName()); | |||||
| resultTestDatasets.add(realDataSet); | |||||
| } | |||||
| } | |||||
| //测试数据集 | |||||
| modelDependency.setTestDataset(JsonUtils.objectToJson(resultTestDatasets)); | |||||
| } | |||||
| //处理target数据 | |||||
| if (target != null) { | |||||
| for (int i = 0; i < target.size(); i++) { | |||||
| Map<String, Object> targetMap = target.get(i); | |||||
| String targetaskId = (String) targetMap.get("task_id"); | |||||
| Map<String, Object> exportMap = (Map<String, Object>) modelExport.get(targetaskId); | |||||
| List<Map<String, Object>> modelTargetList = (List<Map<String, Object>>) exportMap.get("models"); | |||||
| modelDependency.setState(2); | |||||
| for (int j = 0; j < modelTargetList.size(); j++) { | |||||
| Map<String, Object> model = modelTargetList.get(i); | |||||
| modelDependency.setVersion((String) model.get("model_version")); | |||||
| modelDependency.setCurrentModelId((Integer) model.get("model_id")); | |||||
| //因为可能有多成果模型,多次插入 | |||||
| modelDependencyService.insert(modelDependency); | |||||
| } | |||||
| } | |||||
| }else { | |||||
| modelDependency.setState(2); | |||||
| modelDependencyService.insert(modelDependency); | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 被废弃的旧JSON | |||||
| * @param experiment | |||||
| * @return | |||||
| * @throws Exception | |||||
| */ | |||||
| // private void insertModelDependency(Map<String ,Object> dependendcy,Map<String ,Object> trainInfo, Integer experimentInsId, String experimentName, List<Map<String, Object>> params) throws Exception { | |||||
| // Iterator<Map.Entry<String, Object>> dependendcyIterator = dependendcy.entrySet().iterator(); | |||||
| // while (dependendcyIterator.hasNext()) { | |||||
| // ModelDependency modelDependency = new ModelDependency(); | |||||
| // Map.Entry<String, Object> entry = dependendcyIterator.next(); | |||||
| // String key = entry.getKey(); | |||||
| // Map<String, Object> modelDel = (Map<String, Object>) entry.getValue(); | |||||
| // //处理project数据 | |||||
| // Map<String, String> projectMap = (Map<String, String>) modelDel.get("project"); | |||||
| // ProjectDepency projectDepency = new ProjectDepency(); | |||||
| // projectDepency.setBranch(projectMap.get("branch")); | |||||
| // String projectUrl = projectMap.get("url"); | |||||
| // projectDepency.setUrl(projectUrl); | |||||
| // projectDepency.setName(projectUrl.substring(projectUrl.lastIndexOf('/') + 1, projectUrl.length() - 4)); | |||||
| // //依赖项目 | |||||
| // modelDependency.setProjectDependency(JsonUtils.objectToJson(projectDepency)); | |||||
| // //处理source数据 | |||||
| // Map<String, Object> sourceMap = (Map<String, Object>) modelDel.get("source"); | |||||
| // List<Map<String, Object>> modelsList = (List<Map<String, Object>>) sourceMap.get("models"); | |||||
| // for(int i=0;i<modelsList.size();i++){ | |||||
| // Map<String, Object> model = modelsList.get(i); | |||||
| // Models models = modelsService.queryById((Integer) model.get("model_id")); | |||||
| // if (models == null){ | |||||
| // throw new Exception("源模型不存在"); | |||||
| // } | |||||
| // model.put("model_name", models.getName()); | |||||
| // } | |||||
| // //父模型 | |||||
| // modelDependency.setParentModels(JsonUtils.objectToJson(modelsList)); | |||||
| // | |||||
| // List<Map<String, Object>> datasetsList = (List<Map<String, Object>>) sourceMap.get("datasets"); | |||||
| // for(int i=0;i<datasetsList.size();i++){ | |||||
| // Map<String, Object> datasets = datasetsList.get(i); | |||||
| // Dataset dataset = datasetService.queryById((Integer) datasets.get("dataset_id")); | |||||
| // if (dataset == null){ | |||||
| // throw new Exception("源数据集不存在"); | |||||
| // } | |||||
| // datasets.put("dataset_name", dataset.getName()); | |||||
| // } | |||||
| // //训练数据集 | |||||
| // modelDependency.setTrainDataset(JsonUtils.objectToJson(datasetsList)); | |||||
| // | |||||
| // TrainTaskDepency trainTaskDepency = new TrainTaskDepency(); | |||||
| // trainTaskDepency.setTaskId(key); | |||||
| // trainTaskDepency.setInsId(experimentInsId); | |||||
| // trainTaskDepency.setName(experimentName); | |||||
| // //训练任务 | |||||
| // modelDependency.setTrainTask(JsonUtils.objectToJson(trainTaskDepency)); | |||||
| // modelDependency.setExpInsId(experimentInsId); | |||||
| // //处理test数据 | |||||
| // List<Map<String, Object>> testDatasetsList = (List<Map<String, Object>>) modelDel.get("test"); | |||||
| // List<Map<String, Object>> resultTestDatasets = new ArrayList<Map<String, Object>>(); | |||||
| // for(int i=0;i<testDatasetsList.size();i++){ | |||||
| // Map<String, Object> datasets = testDatasetsList.get(i); | |||||
| // List<Map<String, Object>> realDataSetList = (List<Map<String, Object>>) datasets.get("datasets"); | |||||
| // for(int j=0;j<realDataSetList.size();j++){ | |||||
| // Map<String, Object> realDataSet = realDataSetList.get(j); | |||||
| // Dataset dataset = datasetService.queryById((Integer) realDataSet.get("dataset_id")); | |||||
| // if (dataset == null){ | |||||
| // throw new Exception("源数据集不存在"); | |||||
| // } | |||||
| // realDataSet.put("dataset_name", dataset.getName()); | |||||
| // resultTestDatasets.add(realDataSet); | |||||
| // } | |||||
| // | |||||
| // } | |||||
| // //测试数据集 | |||||
| // modelDependency.setTestDataset(JsonUtils.objectToJson(resultTestDatasets)); | |||||
| // | |||||
| // //检查是否存在target,如果存在说明在流水线用了节点导入,如果没有说明没有导入,等待手动push | |||||
| // List<Map<String, Object>> modelTargetList = (List<Map<String, Object>>) modelDel.get("target"); | |||||
| // if (modelTargetList==null||modelTargetList.size()==0){ | |||||
| // modelDependency.setState(1); | |||||
| // modelDependencyService.insert(modelDependency); | |||||
| // }else { | |||||
| // modelDependency.setState(2); | |||||
| // for(int i=0;i<modelTargetList.size();i++){ | |||||
| // Map<String, Object> model = modelTargetList.get(i); | |||||
| // String version = null; | |||||
| // //可能是参数,必须从实验参数读取 | |||||
| // if (params != null) { | |||||
| // for (Map<String, Object> param : params) { | |||||
| // if (param.containsKey("param_name") && StringUtils.equals("model_version",(String) param.get("param_name"))) { | |||||
| // version = param.get("param_value").toString(); | |||||
| // } | |||||
| // } | |||||
| // } | |||||
| // modelDependency.setVersion(StringUtils.isEmpty(version)?(String)model.get("model_version"):version); | |||||
| // modelDependency.setCurrentModelId((Integer) model.get("model_id")); | |||||
| // //因为可能有多成果模型,多次插入 | |||||
| // modelDependencyService.insert(modelDependency); | |||||
| // } | |||||
| // } | |||||
| // } | |||||
| // } | |||||
| @Override | @Override | ||||
| public Experiment addAndRunExperiment(Experiment experiment) throws Exception { | public Experiment addAndRunExperiment(Experiment experiment) throws Exception { | ||||
| @@ -332,7 +543,7 @@ public class ExperimentServiceImpl implements ExperimentService { | |||||
| // 现在我们知道还有另一个具有相同名称的流水线 | // 现在我们知道还有另一个具有相同名称的流水线 | ||||
| Field[] fields = Experiment.class.getDeclaredFields(); | Field[] fields = Experiment.class.getDeclaredFields(); | ||||
| for (Field field : fields) { | for (Field field : fields) { | ||||
| field.setAccessible(true); // 使私有字段可访问 | |||||
| field.setAccessible(true); | |||||
| if ("name".equals(field.getName()) && field.isAnnotationPresent(CheckDuplicate.class)) { | if ("name".equals(field.getName()) && field.isAnnotationPresent(CheckDuplicate.class)) { | ||||
| // 如果字段是“name”并且标记了CheckDuplicate注解 | // 如果字段是“name”并且标记了CheckDuplicate注解 | ||||
| CheckDuplicate annotation = field.getAnnotation(CheckDuplicate.class); | CheckDuplicate annotation = field.getAnnotation(CheckDuplicate.class); | ||||
| @@ -1,7 +1,12 @@ | |||||
| package com.ruoyi.platform.service.impl; | package com.ruoyi.platform.service.impl; | ||||
| import com.ruoyi.common.redis.service.RedisService; | |||||
| import com.ruoyi.common.security.utils.SecurityUtils; | import com.ruoyi.common.security.utils.SecurityUtils; | ||||
| import com.ruoyi.platform.domain.DevEnvironment; | |||||
| import com.ruoyi.platform.mapper.DevEnvironmentDao; | |||||
| import com.ruoyi.platform.service.DevEnvironmentService; | |||||
| import com.ruoyi.platform.service.JupyterService; | import com.ruoyi.platform.service.JupyterService; | ||||
| import com.ruoyi.platform.utils.JacksonUtil; | |||||
| import com.ruoyi.platform.utils.K8sClientUtil; | import com.ruoyi.platform.utils.K8sClientUtil; | ||||
| import com.ruoyi.platform.utils.MinioUtil; | import com.ruoyi.platform.utils.MinioUtil; | ||||
| import com.ruoyi.platform.utils.MlflowUtil; | import com.ruoyi.platform.utils.MlflowUtil; | ||||
| @@ -13,6 +18,7 @@ import org.springframework.stereotype.Service; | |||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||
| import java.io.InputStream; | import java.io.InputStream; | ||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.Map; | |||||
| @Service | @Service | ||||
| public class JupyterServiceImpl implements JupyterService { | public class JupyterServiceImpl implements JupyterService { | ||||
| @@ -39,6 +45,15 @@ public class JupyterServiceImpl implements JupyterService { | |||||
| @Resource | @Resource | ||||
| private MlflowUtil mlflowUtil; | private MlflowUtil mlflowUtil; | ||||
| @Resource | |||||
| private DevEnvironmentDao devEnvironmentDao; | |||||
| @Resource | |||||
| private DevEnvironmentService devEnvironmentService; | |||||
| @Resource | |||||
| private RedisService redisService; | |||||
| public JupyterServiceImpl(MinioUtil minioUtil) { | public JupyterServiceImpl(MinioUtil minioUtil) { | ||||
| this.minioUtil = minioUtil; | this.minioUtil = minioUtil; | ||||
| } | } | ||||
| @@ -53,6 +68,54 @@ public class JupyterServiceImpl implements JupyterService { | |||||
| return masterIp + ":" + podPort; | return masterIp + ":" + podPort; | ||||
| } | } | ||||
| @Override | |||||
| public String runJupyterService(Integer id) { | |||||
| DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); | |||||
| String envName = devEnvironment.getName(); | |||||
| //TODO 设置环境变量 | |||||
| // 提取数据集,模型信息,得到数据集模型的path | |||||
| Map<String, Object> dataset = JacksonUtil.parseJSONStr2Map(devEnvironment.getDataset()); | |||||
| String datasetPath = (String) dataset.get("path"); | |||||
| Map<String, Object> model = JacksonUtil.parseJSONStr2Map(devEnvironment.getModel()); | |||||
| String modelPath = (String) model.get("path"); | |||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||||
| String podName = loginUser.getUsername().toLowerCase() + "-editor-pod"; | |||||
| String pvcName = loginUser.getUsername().toLowerCase() + "-editor-pvc"; | |||||
| V1PersistentVolumeClaim pvc = k8sClientUtil.createPvc(namespace, pvcName, storage, storageClassName); | |||||
| //TODO 设置镜像可配置,这里先用默认镜像启动pod | |||||
| // 调用修改后的 createPod 方法,传入额外的参数 | |||||
| Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, pvc, image, datasetPath, modelPath); | |||||
| return masterIp + ":" + podPort; | |||||
| } | |||||
| @Override | |||||
| public String stopJupyterService(Integer id) throws Exception { | |||||
| DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); | |||||
| if (devEnvironment==null){ | |||||
| throw new Exception("开发环境配置不存在"); | |||||
| } | |||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||||
| String podName = loginUser.getUsername().toLowerCase() + "-editor-pod"; | |||||
| // 使用 Kubernetes API 删除 Pod | |||||
| String deleteResult = k8sClientUtil.deletePod(podName, namespace); | |||||
| // 检查 Pod 是否存在 | |||||
| boolean exists = k8sClientUtil.checkPodExists(podName, namespace); | |||||
| if (exists) { | |||||
| throw new Exception("Pod " + podName + " 删除失败"); | |||||
| } | |||||
| return deleteResult + ",编辑器已停止"; | |||||
| } | |||||
| @Override | @Override | ||||
| public void upload(InputStream inputStream) { | public void upload(InputStream inputStream) { | ||||
| try { | try { | ||||
| @@ -71,4 +134,6 @@ public class JupyterServiceImpl implements JupyterService { | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,252 @@ | |||||
| package com.ruoyi.platform.service.impl; | |||||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||||
| import com.ruoyi.platform.domain.*; | |||||
| import com.ruoyi.platform.mapper.ModelDependencyDao; | |||||
| import com.ruoyi.platform.service.*; | |||||
| import com.ruoyi.platform.utils.JacksonUtil; | |||||
| import com.ruoyi.platform.vo.ModelDependcyTreeVo; | |||||
| import com.ruoyi.platform.vo.ModelVersionDependcyVo; | |||||
| import com.ruoyi.system.api.model.LoginUser; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.springframework.context.annotation.Lazy; | |||||
| import org.springframework.stereotype.Service; | |||||
| import org.springframework.data.domain.Page; | |||||
| import org.springframework.data.domain.PageImpl; | |||||
| import org.springframework.data.domain.PageRequest; | |||||
| import javax.annotation.Resource; | |||||
| import java.io.IOException; | |||||
| import java.util.ArrayList; | |||||
| import java.util.Date; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| import java.util.stream.Collectors; | |||||
| /** | |||||
| * (ModelDependency)表服务实现类 | |||||
| * | |||||
| * @author Xidaray | |||||
| * @since 2024-05-29 13:51:23 | |||||
| */ | |||||
| @Service("modelDependencyService") | |||||
| public class ModelDependencyServiceImpl implements ModelDependencyService { | |||||
| @Resource | |||||
| private ModelDependencyDao modelDependencyDao; | |||||
| @Resource | |||||
| private ModelsService modelsService; | |||||
| @Resource | |||||
| private ModelsVersionService modelsVersionService; | |||||
| @Lazy | |||||
| @Resource | |||||
| private ExperimentService experimentService; | |||||
| @Lazy | |||||
| @Resource | |||||
| private ExperimentInsService experimentInsService; | |||||
| /** | |||||
| * 通过ID查询单条数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| @Override | |||||
| public ModelDependency queryById(Integer id) { | |||||
| return this.modelDependencyDao.queryById(id); | |||||
| } | |||||
| /** | |||||
| * 分页查询 | |||||
| * | |||||
| * @param modelDependency 筛选条件 | |||||
| * @param pageRequest 分页对象 | |||||
| * @return 查询结果 | |||||
| */ | |||||
| @Override | |||||
| public Page<ModelDependency> queryByPage(ModelDependency modelDependency, PageRequest pageRequest) { | |||||
| long total = this.modelDependencyDao.count(modelDependency); | |||||
| return new PageImpl<>(this.modelDependencyDao.queryAllByLimit(modelDependency, pageRequest), pageRequest, total); | |||||
| } | |||||
| /** | |||||
| * 根据对象查询 | |||||
| * | |||||
| * @param modelDependency 筛选条件 | |||||
| * @return 查询结果 | |||||
| */ | |||||
| @Override | |||||
| public List<ModelDependency> queryByModelDependency(ModelDependency modelDependency) throws IOException { | |||||
| List<ModelDependency> modelDependencyList = this.modelDependencyDao.queryByModelDependency(modelDependency); | |||||
| return modelDependencyList; | |||||
| } | |||||
| @Override | |||||
| public ModelDependcyTreeVo getModelDependencyTree(ModelDependency modelDependencyQuery) throws Exception { | |||||
| //查询当前模型 | |||||
| modelDependencyQuery.setState(1); | |||||
| List<ModelDependency> modelDependencyList = this.queryByModelDependency(modelDependencyQuery); | |||||
| if (modelDependencyList==null || modelDependencyList.size()==0){ | |||||
| throw new Exception("当前模型依赖关系不存在"); | |||||
| } | |||||
| ModelDependency modelDependency = modelDependencyList.get(0); | |||||
| ModelDependcyTreeVo modelDependcyTreeVo = ModelDependencyConvertToTree(modelDependency); | |||||
| //递归父模型 | |||||
| processParentModel(modelDependcyTreeVo); | |||||
| //递归子模型 | |||||
| processChildrenModel(modelDependcyTreeVo); | |||||
| return modelDependcyTreeVo; | |||||
| } | |||||
| /** | |||||
| * 递归父模型 | |||||
| * @param modelDependcyTreeVo | |||||
| */ | |||||
| private void processParentModel(ModelDependcyTreeVo modelDependcyTreeVo) throws IOException { | |||||
| if (modelDependcyTreeVo.getParentModelsMap() != null) { | |||||
| List<Map<String, Object>> parentMaps = modelDependcyTreeVo.getParentModelsMap(); | |||||
| List<ModelDependcyTreeVo> ps = new ArrayList<ModelDependcyTreeVo>(); | |||||
| for (Map<String, Object> parent:parentMaps) { | |||||
| Integer model_id = (Integer) parent.get("model_id"); | |||||
| String version = (String) parent.get("model_version"); | |||||
| ModelDependency modelDependencyQuery = new ModelDependency(); | |||||
| modelDependencyQuery.setVersion(version); | |||||
| modelDependencyQuery.setCurrentModelId(model_id); | |||||
| List<ModelDependency> modelDependencyList = this.queryByModelDependency(modelDependencyQuery); | |||||
| if (modelDependencyList!=null&&modelDependencyList.size()>=0){ | |||||
| for (ModelDependency modelDependency:modelDependencyList){ | |||||
| ModelDependcyTreeVo modelDependencyTreeVoIn = ModelDependencyConvertToTree(modelDependency); | |||||
| processParentModel(modelDependencyTreeVoIn); | |||||
| ps.add(modelDependencyTreeVoIn); | |||||
| } | |||||
| } | |||||
| } | |||||
| modelDependcyTreeVo.setParentModels(ps); | |||||
| } | |||||
| } | |||||
| private void processChildrenModel(ModelDependcyTreeVo modelDependcyTreeVo) throws IOException { | |||||
| String version = modelDependcyTreeVo.getVersion(); | |||||
| Integer modelId = modelDependcyTreeVo.getCurrentModelId(); | |||||
| List<ModelDependcyTreeVo> cs = new ArrayList<ModelDependcyTreeVo>(); | |||||
| //查儿子们 | |||||
| List<ModelDependency> modelDependencyList = modelDependencyDao.queryChildrenByVersionId("\"model_id\":"+modelId, "\"model_version\":\""+version+"\""); | |||||
| if (modelDependencyList!=null&&modelDependencyList.size()>=0){ | |||||
| for (ModelDependency modelDependency:modelDependencyList){ | |||||
| ModelDependcyTreeVo modelDependencyTreeVoIn = ModelDependencyConvertToTree(modelDependency); | |||||
| processChildrenModel(modelDependencyTreeVoIn); | |||||
| cs.add(modelDependencyTreeVoIn); | |||||
| } | |||||
| } | |||||
| modelDependcyTreeVo.setChildrenModels(cs); | |||||
| } | |||||
| private ModelDependcyTreeVo ModelDependencyConvertToTree(ModelDependency modelDependency) throws IOException { | |||||
| ModelDependcyTreeVo modelDependcyTreeVo = new ModelDependcyTreeVo(); | |||||
| modelDependcyTreeVo.setCurrentModelId(modelDependency.getCurrentModelId()); | |||||
| modelDependcyTreeVo.setExpInsId(modelDependency.getExpInsId()); | |||||
| modelDependcyTreeVo.setVersion(modelDependency.getVersion()); | |||||
| modelDependcyTreeVo.setRefItem(modelDependency.getRefItem()); | |||||
| modelDependcyTreeVo.setTrainTask(JacksonUtil.parseJSONStr2Map(modelDependency.getTrainTask())); | |||||
| modelDependcyTreeVo.setTrainDataset(JacksonUtil.parseJSONStr2MapList(modelDependency.getTrainDataset())); | |||||
| modelDependcyTreeVo.setTrainImage(modelDependency.getTrainImage()); | |||||
| modelDependcyTreeVo.setTrainParams(JacksonUtil.parseJSONStr2TList(modelDependency.getTrainParams(),Object.class,null)); | |||||
| modelDependcyTreeVo.setTestDataset(JacksonUtil.parseJSONStr2MapList(modelDependency.getTestDataset())); | |||||
| modelDependcyTreeVo.setProjectDependency(JacksonUtil.parseJSONStr2Map(modelDependency.getProjectDependency())); | |||||
| modelDependcyTreeVo.setParentModelsMap(JacksonUtil.parseJSONStr2MapList(modelDependency.getParentModels())); | |||||
| /** | |||||
| * 补充workFlow_id + 是否共有 | |||||
| */ | |||||
| Integer currentModelId = modelDependency.getCurrentModelId(); | |||||
| Integer expInsId = modelDependency.getExpInsId(); | |||||
| Models models = modelsService.queryById(currentModelId); | |||||
| ModelsVersion modelsVersionquery = new ModelsVersion(); | |||||
| modelsVersionquery.setModelsId(currentModelId); | |||||
| modelsVersionquery.setVersion(modelDependency.getVersion()); | |||||
| ModelsVersion modelsVersion = modelsVersionService.queryByModelsVersion(modelsVersionquery); | |||||
| ExperimentIns experimentIns = experimentInsService.queryById(expInsId); | |||||
| Experiment experiment = experimentService.queryById(experimentIns.getExperimentId()); | |||||
| ModelVersionDependcyVo modelVersionDependcyVo = new ModelVersionDependcyVo(); | |||||
| modelVersionDependcyVo.setName(models.getName()); | |||||
| modelVersionDependcyVo.setAvailableRange(models.getAvailableRange()); | |||||
| modelVersionDependcyVo.setDescription(models.getDescription()); | |||||
| modelVersionDependcyVo.setModelTag(models.getModelTag()); | |||||
| modelVersionDependcyVo.setModelType(models.getModelType()); | |||||
| modelVersionDependcyVo.setModelTagName(models.getModelTagName()); | |||||
| modelVersionDependcyVo.setModelTypeName(models.getModelTypeName()); | |||||
| modelVersionDependcyVo.setFileName(modelsVersion.getFileName()); | |||||
| modelVersionDependcyVo.setFileSize(modelsVersion.getFileSize()); | |||||
| modelVersionDependcyVo.setUrl(modelsVersion.getUrl()); | |||||
| modelDependcyTreeVo.setWorkflowId(experiment.getWorkflowId()); | |||||
| modelDependcyTreeVo.setModelVersionDependcyVo(modelVersionDependcyVo); | |||||
| return modelDependcyTreeVo; | |||||
| } | |||||
| /** | |||||
| * 新增数据 | |||||
| * | |||||
| * @param modelDependency 实例对象 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| @Override | |||||
| public ModelDependency insert(ModelDependency modelDependency) { | |||||
| //插入预备,此时不需要判断版本重复 | |||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||||
| modelDependency.setCreateBy(loginUser.getUsername()); | |||||
| modelDependency.setUpdateBy(loginUser.getUsername()); | |||||
| modelDependency.setUpdateTime(new Date()); | |||||
| modelDependency.setCreateTime(new Date()); | |||||
| this.modelDependencyDao.insert(modelDependency); | |||||
| return modelDependency; | |||||
| } | |||||
| /** | |||||
| * 修改数据 | |||||
| * | |||||
| * @param modelDependency 实例对象 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| @Override | |||||
| public ModelDependency update(ModelDependency modelDependency) { | |||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||||
| modelDependency.setUpdateBy(loginUser.getUsername()); | |||||
| modelDependency.setUpdateTime(new Date()); | |||||
| this.modelDependencyDao.update(modelDependency); | |||||
| return this.queryById(modelDependency.getId()); | |||||
| } | |||||
| /** | |||||
| * 通过主键删除数据 | |||||
| * | |||||
| * @param id 主键 | |||||
| * @return 是否成功 | |||||
| */ | |||||
| @Override | |||||
| public boolean deleteById(Integer id) { | |||||
| return this.modelDependencyDao.deleteById(id) > 0; | |||||
| } | |||||
| @Override | |||||
| public String removeById(Integer id) { | |||||
| ModelDependency modelDependency = this.modelDependencyDao.queryById(id); | |||||
| if (modelDependency == null){ | |||||
| return "模型依赖信息不存在"; | |||||
| } | |||||
| //判断权限,只有admin和创建者本身可以删除该数据集 | |||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | |||||
| String username = loginUser.getUsername(); | |||||
| String createdBy = modelDependency.getCreateBy(); | |||||
| if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){ | |||||
| return "无权限删除"; | |||||
| } | |||||
| modelDependency.setState(0); | |||||
| return this.modelDependencyDao.update(modelDependency)>0?"删除成功":"删除失败"; | |||||
| } | |||||
| } | |||||
| @@ -192,6 +192,7 @@ public class ModelsServiceImpl implements ModelsService { | |||||
| * | * | ||||
| * @param id models_version表的主键 | * @param id models_version表的主键 | ||||
| * @return 文件内容 | * @return 文件内容 | ||||
| * | |||||
| */ | */ | ||||
| @Override | @Override | ||||