| @@ -107,7 +107,12 @@ export default [ | |||||
| { | { | ||||
| name: '开发环境', | name: '开发环境', | ||||
| path: '', | path: '', | ||||
| component: './DevelopmentEnvironment/index', | |||||
| component: './DevelopmentEnvironment/List', | |||||
| }, | |||||
| { | |||||
| name: '创建编辑器', | |||||
| path: 'create', | |||||
| component: './DevelopmentEnvironment/Create', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -43,7 +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; | |||||
| const placeholder = valueObj?.placeholder || rest?.placeholder; | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -20,6 +20,7 @@ export enum ModelDeploymentStatus { | |||||
| Pending = 'Pending', // 挂起中 | Pending = 'Pending', // 挂起中 | ||||
| } | } | ||||
| // 模型部署状态选项列表 | |||||
| export const modelDeploymentStatusOptions = [ | export const modelDeploymentStatusOptions = [ | ||||
| { label: '全部', value: '' }, | { label: '全部', value: '' }, | ||||
| { label: '启动中', value: ModelDeploymentStatus.Init }, | { label: '启动中', value: ModelDeploymentStatus.Init }, | ||||
| @@ -28,3 +29,12 @@ export const modelDeploymentStatusOptions = [ | |||||
| { label: '失败', value: ModelDeploymentStatus.Failed }, | { label: '失败', value: ModelDeploymentStatus.Failed }, | ||||
| { label: '挂起中', value: ModelDeploymentStatus.Pending }, | { label: '挂起中', value: ModelDeploymentStatus.Pending }, | ||||
| ]; | ]; | ||||
| // 开发环境编辑器状态 | |||||
| export enum DevEditorStatus { | |||||
| Pending = 'Pending', // 启动中 | |||||
| Running = 'Running', // 运行中 | |||||
| Terminated = 'Terminated', // 已终止 | |||||
| Failed = 'Failed', // 失败 | |||||
| Unknown = 'Unknown', // 未启动 | |||||
| } | |||||
| @@ -33,10 +33,11 @@ function AddVersionModal({ | |||||
| ...rest | ...rest | ||||
| }: AddVersionModalProps) { | }: AddVersionModalProps) { | ||||
| const [uuid] = useState(Date.now()); | const [uuid] = useState(Date.now()); | ||||
| const config = resourceConfig[resourceType]; | |||||
| // 上传组件参数 | // 上传组件参数 | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| action: resourceConfig[resourceType].uploadAction, | |||||
| action: config.uploadAction, | |||||
| headers: { | headers: { | ||||
| Authorization: getAccessToken() || '', | Authorization: getAccessToken() || '', | ||||
| }, | }, | ||||
| @@ -45,7 +46,7 @@ function AddVersionModal({ | |||||
| // 上传请求 | // 上传请求 | ||||
| const createDatasetVersion = async (params: any) => { | const createDatasetVersion = async (params: any) => { | ||||
| const request = resourceConfig[resourceType].addVersion; | |||||
| const request = config.addVersion; | |||||
| const [res] = await to(request(params)); | const [res] = await to(request(params)); | ||||
| if (res) { | if (res) { | ||||
| message.success('创建成功'); | message.success('创建成功'); | ||||
| @@ -62,7 +63,7 @@ function AddVersionModal({ | |||||
| const data = item.response?.data?.[0] ?? {}; | const data = item.response?.data?.[0] ?? {}; | ||||
| return { | return { | ||||
| ...otherParams, | ...otherParams, | ||||
| [resourceConfig[resourceType].idParamKey]: resourceId, | |||||
| [config.idParamKey]: resourceId, | |||||
| file_name: data.fileName, | file_name: data.fileName, | ||||
| file_size: data.fileSize, | file_size: data.fileSize, | ||||
| url: data.url, | url: data.url, | ||||
| @@ -72,8 +73,8 @@ function AddVersionModal({ | |||||
| } | } | ||||
| }; | }; | ||||
| const name = resourceConfig[resourceType].name; | |||||
| const accept = resourceConfig[resourceType].uploadAccept; | |||||
| const name = config.name; | |||||
| const accept = config.uploadAccept; | |||||
| return ( | return ( | ||||
| <KFModal | <KFModal | ||||
| {...rest} | {...rest} | ||||
| @@ -10,6 +10,7 @@ type CategoryItemProps = { | |||||
| }; | }; | ||||
| function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemProps) { | function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemProps) { | ||||
| const config = resourceConfig[resourceType]; | |||||
| return ( | return ( | ||||
| <div | <div | ||||
| className={classNames(styles['category-item'], { | className={classNames(styles['category-item'], { | ||||
| @@ -20,13 +21,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].prefix}/${item.path}.png`} | |||||
| src={`/assets/images/${config.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].prefix}/${item.path}-hover.png`} | |||||
| src={`/assets/images/${config.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> | ||||
| @@ -29,15 +29,14 @@ function CategoryList({ | |||||
| onTagSelect, | onTagSelect, | ||||
| onSearch, | onSearch, | ||||
| }: CategoryProps) { | }: CategoryProps) { | ||||
| const config = resourceConfig[resourceType]; | |||||
| return ( | return ( | ||||
| <div className={styles['category-list']}> | <div className={styles['category-list']}> | ||||
| <div style={{ padding: '0 20px' }}> | <div style={{ padding: '0 20px' }}> | ||||
| <Input.Search placeholder="搜索" allowClear onSearch={onSearch} /> | <Input.Search placeholder="搜索" allowClear onSearch={onSearch} /> | ||||
| </div> | </div> | ||||
| <div className={styles['category-list__content']}> | <div className={styles['category-list__content']}> | ||||
| <div className={styles['category-list__content__title']}> | |||||
| {resourceConfig[resourceType].typeTitle} | |||||
| </div> | |||||
| <div className={styles['category-list__content__title']}>{config.typeTitle}</div> | |||||
| <Flex wrap="wrap" gap="20px 12px"> | <Flex wrap="wrap" gap="20px 12px"> | ||||
| {typeList?.map((item) => ( | {typeList?.map((item) => ( | ||||
| <CategoryItem | <CategoryItem | ||||
| @@ -50,7 +49,7 @@ function CategoryList({ | |||||
| ))} | ))} | ||||
| </Flex> | </Flex> | ||||
| <div className={styles['category-list__content__title']} style={{ marginTop: '25px' }}> | <div className={styles['category-list__content__title']} style={{ marginTop: '25px' }}> | ||||
| {resourceConfig[resourceType].tagTitle} | |||||
| {config.tagTitle} | |||||
| </div> | </div> | ||||
| <Flex wrap="wrap" gap="20px 12px"> | <Flex wrap="wrap" gap="20px 12px"> | ||||
| {tagList?.map((item) => ( | {tagList?.map((item) => ( | ||||
| @@ -28,16 +28,17 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||||
| const [version, setVersion] = useState<string | undefined>(undefined); | const [version, setVersion] = useState<string | undefined>(undefined); | ||||
| const [activeTab, setActiveTab] = useState<string>(defaultTab); | const [activeTab, setActiveTab] = useState<string>(defaultTab); | ||||
| const resourceId = Number(locationParams.id); | const resourceId = Number(locationParams.id); | ||||
| const typeName = resourceConfig[resourceType].name; // 数据集/模型 | |||||
| const config = resourceConfig[resourceType]; | |||||
| const typeName = config.name; // 数据集/模型 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getModelByDetail(); | getModelByDetail(); | ||||
| getVersionList(); | getVersionList(); | ||||
| }, []); | |||||
| }, [resourceId]); | |||||
| // 获取详情 | // 获取详情 | ||||
| const getModelByDetail = async () => { | const getModelByDetail = async () => { | ||||
| const request = resourceConfig[resourceType].getInfo; | |||||
| const request = config.getInfo; | |||||
| const [res] = await to(request(resourceId)); | const [res] = await to(request(resourceId)); | ||||
| if (res) { | if (res) { | ||||
| setInfo(res.data); | setInfo(res.data); | ||||
| @@ -46,7 +47,7 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||||
| // 获取版本列表 | // 获取版本列表 | ||||
| const getVersionList = async () => { | const getVersionList = async () => { | ||||
| const request = resourceConfig[resourceType].getVersions; | |||||
| const request = config.getVersions; | |||||
| const [res] = await to(request(resourceId)); | const [res] = await to(request(resourceId)); | ||||
| if (res && res.data && res.data.length > 0) { | if (res && res.data && res.data.length > 0) { | ||||
| setVersionList( | setVersionList( | ||||
| @@ -122,10 +123,8 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||||
| }); | }); | ||||
| } | } | ||||
| const infoTypePropertyName = resourceConfig[resourceType] | |||||
| .infoTypePropertyName as keyof ResourceData; | |||||
| const infoTagPropertyName = resourceConfig[resourceType] | |||||
| .infoTagPropertyName as keyof ResourceData; | |||||
| const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData; | |||||
| const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData; | |||||
| return ( | return ( | ||||
| <div className={styles['resource-intro']}> | <div className={styles['resource-intro']}> | ||||
| @@ -54,6 +54,7 @@ function ResourceList( | |||||
| const [searchText, setSearchText] = useState(initialSearchText); | const [searchText, setSearchText] = useState(initialSearchText); | ||||
| const [inputText, setInputText] = useState(initialSearchText); | const [inputText, setInputText] = useState(initialSearchText); | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const config = resourceConfig[resourceType]; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getDataList(); | getDataList(); | ||||
| @@ -82,12 +83,12 @@ function ResourceList( | |||||
| const params = { | const params = { | ||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| [resourceConfig[resourceType].typeParamKey]: dataType, | |||||
| [resourceConfig[resourceType].tagParamKey]: dataTag, | |||||
| [config.typeParamKey]: dataType, | |||||
| [config.tagParamKey]: dataTag, | |||||
| available_range: isPublic ? 1 : 0, | available_range: isPublic ? 1 : 0, | ||||
| name: searchText !== '' ? searchText : undefined, | name: searchText !== '' ? searchText : undefined, | ||||
| }; | }; | ||||
| const request = resourceConfig[resourceType].getList; | |||||
| const request = config.getList; | |||||
| const [res] = await to(request(params)); | const [res] = await to(request(params)); | ||||
| if (res && res.data && res.data.content) { | if (res && res.data && res.data.content) { | ||||
| setDataList(res.data.content); | setDataList(res.data.content); | ||||
| @@ -97,7 +98,7 @@ function ResourceList( | |||||
| // 删除请求 | // 删除请求 | ||||
| const deleteRecord = async (id: number) => { | const deleteRecord = async (id: number) => { | ||||
| const request = resourceConfig[resourceType].deleteRecord; | |||||
| const request = config.deleteRecord; | |||||
| const [res] = await to(request(id)); | const [res] = await to(request(id)); | ||||
| if (res) { | if (res) { | ||||
| getDataList(); | getDataList(); | ||||
| @@ -113,7 +114,7 @@ function ResourceList( | |||||
| // 删除 | // 删除 | ||||
| const handleRemove = (record: ResourceData) => { | const handleRemove = (record: ResourceData) => { | ||||
| modalConfirm({ | modalConfirm({ | ||||
| title: resourceConfig[resourceType].deleteModalTitle, | |||||
| title: config.deleteModalTitle, | |||||
| onOk: () => { | onOk: () => { | ||||
| deleteRecord(record.id); | deleteRecord(record.id); | ||||
| }, | }, | ||||
| @@ -129,7 +130,7 @@ function ResourceList( | |||||
| activeType: dataType, | activeType: dataType, | ||||
| activeTag: dataTag, | activeTag: dataTag, | ||||
| }); | }); | ||||
| const prefix = resourceConfig[resourceType].prefix; | |||||
| const prefix = config.prefix; | |||||
| navigate(`/dataset/${prefix}/${record.id}`); | navigate(`/dataset/${prefix}/${record.id}`); | ||||
| }; | }; | ||||
| @@ -176,7 +177,7 @@ function ResourceList( | |||||
| onClick={showModal} | onClick={showModal} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| {resourceConfig[resourceType].addBtnTitle} | |||||
| {config.addBtnTitle} | |||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| @@ -21,6 +21,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) { | |||||
| const [activeType, setActiveType] = useState<number | undefined>(cacheState?.activeType); | const [activeType, setActiveType] = useState<number | undefined>(cacheState?.activeType); | ||||
| const [activeTag, setActiveTag] = useState<number | undefined>(cacheState?.activeTag); | const [activeTag, setActiveTag] = useState<number | undefined>(cacheState?.activeTag); | ||||
| const dataListRef = useRef<ResourceListRef>(null); | const dataListRef = useRef<ResourceListRef>(null); | ||||
| const config = resourceConfig[resourceType]; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getAssetIconList(); | getAssetIconList(); | ||||
| @@ -52,16 +53,10 @@ function ResourcePage({ resourceType }: ResourcePageProps) { | |||||
| if (res && res.data && res.data.content) { | if (res && res.data && res.data.content) { | ||||
| const { content } = res.data; | const { content } = res.data; | ||||
| setTypeList( | setTypeList( | ||||
| content.filter( | |||||
| (item: CategoryData) => | |||||
| Number(item.category_id) === resourceConfig[resourceType].typeValue, | |||||
| ), | |||||
| content.filter((item: CategoryData) => Number(item.category_id) === config.typeValue), | |||||
| ); | ); | ||||
| setTagList( | setTagList( | ||||
| content.filter( | |||||
| (item: CategoryData) => | |||||
| Number(item.category_id) === resourceConfig[resourceType].tagValue, | |||||
| ), | |||||
| content.filter((item: CategoryData) => Number(item.category_id) === config.tagValue), | |||||
| ); | ); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -76,11 +71,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['resource-page']}> | <div className={styles['resource-page']}> | ||||
| <div className={styles['resource-page__tabs-container']}> | <div className={styles['resource-page__tabs-container']}> | ||||
| <Tabs | |||||
| activeKey={activeTab} | |||||
| items={resourceConfig[resourceType].tabItems} | |||||
| onChange={hanleTabChange} | |||||
| /> | |||||
| <Tabs activeKey={activeTab} items={config.tabItems} onChange={hanleTabChange} /> | |||||
| </div> | </div> | ||||
| <Flex style={{ marginTop: '10px', height: 'calc(100% - 59px)' }}> | <Flex style={{ marginTop: '10px', height: 'calc(100% - 59px)' }}> | ||||
| <CategoryList | <CategoryList | ||||
| @@ -41,6 +41,7 @@ function ResourceVersion({ | |||||
| }: ResourceVersionProps) { | }: ResourceVersionProps) { | ||||
| const [fileList, setFileList] = useState<ResourceFileData[]>([]); | const [fileList, setFileList] = useState<ResourceFileData[]>([]); | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const config = resourceConfig[resourceType]; | |||||
| // 获取版本文件列表 | // 获取版本文件列表 | ||||
| useEffectWhen( | useEffectWhen( | ||||
| @@ -59,9 +60,9 @@ function ResourceVersion({ | |||||
| const getFileList = async (version: string) => { | const getFileList = async (version: string) => { | ||||
| const params = { | const params = { | ||||
| version, | version, | ||||
| [resourceConfig[resourceType].fileReqParamKey]: resourceId, | |||||
| [config.fileReqParamKey]: resourceId, | |||||
| }; | }; | ||||
| const request = resourceConfig[resourceType].getFiles; | |||||
| const request = config.getFiles; | |||||
| const [res] = await to(request(params)); | const [res] = await to(request(params)); | ||||
| if (res) { | if (res) { | ||||
| setFileList(res?.data?.content ?? []); | setFileList(res?.data?.content ?? []); | ||||
| @@ -70,9 +71,9 @@ function ResourceVersion({ | |||||
| // 删除版本 | // 删除版本 | ||||
| const deleteVersion = async () => { | const deleteVersion = async () => { | ||||
| const request = resourceConfig[resourceType].deleteVersion; | |||||
| const request = config.deleteVersion; | |||||
| const params = { | const params = { | ||||
| [resourceConfig[resourceType].idParamKey]: resourceId, | |||||
| [config.idParamKey]: resourceId, | |||||
| version, | version, | ||||
| }; | }; | ||||
| const [res] = await to(request(params)); | const [res] = await to(request(params)); | ||||
| @@ -111,13 +112,13 @@ function ResourceVersion({ | |||||
| // 全部导出 | // 全部导出 | ||||
| const handleExport = async () => { | const handleExport = async () => { | ||||
| const url = resourceConfig[resourceType].downloadAllAction; | |||||
| const url = config.downloadAllAction; | |||||
| downLoadZip(url, { models_id: resourceId, version }); | downLoadZip(url, { models_id: resourceId, version }); | ||||
| }; | }; | ||||
| // 单个导出 | // 单个导出 | ||||
| const downloadAlone = (record: ResourceFileData) => { | const downloadAlone = (record: ResourceFileData) => { | ||||
| const url = resourceConfig[resourceType].downloadSingleAction; | |||||
| const url = config.downloadSingleAction; | |||||
| downLoadZip(`${url}/${record.id}`); | downLoadZip(`${url}/${record.id}`); | ||||
| }; | }; | ||||
| @@ -0,0 +1,17 @@ | |||||
| .editor-create { | |||||
| height: 100%; | |||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 30px 30px 10px; | |||||
| overflow: auto; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__type { | |||||
| color: @text-color; | |||||
| font-size: @font-size-input-lg; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,344 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 创建镜像 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | |||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import ParameterInput from '@/components/ParameterInput'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import ResourceSelectorModal, { | |||||
| ResourceSelectorResponse, | |||||
| ResourceSelectorType, | |||||
| selectorTypeConfig, | |||||
| } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||||
| import { createEditorReq } from '@/services/developmentEnvironment'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { App, Button, Col, Form, Input, Row, Select } from 'antd'; | |||||
| import { pick } from 'lodash'; | |||||
| import { useState } from 'react'; | |||||
| import styles from './index.less'; | |||||
| type FormData = { | |||||
| name: string; | |||||
| computing_resource: string; | |||||
| standard: string; | |||||
| image: string; | |||||
| model: ResourceSelectorResponse; | |||||
| dataset: ResourceSelectorResponse; | |||||
| }; | |||||
| enum ComputingResourceType { | |||||
| GPU = 'GPU', | |||||
| NPU = 'NPU', | |||||
| } | |||||
| const EditorRadioItems: KFRadioItem[] = [ | |||||
| { | |||||
| key: ComputingResourceType.GPU, | |||||
| title: '英伟达GPU', | |||||
| icon: <KFIcon type="icon-jiyugongwangjingxiang" />, | |||||
| }, | |||||
| { | |||||
| key: ComputingResourceType.NPU, | |||||
| title: '昇腾NPU', | |||||
| icon: <KFIcon type="icon-bendishangchuan" />, | |||||
| }, | |||||
| ]; | |||||
| function EditorCreate() { | |||||
| const navgite = useNavigate(); | |||||
| const [form] = Form.useForm(); | |||||
| const { message } = App.useApp(); | |||||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); | |||||
| const [selectedModel, setSelectedModel] = useState<ResourceSelectorResponse | undefined>( | |||||
| undefined, | |||||
| ); // 选择的模型,为了再次打开时恢复原来的选择 | |||||
| const [selectedDataset, setSelectedDataset] = useState<ResourceSelectorResponse | undefined>( | |||||
| undefined, | |||||
| ); // 选择的数据集,为了再次打开时恢复原来的选择 | |||||
| const [selectedMirror, setSelectedMirror] = useState<ResourceSelectorResponse | undefined>( | |||||
| undefined, | |||||
| ); // 选择的镜像,为了再次打开时恢复原来的选择 | |||||
| // 创建编辑器 | |||||
| const createEditor = async (formData: FormData) => { | |||||
| // const { model, dataset } = formData; | |||||
| // const params = { | |||||
| // ...formData, | |||||
| // model: JSON.stringify(omit(model, ['showValue'])), | |||||
| // dataset: JSON.stringify(dataset, ['showValue']), | |||||
| // }; | |||||
| const [res] = await to(createEditorReq(formData)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navgite(-1); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const handleSubmit = (values: FormData) => { | |||||
| createEditor(values); | |||||
| }; | |||||
| // 取消 | |||||
| const cancel = () => { | |||||
| navgite(-1); | |||||
| }; | |||||
| // 获取选择数据集、模型后面按钮 icon | |||||
| const getSelectBtnIcon = (type: ResourceSelectorType) => { | |||||
| return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />; | |||||
| }; | |||||
| // 选择模型、镜像、数据集 | |||||
| const selectResource = (name: string, type: ResourceSelectorType) => { | |||||
| let resource: ResourceSelectorResponse | undefined; | |||||
| switch (type) { | |||||
| case ResourceSelectorType.Model: | |||||
| resource = selectedModel; | |||||
| break; | |||||
| case ResourceSelectorType.Dataset: | |||||
| resource = selectedDataset; | |||||
| break; | |||||
| default: | |||||
| resource = selectedMirror; | |||||
| break; | |||||
| } | |||||
| const { close } = openAntdModal(ResourceSelectorModal, { | |||||
| type, | |||||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||||
| defaultActiveTab: resource?.activeTab, | |||||
| onOk: (res) => { | |||||
| if (res) { | |||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| form.setFieldValue(name, res.path); | |||||
| setSelectedMirror(res); | |||||
| } else { | |||||
| const showValue = `${res.name}:${res.version}`; | |||||
| form.setFieldValue(name, { | |||||
| ...pick(res, ['id', 'version', 'path']), | |||||
| showValue, | |||||
| }); | |||||
| if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(res); | |||||
| } else if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(res); | |||||
| } | |||||
| } | |||||
| } else { | |||||
| if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(undefined); | |||||
| } else if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(undefined); | |||||
| } else if (type === ResourceSelectorType.Mirror) { | |||||
| setSelectedMirror(undefined); | |||||
| } | |||||
| form.setFieldValue(name, ''); | |||||
| } | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['editor-create']}> | |||||
| <PageTitle title="创建编辑器"></PageTitle> | |||||
| <div className={styles['editor-create__content']}> | |||||
| <div> | |||||
| <Form | |||||
| name="editor-create" | |||||
| labelCol={{ flex: '100px' }} | |||||
| wrapperCol={{ flex: 1 }} | |||||
| labelAlign="left" | |||||
| form={form} | |||||
| initialValues={{ computing_resource: ComputingResourceType.GPU }} | |||||
| onFinish={handleSubmit} | |||||
| size="large" | |||||
| > | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <Row gutter={10}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="任务名称" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入任务名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入任务名称" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={10}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="计算资源" | |||||
| name="computing_resource" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择计算资源', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <KFRadio items={EditorRadioItems}></KFRadio> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="资源规格" | |||||
| name="standard" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select | |||||
| showSearch | |||||
| placeholder="请选择资源规格" | |||||
| filterOption={filterResourceStandard} | |||||
| options={resourceStandardList} | |||||
| fieldNames={{ | |||||
| label: 'description', | |||||
| value: 'standard', | |||||
| }} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <SubAreaTitle | |||||
| title="参数设置" | |||||
| image={require('@/assets/img/editor-parameter.png')} | |||||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||||
| ></SubAreaTitle> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="镜像" | |||||
| name="image" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <ParameterInput | |||||
| placeholder="请选择镜像" | |||||
| canInput={false} | |||||
| size="large" | |||||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <Button | |||||
| size="large" | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} | |||||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||||
| > | |||||
| 选择镜像 | |||||
| </Button> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="模型" | |||||
| name="model" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择模型', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <ParameterInput | |||||
| placeholder="请选择模型" | |||||
| canInput={false} | |||||
| size="large" | |||||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <Button | |||||
| size="large" | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(ResourceSelectorType.Model)} | |||||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||||
| > | |||||
| 选择模型 | |||||
| </Button> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="数据集" | |||||
| name="dataset" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择数据集', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <ParameterInput | |||||
| placeholder="请选择数据集" | |||||
| canInput={false} | |||||
| size="large" | |||||
| onClick={() => selectResource('dataset', ResourceSelectorType.Dataset)} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <Button | |||||
| size="large" | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(ResourceSelectorType.Dataset)} | |||||
| onClick={() => selectResource('dataset', ResourceSelectorType.Dataset)} | |||||
| > | |||||
| 选择数据集 | |||||
| </Button> | |||||
| </Col> | |||||
| </Row> | |||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||||
| <Button type="primary" htmlType="submit"> | |||||
| 确定 | |||||
| </Button> | |||||
| <Button | |||||
| type="default" | |||||
| htmlType="button" | |||||
| onClick={cancel} | |||||
| style={{ marginLeft: '20px' }} | |||||
| > | |||||
| 取消 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default EditorCreate; | |||||
| @@ -0,0 +1,22 @@ | |||||
| .develop-env { | |||||
| height: 100%; | |||||
| &__header { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: flex-end; | |||||
| height: 50px; | |||||
| margin-bottom: 10px; | |||||
| padding: 0 30px; | |||||
| background-image: url(@/assets/img/page-title-bg.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| } | |||||
| &__table { | |||||
| height: calc(100% - 60px); | |||||
| padding: 20px 30px 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,263 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 开发环境 | |||||
| */ | |||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { DevEditorStatus } from '@/enums'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { | |||||
| deleteEditorReq, | |||||
| getEditorListReq, | |||||
| startEditorReq, | |||||
| stopEditorReq, | |||||
| } from '@/services/developmentEnvironment'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { | |||||
| App, | |||||
| Button, | |||||
| ConfigProvider, | |||||
| Table, | |||||
| type TablePaginationConfig, | |||||
| type TableProps, | |||||
| } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import EditorStatusCell from '../components/EditorStatusCell'; | |||||
| import styles from './index.less'; | |||||
| export type EditorData = { | |||||
| id: number; | |||||
| name: string; | |||||
| status: string; | |||||
| computing_resource: string; | |||||
| update_by: string; | |||||
| create_time: string; | |||||
| }; | |||||
| function EditorList() { | |||||
| const navigate = useNavigate(); | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const { message } = App.useApp(); | |||||
| const [tableData, setTableData] = useState<EditorData[]>([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||||
| cacheState?.pagination ?? { | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }, | |||||
| ); | |||||
| useEffect(() => { | |||||
| getEditorList(); | |||||
| }, [pagination]); | |||||
| // 获取编辑器列表 | |||||
| const getEditorList = async () => { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| }; | |||||
| const [res] = await to(getEditorListReq(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }; | |||||
| // 删除编辑器 | |||||
| const deleteEditor = async (id: number) => { | |||||
| const [res] = await to(deleteEditorReq(id)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getEditorList(); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 启动编辑器 | |||||
| const startEditor = async (id: number) => { | |||||
| const [res] = await to(startEditorReq(id)); | |||||
| if (res) { | |||||
| message.success('操作成功'); | |||||
| getEditorList(); | |||||
| } | |||||
| }; | |||||
| // 停止编辑器 | |||||
| const stopEditor = async (id: number) => { | |||||
| const [res] = await to(stopEditorReq(id)); | |||||
| if (res) { | |||||
| message.success('操作成功'); | |||||
| getEditorList(); | |||||
| } | |||||
| }; | |||||
| // 处理删除 | |||||
| const handleEditorDelete = (record: EditorData) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该编辑器将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteEditor(record.id); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 创建编辑器 | |||||
| const createEditor = () => { | |||||
| navigate(`/developmentEnvironment/create`); | |||||
| setCacheState({ | |||||
| pagination, | |||||
| }); | |||||
| }; | |||||
| // 分页切换 | |||||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| }; | |||||
| const columns: TableProps<EditorData>['columns'] = [ | |||||
| { | |||||
| title: '编辑器名称', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| width: '30%', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'status', | |||||
| key: 'status', | |||||
| width: '10%', | |||||
| render: EditorStatusCell, | |||||
| }, | |||||
| { | |||||
| title: '资源', | |||||
| dataIndex: 'computing_resource', | |||||
| key: 'computing_resource', | |||||
| width: '20%', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '创建者', | |||||
| dataIndex: 'update_by', | |||||
| key: 'update_by', | |||||
| width: '20%', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '创建时间', | |||||
| dataIndex: 'create_time', | |||||
| key: 'create_time', | |||||
| width: '20%', | |||||
| render: DateTableCell, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: 300, | |||||
| key: 'operation', | |||||
| render: (_: any, record: EditorData) => ( | |||||
| <div> | |||||
| {record.status === DevEditorStatus.Pending || | |||||
| record.status === DevEditorStatus.Running ? ( | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="stop" | |||||
| icon={<KFIcon type="icon-tingzhi" />} | |||||
| onClick={() => stopEditor(record.id)} | |||||
| > | |||||
| 停止 | |||||
| </Button> | |||||
| ) : ( | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="debug" | |||||
| icon={<KFIcon type="icon-tiaoshi" />} | |||||
| onClick={() => startEditor(record.id)} | |||||
| > | |||||
| 再次调试 | |||||
| </Button> | |||||
| )} | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['warningColor'], | |||||
| }, | |||||
| }} | |||||
| > | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => handleEditorDelete(record)} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </ConfigProvider> | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['develop-env']}> | |||||
| <div className={styles['develop-env__header']}> | |||||
| <Button | |||||
| style={{ marginLeft: '20px' }} | |||||
| type="default" | |||||
| onClick={createEditor} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 创建编辑器 | |||||
| </Button> | |||||
| <Button | |||||
| style={{ marginLeft: '20px' }} | |||||
| type="default" | |||||
| onClick={getEditorList} | |||||
| icon={<KFIcon type="icon-shuaxin" />} | |||||
| > | |||||
| 刷新 | |||||
| </Button> | |||||
| </div> | |||||
| <div className={classNames('vertical-scroll-table', styles['develop-env__table'])}> | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columns} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default EditorList; | |||||
| @@ -0,0 +1,19 @@ | |||||
| .model-deployment-status-cell { | |||||
| color: @text-color; | |||||
| &--running { | |||||
| color: @primary-color; | |||||
| } | |||||
| &--terminated { | |||||
| color: @abort-color; | |||||
| } | |||||
| &--error { | |||||
| color: @error-color; | |||||
| } | |||||
| &--pending { | |||||
| color: @warning-color; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,44 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-18 18:35:41 | |||||
| * @Description: 编辑器状态组件 | |||||
| */ | |||||
| import { DevEditorStatus } from '@/enums'; | |||||
| import styles from './index.less'; | |||||
| export type DevEditorStatusInfo = { | |||||
| text: string; | |||||
| classname: string; | |||||
| }; | |||||
| export const statusInfo: Record<DevEditorStatus, DevEditorStatusInfo> = { | |||||
| [DevEditorStatus.Unknown]: { | |||||
| text: '未启动', | |||||
| classname: styles['model-deployment-status-cell'], | |||||
| }, | |||||
| [DevEditorStatus.Running]: { | |||||
| classname: styles['model-deployment-status-cell--running'], | |||||
| text: '运行中', | |||||
| }, | |||||
| [DevEditorStatus.Terminated]: { | |||||
| classname: styles['model-deployment-status-cell--terminated'], | |||||
| text: '已停止', | |||||
| }, | |||||
| [DevEditorStatus.Failed]: { | |||||
| classname: styles['model-deployment-status-cell--error'], | |||||
| text: '失败', | |||||
| }, | |||||
| [DevEditorStatus.Pending]: { | |||||
| classname: styles['model-deployment-status-cell--pending'], | |||||
| text: '启动中', | |||||
| }, | |||||
| }; | |||||
| function EditorStatusCell(status?: DevEditorStatus | null) { | |||||
| if (status === null || status === undefined || !statusInfo[status]) { | |||||
| return <span>--</span>; | |||||
| } | |||||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | |||||
| } | |||||
| export default EditorStatusCell; | |||||
| @@ -1,4 +1,4 @@ | |||||
| .modal { | |||||
| .add-experiment-modal { | |||||
| .global_param_item { | .global_param_item { | ||||
| max-height: 230px; | max-height: 230px; | ||||
| padding: 24px 12px 0; | padding: 24px 12px 0; | ||||
| @@ -115,7 +115,7 @@ function AddExperimentModal({ | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <KFModal | <KFModal | ||||
| className={styles.modal} | |||||
| className={styles['add-experiment-modal']} | |||||
| title={modalTitle} | title={modalTitle} | ||||
| image={modalIcon} | image={modalIcon} | ||||
| open={open} | open={open} | ||||
| @@ -17,7 +17,6 @@ | |||||
| &__name { | &__name { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: space-between; | |||||
| padding: 10px 0; | padding: 10px 0; | ||||
| border-bottom: 1px solid rgba(234, 234, 234, 0.8); | border-bottom: 1px solid rgba(234, 234, 234, 0.8); | ||||
| } | } | ||||
| @@ -1,6 +1,9 @@ | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { Button } from 'antd'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { App, Button } from 'antd'; | |||||
| import ExportModelModal from '../ExportModelModal'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ExperimentResultProps = { | type ExperimentResultProps = { | ||||
| results?: ExperimentResultData[] | null; | results?: ExperimentResultData[] | null; | ||||
| }; | }; | ||||
| @@ -16,8 +19,22 @@ type ExperimentResultData = { | |||||
| }; | }; | ||||
| function ExperimentResult({ results }: ExperimentResultProps) { | function ExperimentResult({ results }: ExperimentResultProps) { | ||||
| const exportResult = (val: string) => { | |||||
| downLoadZip(`/api/mmp/minioStorage/download`, { path: val }); | |||||
| const { message } = App.useApp(); | |||||
| // 下载 | |||||
| const download = (path: string) => { | |||||
| downLoadZip(`/api/mmp/minioStorage/download`, { path }); | |||||
| }; | |||||
| // 导出到模型库 | |||||
| const exportToModel = (path: string) => { | |||||
| const { close } = openAntdModal(ExportModelModal, { | |||||
| path, | |||||
| onOk: () => { | |||||
| message.success('导出成功'); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| @@ -31,12 +48,22 @@ function ExperimentResult({ results }: ExperimentResultProps) { | |||||
| <Button | <Button | ||||
| size="small" | size="small" | ||||
| type="link" | type="link" | ||||
| style={{ marginLeft: 'auto' }} | |||||
| onClick={() => { | onClick={() => { | ||||
| exportResult(item.path); | |||||
| download(item.path); | |||||
| }} | }} | ||||
| > | > | ||||
| 下载 | 下载 | ||||
| </Button> | </Button> | ||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| onClick={() => { | |||||
| exportToModel(item.path); | |||||
| }} | |||||
| > | |||||
| 导出到模型库 | |||||
| </Button> | |||||
| {/* <a style={{ marginRight: '10px' }}>导出到模型库</a> | {/* <a style={{ marginRight: '10px' }}>导出到模型库</a> | ||||
| <a style={{ marginRight: '10px' }}>导出到数据集</a> */} | <a style={{ marginRight: '10px' }}>导出到数据集</a> */} | ||||
| </div> | </div> | ||||
| @@ -0,0 +1,7 @@ | |||||
| .export-model-modal__tooltip { | |||||
| :global { | |||||
| .ant-tooltip-inner { | |||||
| white-space: pre-line; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,207 @@ | |||||
| import editExperimentIcon from '@/assets/img/edit-experiment.png'; | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { type ResourceData } from '@/pages/Dataset/config'; | |||||
| import { | |||||
| addModelsVersionDetail, | |||||
| exportModelReq, | |||||
| getModelList, | |||||
| getModelVersionsById, | |||||
| } from '@/services/dataset'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { InfoCircleOutlined } from '@ant-design/icons'; | |||||
| import { Form, Input, ModalProps, Select } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from './index.less'; | |||||
| type FormData = { | |||||
| models_id: string; | |||||
| version: string; | |||||
| description: string; | |||||
| }; | |||||
| type ExportModelResponce = { | |||||
| fileName: string; | |||||
| fileSize: string; | |||||
| url: string; | |||||
| }; | |||||
| type CreateModelVersionParams = FormData & { | |||||
| file_name: string; | |||||
| file_size: string; | |||||
| url: string; | |||||
| // name: string; | |||||
| }; | |||||
| interface ExportModelModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| path: string; | |||||
| onOk: () => void; | |||||
| } | |||||
| function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { | |||||
| const [form] = Form.useForm(); | |||||
| const [models, setModels] = useState<ResourceData[]>([]); | |||||
| const [versions, setVersions] = useState<string[]>([]); | |||||
| const [uuid] = useState(Date.now()); | |||||
| const layout = { | |||||
| labelCol: { span: 24 }, | |||||
| wrapperCol: { span: 24 }, | |||||
| }; | |||||
| useEffect(() => { | |||||
| requestModelList(); | |||||
| }, []); | |||||
| // 模型版本tooltip | |||||
| const getTooltip = () => { | |||||
| const id = form.getFieldValue('models_id'); | |||||
| const name = models.find((item) => item.id === id)?.name ?? ''; | |||||
| const tooltip = | |||||
| versions.length > 0 ? `${name}有以下版本:\n${versions.join('、')}\n注意不能重复` : undefined; | |||||
| return tooltip; | |||||
| }; | |||||
| // 处理模型名称变化 | |||||
| const handleModelChange = (id: number | undefined) => { | |||||
| if (id) { | |||||
| getModelVersions(id); | |||||
| } else { | |||||
| setVersions([]); | |||||
| } | |||||
| }; | |||||
| // 获取模型列表 | |||||
| const requestModelList = async () => { | |||||
| const params = { | |||||
| page: 0, | |||||
| size: 1000, | |||||
| available_range: 0, // 个人 | |||||
| }; | |||||
| const [res] = await to(getModelList(params)); | |||||
| if (res && res.data) { | |||||
| setModels(res.data.content || []); | |||||
| } | |||||
| }; | |||||
| // 获取模型版本列表 | |||||
| const getModelVersions = async (id: number) => { | |||||
| const [res] = await to(getModelVersionsById(id)); | |||||
| if (res && res.data) { | |||||
| setVersions(res.data); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const hanldeFinish = (formData: FormData) => { | |||||
| exportToModel(formData); | |||||
| }; | |||||
| // 导出到模型 | |||||
| const exportToModel = async (formData: FormData) => { | |||||
| const params = { | |||||
| uuid: String(uuid), | |||||
| path, | |||||
| }; | |||||
| const [res] = await to(exportModelReq(params)); | |||||
| if (res && res.data) { | |||||
| const files = res.data as ExportModelResponce[]; | |||||
| const params: CreateModelVersionParams[] = files.map((item) => ({ | |||||
| ...formData, | |||||
| file_name: item.fileName, | |||||
| file_size: item.fileSize, | |||||
| url: item.url, | |||||
| })); | |||||
| createModelVersion(params); | |||||
| } | |||||
| }; | |||||
| // 创建模型版本 | |||||
| const createModelVersion = async (params: CreateModelVersionParams[]) => { | |||||
| const [res] = await to(addModelsVersionDetail(params)); | |||||
| if (res) { | |||||
| onOk(); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title="导出到模型库" | |||||
| image={editExperimentIcon} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| width={825} | |||||
| className={styles['export-model-modal']} | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| layout="horizontal" | |||||
| onFinish={hanldeFinish} | |||||
| autoComplete="off" | |||||
| form={form} | |||||
| {...layout} | |||||
| labelAlign="left" | |||||
| labelWrap | |||||
| > | |||||
| <Form.Item | |||||
| label="模型名称" | |||||
| name="models_id" | |||||
| rules={[{ required: true, message: '请选择模型' }]} | |||||
| > | |||||
| <Select | |||||
| placeholder="请选择模型" | |||||
| onChange={handleModelChange} | |||||
| options={models} | |||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| allowClear | |||||
| ></Select> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="模型版本" | |||||
| name="version" | |||||
| tooltip={ | |||||
| getTooltip() | |||||
| ? { | |||||
| overlayClassName: styles['export-model-modal__tooltip'], | |||||
| title: getTooltip(), | |||||
| icon: <InfoCircleOutlined />, | |||||
| } | |||||
| : undefined | |||||
| } | |||||
| rules={[ | |||||
| { required: true, message: '请输入模型版本' }, | |||||
| { | |||||
| validator: (_, value) => { | |||||
| if (value && versions.includes(value)) { | |||||
| return Promise.reject('模型版本已存在'); | |||||
| } else { | |||||
| return Promise.resolve(); | |||||
| } | |||||
| }, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入模型版本" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="版本描述" | |||||
| name="description" | |||||
| rules={[{ required: true, message: '请输入版本描述' }]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入版本描述" | |||||
| maxLength={128} | |||||
| autoSize={{ minRows: 2, maxRows: 5 }} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default ExportModelModal; | |||||
| @@ -21,8 +21,8 @@ function ExperimentText() { | |||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| const locationParams = useParams(); //新版本获取路由参数接口 | const locationParams = useParams(); //新版本获取路由参数接口 | ||||
| const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); | const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); | ||||
| const graphRef = useRef(); | const graphRef = useRef(); | ||||
| const getGraphData = (data) => { | const getGraphData = (data) => { | ||||
| if (graph) { | if (graph) { | ||||
| // 修改历史数据有蓝色边框的问题 | // 修改历史数据有蓝色边框的问题 | ||||
| @@ -202,9 +202,6 @@ function ExperimentText() { | |||||
| }, | }, | ||||
| }, | }, | ||||
| defaultEdge: { | defaultEdge: { | ||||
| // type: 'quadratic', | |||||
| // type: 'cubic-vertical', | |||||
| style: { | style: { | ||||
| endArrow: { | endArrow: { | ||||
| // 设置终点箭头 | // 设置终点箭头 | ||||
| @@ -226,16 +223,6 @@ function ExperimentText() { | |||||
| }, | }, | ||||
| }, | }, | ||||
| }, | }, | ||||
| defaultCombo: { | |||||
| type: 'rect', | |||||
| fixCollapseSize: 70, | |||||
| style: { | |||||
| fill: '#00e0ff0d', | |||||
| stroke: '#00e0ff', | |||||
| lineDash: [5, 10], | |||||
| cursor: 'pointer', | |||||
| }, | |||||
| }, | |||||
| }); | }); | ||||
| graph.on('node:click', (e) => { | graph.on('node:click', (e) => { | ||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | if (e.target.get('name') !== 'anchor-point' && e.item) { | ||||
| @@ -31,23 +31,5 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | 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; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -32,7 +32,6 @@ import { | |||||
| type TablePaginationConfig, | type TablePaginationConfig, | ||||
| type TableProps, | type TableProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useMemo, 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'; | ||||
| @@ -282,7 +281,7 @@ function MirrorInfo() { | |||||
| </Flex> | </Flex> | ||||
| </div> | </div> | ||||
| <div | <div | ||||
| className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])} | |||||
| className="vertical-scroll-table" | |||||
| style={{ marginTop: '24px', height: `calc(100% - ${topHeight + 24}px)` }} | style={{ marginTop: '24px', height: `calc(100% - ${topHeight + 24}px)` }} | ||||
| > | > | ||||
| <Table | <Table | ||||
| @@ -5,21 +5,13 @@ import themes from '@/styles/theme.less'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6, { G6GraphEvent, Graph } from '@antv/g6'; | import G6, { G6GraphEvent, Graph } from '@antv/g6'; | ||||
| // @ts-ignore | // @ts-ignore | ||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import { Flex, Select } from 'antd'; | import { Flex, Select } from 'antd'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import GraphLegend from '../GraphLegend'; | import GraphLegend from '../GraphLegend'; | ||||
| import NodeTooltips from '../NodeTooltips'; | import NodeTooltips from '../NodeTooltips'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils'; | import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils'; | ||||
| import { | |||||
| NodeType, | |||||
| getGraphData, | |||||
| nodeFontSize, | |||||
| nodeHeight, | |||||
| nodeWidth, | |||||
| normalizeTreeData, | |||||
| } from './utils'; | |||||
| import { getGraphData, nodeFontSize, nodeHeight, nodeWidth, normalizeTreeData } from './utils'; | |||||
| type modeModelEvolutionProps = { | type modeModelEvolutionProps = { | ||||
| resourceId: number; | resourceId: number; | ||||
| @@ -80,7 +72,7 @@ function ModelEvolution({ | |||||
| width: graphRef.current!.clientWidth, | width: graphRef.current!.clientWidth, | ||||
| height: graphRef.current!.clientHeight, | height: graphRef.current!.clientHeight, | ||||
| fitView: true, | fitView: true, | ||||
| fitViewPadding: [100, 100, 100, 100], | |||||
| fitViewPadding: 200, | |||||
| minZoom: 0.5, | minZoom: 0.5, | ||||
| maxZoom: 5, | maxZoom: 5, | ||||
| defaultNode: { | defaultNode: { | ||||
| @@ -172,36 +164,7 @@ function ModelEvolution({ | |||||
| const nodeItem = e.item; | const nodeItem = e.item; | ||||
| const model = nodeItem.getModel() as ModelDepsData | ProjectDependency | TrainDataset; | const model = nodeItem.getModel() as ModelDepsData | ProjectDependency | TrainDataset; | ||||
| const { model_type } = model; | const { model_type } = model; | ||||
| const { origin } = location; | |||||
| let url: string = ''; | |||||
| switch (model_type) { | switch (model_type) { | ||||
| case NodeType.children: | |||||
| case NodeType.parent: { | |||||
| const { current_model_id, version } = model; | |||||
| url = `${origin}/dataset/model/${current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${version}`; | |||||
| break; | |||||
| } | |||||
| case NodeType.project: { | |||||
| const { url: projectUrl } = model; | |||||
| url = projectUrl; | |||||
| break; | |||||
| } | |||||
| case NodeType.trainDataset: | |||||
| case NodeType.testDataset: { | |||||
| const { dataset_id, dataset_version } = model; | |||||
| url = `${origin}/dataset/dataset/${dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${dataset_version}`; | |||||
| break; | |||||
| } | |||||
| case NodeType.current: { | |||||
| // TODO: 隐藏数据集和项目 | |||||
| break; | |||||
| } | |||||
| default: | |||||
| break; | |||||
| } | |||||
| if (url) { | |||||
| window.open(url, '_blank'); | |||||
| } | } | ||||
| }); | }); | ||||
| @@ -234,6 +197,8 @@ function ModelEvolution({ | |||||
| graph.data(graphData); | graph.data(graphData); | ||||
| graph.render(); | graph.render(); | ||||
| graph.fitView(); | graph.fitView(); | ||||
| setShowNodeTooltip(false); | |||||
| setEnterTooltip(false); | |||||
| } else { | } else { | ||||
| clearGraphData(); | clearGraphData(); | ||||
| } | } | ||||
| @@ -266,9 +231,11 @@ function ModelEvolution({ | |||||
| <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | ||||
| {(showNodeTooltip || enterTooltip) && ( | {(showNodeTooltip || enterTooltip) && ( | ||||
| <NodeTooltips | <NodeTooltips | ||||
| resourceId={resourceId} | |||||
| x={nodeTooltipX} | x={nodeTooltipX} | ||||
| y={nodeTooltipY} | y={nodeTooltipY} | ||||
| data={hoverNodeData!} | data={hoverNodeData!} | ||||
| onVersionChange={onVersionChange} | |||||
| onMouseEnter={handleTooltipsMouseEnter} | onMouseEnter={handleTooltipsMouseEnter} | ||||
| onMouseLeave={handleTooltipsMouseLeave} | onMouseLeave={handleTooltipsMouseLeave} | ||||
| /> | /> | ||||
| @@ -3,8 +3,8 @@ import { EdgeConfig, GraphData, LayoutConfig, NodeConfig, TreeGraphData, Util } | |||||
| // @ts-ignore | // @ts-ignore | ||||
| import Hierarchy from '@antv/hierarchy'; | import Hierarchy from '@antv/hierarchy'; | ||||
| export const nodeWidth = 110; | |||||
| export const nodeHeight = 50; | |||||
| export const nodeWidth = 90; | |||||
| export const nodeHeight = 40; | |||||
| export const vGap = nodeHeight + 20; | export const vGap = nodeHeight + 20; | ||||
| export const hGap = nodeWidth; | export const hGap = nodeWidth; | ||||
| export const ellipseWidth = nodeWidth; | export const ellipseWidth = nodeWidth; | ||||
| @@ -64,6 +64,7 @@ export type ModalDetail = { | |||||
| export interface ModelDepsAPIData { | export interface ModelDepsAPIData { | ||||
| current_model_id: number; | current_model_id: number; | ||||
| version: string; | version: string; | ||||
| workflow_id: number; | |||||
| exp_ins_id: number; | exp_ins_id: number; | ||||
| model_type: NodeType.children | NodeType.current | NodeType.parent; | model_type: NodeType.children | NodeType.current | NodeType.parent; | ||||
| current_model_name: string; | current_model_name: string; | ||||
| @@ -1,20 +1,35 @@ | |||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; | import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type NodeTooltipsProps = { | |||||
| data?: ModelDepsData | ProjectDependency | TrainDataset; | |||||
| x: number; | |||||
| y: number; | |||||
| onMouseEnter?: () => void; | |||||
| onMouseLeave?: () => void; | |||||
| type ModelInfoProps = { | |||||
| resourceId: number; | |||||
| data: ModelDepsData; | |||||
| onVersionChange: (version: string) => void; | |||||
| }; | }; | ||||
| function ModelInfo({ data }: { data: ModelDepsData }) { | |||||
| function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) { | |||||
| const navigate = useNavigate(); | |||||
| const gotoExperimentPage = () => { | const gotoExperimentPage = () => { | ||||
| if (data.train_task?.ins_id) { | if (data.train_task?.ins_id) { | ||||
| const { origin } = location; | const { origin } = location; | ||||
| window.open(`${origin}/pipeline/experiment/144/${data.train_task.ins_id}`, '_blank'); | |||||
| const url = `${origin}/pipeline/experiment/${data.workflow_id}/${data.train_task.ins_id}`; | |||||
| window.open(url, '_blank'); | |||||
| } | |||||
| }; | |||||
| const gotoModelPage = () => { | |||||
| if (data.model_type === NodeType.current) { | |||||
| return; | |||||
| } | |||||
| if (data.current_model_id === resourceId) { | |||||
| onVersionChange?.(data.version); | |||||
| } else { | |||||
| const path = `/dataset/model/${data.current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}`; | |||||
| navigate(path); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -24,9 +39,18 @@ function ModelInfo({ data }: { data: ModelDepsData }) { | |||||
| <div> | <div> | ||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>模型名称:</span> | <span className={styles['node-tooltips__row__title']}>模型名称:</span> | ||||
| <span className={styles['node-tooltips__row__value']}> | |||||
| {data.model_version_dependcy_vo.name || '--'} | |||||
| </span> | |||||
| {data.model_type === NodeType.current ? ( | |||||
| <span className={styles['node-tooltips__row__value']}> | |||||
| {data.model_version_dependcy_vo?.name || '--'} | |||||
| </span> | |||||
| ) : ( | |||||
| <ValueLink | |||||
| value={data.model_version_dependcy_vo?.name} | |||||
| className={styles['node-tooltips__row__link']} | |||||
| nullClassName={styles['node-tooltips__row__value']} | |||||
| onClick={gotoModelPage} | |||||
| ></ValueLink> | |||||
| )} | |||||
| </div> | </div> | ||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>模型版本:</span> | <span className={styles['node-tooltips__row__title']}>模型版本:</span> | ||||
| @@ -61,13 +85,12 @@ function ModelInfo({ data }: { data: ModelDepsData }) { | |||||
| <div> | <div> | ||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>训练任务:</span> | <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> | |||||
| ) : ( | |||||
| '--' | |||||
| )} | |||||
| <ValueLink | |||||
| value={data.train_task?.name} | |||||
| className={styles['node-tooltips__row__link']} | |||||
| nullClassName={styles['node-tooltips__row__value']} | |||||
| onClick={gotoExperimentPage} | |||||
| ></ValueLink> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </> | </> | ||||
| @@ -75,13 +98,24 @@ function ModelInfo({ data }: { data: ModelDepsData }) { | |||||
| } | } | ||||
| function DatasetInfo({ data }: { data: TrainDataset }) { | function DatasetInfo({ data }: { data: TrainDataset }) { | ||||
| const gotoDatasetPage = () => { | |||||
| const { origin } = location; | |||||
| const url = `${origin}/dataset/dataset/${data.dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.dataset_version}`; | |||||
| window.open(url, '_blank'); | |||||
| }; | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <div className={styles['node-tooltips__title']}>数据集信息</div> | <div className={styles['node-tooltips__title']}>数据集信息</div> | ||||
| <div> | <div> | ||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>数据集名称:</span> | <span className={styles['node-tooltips__row__title']}>数据集名称:</span> | ||||
| <span className={styles['node-tooltips__row__value']}>{data.dataset_name || '--'}</span> | |||||
| <ValueLink | |||||
| value={data.dataset_name} | |||||
| className={styles['node-tooltips__row__link']} | |||||
| nullClassName={styles['node-tooltips__row__value']} | |||||
| onClick={gotoDatasetPage} | |||||
| ></ValueLink> | |||||
| </div> | </div> | ||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>数据集版本:</span> | <span className={styles['node-tooltips__row__title']}>数据集版本:</span> | ||||
| @@ -95,13 +129,23 @@ function DatasetInfo({ data }: { data: TrainDataset }) { | |||||
| } | } | ||||
| function ProjectInfo({ data }: { data: ProjectDependency }) { | function ProjectInfo({ data }: { data: ProjectDependency }) { | ||||
| const gotoProjectPage = () => { | |||||
| const { url } = data; | |||||
| window.open(url, '_blank'); | |||||
| }; | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <div className={styles['node-tooltips__title']}>项目信息</div> | <div className={styles['node-tooltips__title']}>项目信息</div> | ||||
| <div> | <div> | ||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>项目名称:</span> | <span className={styles['node-tooltips__row__title']}>项目名称:</span> | ||||
| <span className={styles['node-tooltips__row__value']}>{data.name || '--'}</span> | |||||
| <ValueLink | |||||
| value={data.name} | |||||
| className={styles['node-tooltips__row__link']} | |||||
| nullClassName={styles['node-tooltips__row__value']} | |||||
| onClick={gotoProjectPage} | |||||
| ></ValueLink> | |||||
| </div> | </div> | ||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>项目分支:</span> | <span className={styles['node-tooltips__row__title']}>项目分支:</span> | ||||
| @@ -116,7 +160,42 @@ function ProjectInfo({ data }: { data: ProjectDependency }) { | |||||
| ); | ); | ||||
| } | } | ||||
| function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsProps) { | |||||
| type ValueLinkProps = { | |||||
| value: string | undefined; | |||||
| onClick?: () => void; | |||||
| className?: string; | |||||
| nullClassName?: string; | |||||
| }; | |||||
| const ValueLink = ({ value, onClick, className, nullClassName }: ValueLinkProps) => { | |||||
| return value ? ( | |||||
| <a className={className} onClick={onClick}> | |||||
| {value} | |||||
| </a> | |||||
| ) : ( | |||||
| <span className={nullClassName}>--</span> | |||||
| ); | |||||
| }; | |||||
| type NodeTooltipsProps = { | |||||
| resourceId: number; | |||||
| data: ModelDepsData | ProjectDependency | TrainDataset; | |||||
| x: number; | |||||
| y: number; | |||||
| onMouseEnter?: () => void; | |||||
| onMouseLeave?: () => void; | |||||
| onVersionChange: (version: string) => void; | |||||
| }; | |||||
| function NodeTooltips({ | |||||
| resourceId, | |||||
| data, | |||||
| x, | |||||
| y, | |||||
| onMouseEnter, | |||||
| onMouseLeave, | |||||
| onVersionChange, | |||||
| }: NodeTooltipsProps) { | |||||
| if (!data) return null; | if (!data) return null; | ||||
| let Component = null; | let Component = null; | ||||
| const { model_type } = data; | const { model_type } = data; | ||||
| @@ -129,7 +208,7 @@ function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsPr | |||||
| model_type === NodeType.parent || | model_type === NodeType.parent || | ||||
| model_type === NodeType.current | model_type === NodeType.current | ||||
| ) { | ) { | ||||
| Component = <ModelInfo data={data} />; | |||||
| Component = <ModelInfo resourceId={resourceId} data={data} onVersionChange={onVersionChange} />; | |||||
| } | } | ||||
| return ( | return ( | ||||
| <div | <div | ||||
| @@ -63,6 +63,9 @@ function ModelDeploymentCreate() { | |||||
| const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>( | const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>( | ||||
| undefined, | undefined, | ||||
| ); | ); | ||||
| const [selectedMirror, setSelectedMirror] = useState<ResourceSelectorResponse | undefined>( | |||||
| undefined, | |||||
| ); // 选择的镜像,为了再次打开时恢复原来的选择 | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -84,16 +87,14 @@ function ModelDeploymentCreate() { | |||||
| }; | }; | ||||
| // 选择模型、镜像 | // 选择模型、镜像 | ||||
| const selectResource = (name: string, selectType: string) => { | |||||
| let type; | |||||
| const selectResource = (name: string, type: ResourceSelectorType) => { | |||||
| let resource: ResourceSelectorResponse | undefined; | let resource: ResourceSelectorResponse | undefined; | ||||
| switch (selectType) { | |||||
| case 'model': | |||||
| type = ResourceSelectorType.Model; | |||||
| switch (type) { | |||||
| case ResourceSelectorType.Model: | |||||
| resource = selectedModel; | resource = selectedModel; | ||||
| break; | break; | ||||
| default: | default: | ||||
| type = ResourceSelectorType.Mirror; | |||||
| resource = selectedMirror; | |||||
| break; | break; | ||||
| } | } | ||||
| const { close } = openAntdModal(ResourceSelectorModal, { | const { close } = openAntdModal(ResourceSelectorModal, { | ||||
| @@ -105,18 +106,20 @@ function ModelDeploymentCreate() { | |||||
| if (res) { | if (res) { | ||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| form.setFieldValue(name, res.path); | form.setFieldValue(name, res.path); | ||||
| setSelectedMirror(res); | |||||
| } else { | } else { | ||||
| const response = res as ResourceSelectorResponse; | |||||
| const showValue = `${response.name}:${response.version}`; | |||||
| const showValue = `${res.name}:${res.version}`; | |||||
| form.setFieldValue(name, { | form.setFieldValue(name, { | ||||
| ...pick(response, ['id', 'version', 'path']), | |||||
| ...pick(res, ['id', 'version', 'path']), | |||||
| showValue, | showValue, | ||||
| }); | }); | ||||
| setSelectedModel(response); | |||||
| setSelectedModel(res); | |||||
| } | } | ||||
| } else { | } else { | ||||
| if (type === ResourceSelectorType.Model) { | if (type === ResourceSelectorType.Model) { | ||||
| setSelectedModel(undefined); | setSelectedModel(undefined); | ||||
| } else { | |||||
| setSelectedMirror(undefined); | |||||
| } | } | ||||
| form.setFieldValue(name, ''); | form.setFieldValue(name, ''); | ||||
| } | } | ||||
| @@ -248,7 +251,6 @@ function ModelDeploymentCreate() { | |||||
| image={require('@/assets/img/model-deployment.png')} | image={require('@/assets/img/model-deployment.png')} | ||||
| style={{ marginTop: '20px', marginBottom: '24px' }} | style={{ marginTop: '20px', marginBottom: '24px' }} | ||||
| ></SubAreaTitle> | ></SubAreaTitle> | ||||
| <Row gutter={8}> | <Row gutter={8}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| @@ -266,6 +268,7 @@ function ModelDeploymentCreate() { | |||||
| disabled={disabled} | disabled={disabled} | ||||
| canInput={false} | canInput={false} | ||||
| size="large" | size="large" | ||||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| @@ -275,7 +278,7 @@ function ModelDeploymentCreate() { | |||||
| size="large" | size="large" | ||||
| type="link" | type="link" | ||||
| icon={getSelectBtnIcon(ResourceSelectorType.Model)} | icon={getSelectBtnIcon(ResourceSelectorType.Model)} | ||||
| onClick={() => selectResource('model', 'model')} | |||||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||||
| > | > | ||||
| 选择模型 | 选择模型 | ||||
| </Button> | </Button> | ||||
| @@ -293,7 +296,12 @@ function ModelDeploymentCreate() { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <ParameterInput placeholder="请选择镜像" canInput={false} size="large" /> | |||||
| <ParameterInput | |||||
| placeholder="请选择镜像" | |||||
| canInput={false} | |||||
| size="large" | |||||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||||
| /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| @@ -301,7 +309,7 @@ function ModelDeploymentCreate() { | |||||
| size="large" | size="large" | ||||
| type="link" | type="link" | ||||
| icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} | icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} | ||||
| onClick={() => selectResource('image', 'image')} | |||||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||||
| > | > | ||||
| 选择镜像 | 选择镜像 | ||||
| </Button> | </Button> | ||||
| @@ -11,7 +11,7 @@ | |||||
| :global { | :global { | ||||
| .anticon.anticon-question-circle { | .anticon.anticon-question-circle { | ||||
| margin-top: -14px; | |||||
| margin-top: -12px; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -76,9 +76,27 @@ const GlobalParamsDrawer = forwardRef( | |||||
| {...restField} | {...restField} | ||||
| name={[name, 'param_name']} | name={[name, 'param_name']} | ||||
| label="参数名称" | label="参数名称" | ||||
| rules={[{ required: true, message: '请输入参数名称' }]} | |||||
| validateTrigger={[]} | |||||
| rules={[ | |||||
| { required: true, message: '请输入参数名称' }, | |||||
| { | |||||
| validator: (_, value) => { | |||||
| const list = form.getFieldValue('global_param') || []; | |||||
| const names = list.filter((item: any) => item?.param_name === value); | |||||
| if (value && names.length > 1) { | |||||
| return Promise.reject('参数名称不能重复'); | |||||
| } else { | |||||
| return Promise.resolve(); | |||||
| } | |||||
| }, | |||||
| }, | |||||
| ]} | |||||
| > | > | ||||
| <Input placeholder="请输入参数名称" allowClear /> | |||||
| <Input | |||||
| placeholder="请输入参数名称" | |||||
| allowClear | |||||
| onBlur={() => form.validateFields()} | |||||
| /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| {...restField} | {...restField} | ||||
| @@ -118,6 +118,7 @@ function ResourceSelectorModal({ | |||||
| const [fisrtLoadList, setFisrtLoadList] = useState(false); | const [fisrtLoadList, setFisrtLoadList] = useState(false); | ||||
| const [fisrtLoadVersions, setFisrtLoadVersions] = useState(false); | const [fisrtLoadVersions, setFisrtLoadVersions] = useState(false); | ||||
| const treeRef = useRef<TreeRef>(null); | const treeRef = useRef<TreeRef>(null); | ||||
| const config = selectorTypeConfig[type]; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| setExpandedKeys([]); | setExpandedKeys([]); | ||||
| @@ -143,9 +144,9 @@ function ResourceSelectorModal({ | |||||
| const params = { | const params = { | ||||
| page: 0, | page: 0, | ||||
| size: 1000, | size: 1000, | ||||
| [selectorTypeConfig[type].litReqParamKey]: available_range, | |||||
| [config.litReqParamKey]: available_range, | |||||
| }; | }; | ||||
| const getListReq = selectorTypeConfig[type].getList; | |||||
| const getListReq = config.getList; | |||||
| const [res] = await to(getListReq(params)); | const [res] = await to(getListReq(params)); | ||||
| if (res) { | if (res) { | ||||
| const list = res.data?.content || []; | const list = res.data?.content || []; | ||||
| @@ -161,10 +162,10 @@ function ResourceSelectorModal({ | |||||
| // 获取数据集\模型\镜像版本列表 | // 获取数据集\模型\镜像版本列表 | ||||
| const getVersions = async (parentId: number) => { | const getVersions = async (parentId: number) => { | ||||
| const getVersionsReq = selectorTypeConfig[type].getVersions; | |||||
| const getVersionsReq = config.getVersions; | |||||
| const [res, error] = await to(getVersionsReq(parentId)); | const [res, error] = await to(getVersionsReq(parentId)); | ||||
| if (res) { | if (res) { | ||||
| const list = selectorTypeConfig[type].handleVersionResponse(res); | |||||
| const list = config.handleVersionResponse(res); | |||||
| const children = list.map(convertVersionToTreeData(parentId)); | const children = list.map(convertVersionToTreeData(parentId)); | ||||
| // 更新 treeData children | // 更新 treeData children | ||||
| setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); | setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); | ||||
| @@ -183,8 +184,8 @@ function ResourceSelectorModal({ | |||||
| // 获取版本下的文件 | // 获取版本下的文件 | ||||
| const getFiles = async (id: number, version: string) => { | const getFiles = async (id: number, version: string) => { | ||||
| const getFilesReq = selectorTypeConfig[type].getFiles; | |||||
| const paramsKey = selectorTypeConfig[type].fileReqParamKey; | |||||
| const getFilesReq = config.getFiles; | |||||
| const paramsKey = config.fileReqParamKey; | |||||
| const params = { version: version, [paramsKey]: id }; | const params = { version: version, [paramsKey]: id }; | ||||
| const [res] = await to(getFilesReq(params)); | const [res] = await to(getFilesReq(params)); | ||||
| if (res) { | if (res) { | ||||
| @@ -282,14 +283,12 @@ function ResourceSelectorModal({ | |||||
| } | } | ||||
| }; | }; | ||||
| const title = `选择${selectorTypeConfig[type].name}`; | |||||
| const palceholder = `请输入${selectorTypeConfig[type].name}名称`; | |||||
| const title = `选择${config.name}`; | |||||
| const palceholder = `请输入${config.name}名称`; | |||||
| const fileTitle = | const fileTitle = | ||||
| type === ResourceSelectorType.Mirror | |||||
| ? '已选镜像' | |||||
| : `已选${selectorTypeConfig[type].name}文件(${files.length})`; | |||||
| const tabItems = selectorTypeConfig[type].tabItems; | |||||
| const titleImg = selectorTypeConfig[type].modalIcon; | |||||
| type === ResourceSelectorType.Mirror ? '已选镜像' : `已选${config.name}文件(${files.length})`; | |||||
| const tabItems = config.tabItems; | |||||
| const titleImg = config.modalIcon; | |||||
| return ( | return ( | ||||
| <KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose> | <KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose> | ||||
| @@ -13,13 +13,12 @@ import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | |||||
| import ModelMenu from '../components/ModelMenu'; | import ModelMenu from '../components/ModelMenu'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import Props from './props'; | import Props from './props'; | ||||
| import { findAllParentNodes, findFirstDuplicate } from './utils'; | |||||
| import { findAllParentNodes } from './utils'; | |||||
| let graph = null; | let graph = null; | ||||
| const EditPipeline = () => { | const EditPipeline = () => { | ||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| let contextMenu = {}; | |||||
| const locationParams = useParams(); //新版本获取路由参数接口 | const locationParams = useParams(); //新版本获取路由参数接口 | ||||
| const graphRef = useRef(); | const graphRef = useRef(); | ||||
| const paramsDrawerRef = useRef(); | const paramsDrawerRef = useRef(); | ||||
| @@ -31,10 +30,23 @@ const EditPipeline = () => { | |||||
| let dragSourceNode; | let dragSourceNode; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| initMenu(); | |||||
| initGraph(); | |||||
| getFirstWorkflow(locationParams.id); | getFirstWorkflow(locationParams.id); | ||||
| const changeSize = () => { | |||||
| if (!graph || graph.get('destroyed')) return; | |||||
| if (!graphRef.current) return; | |||||
| graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); | |||||
| graph.fitView(); | |||||
| }; | |||||
| window.addEventListener('resize', changeSize); | |||||
| return () => { | |||||
| window.removeEventListener('resize', changeSize); | |||||
| }; | |||||
| }, []); | }, []); | ||||
| // 拖拽结束,添加新节点 | |||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| const { x, y } = val; | const { x, y } = val; | ||||
| const point = graph.getPointByClient(x, y); | const point = graph.getPointByClient(x, y); | ||||
| @@ -45,11 +57,14 @@ const EditPipeline = () => { | |||||
| y: point.y, | y: point.y, | ||||
| id: val.component_name + '-' + s8(), | id: val.component_name + '-' + s8(), | ||||
| isCluster: false, | isCluster: false, | ||||
| formError: true, | |||||
| }; | }; | ||||
| // console.log('model', model); | // console.log('model', model); | ||||
| graph.addItem('node', model, false); | graph.addItem('node', model, false); | ||||
| }; | }; | ||||
| const formChange = (val) => { | |||||
| // 节点数据发生变化 | |||||
| const handleFormChange = (val) => { | |||||
| if (graph) { | if (graph) { | ||||
| const data = graph.save(); | const data = graph.save(); | ||||
| const index = data.nodes.findIndex((item) => { | const index = data.nodes.findIndex((item) => { | ||||
| @@ -68,6 +83,8 @@ const EditPipeline = () => { | |||||
| graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y); | graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y); | ||||
| } | } | ||||
| }; | }; | ||||
| // 保存 | |||||
| const savePipeline = async (val) => { | const savePipeline = async (val) => { | ||||
| const [res, error] = await to(paramsDrawerRef.current.validateFields()); | const [res, error] = await to(paramsDrawerRef.current.validateFields()); | ||||
| if (error) { | if (error) { | ||||
| @@ -76,13 +93,6 @@ const EditPipeline = () => { | |||||
| return; | return; | ||||
| } | } | ||||
| const duplicateName = findFirstDuplicate(res.global_param || []); | |||||
| if (duplicateName) { | |||||
| message.error('全局参数配置有重复的参数名称:' + duplicateName); | |||||
| openParamsDrawer(); | |||||
| return; | |||||
| } | |||||
| // const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); | // const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); | ||||
| // if (propsError) { | // if (propsError) { | ||||
| // message.error('基本信息必填项需配置'); | // message.error('基本信息必填项需配置'); | ||||
| @@ -108,6 +118,8 @@ const EditPipeline = () => { | |||||
| }); | }); | ||||
| }, 500); | }, 500); | ||||
| }; | }; | ||||
| // 渲染数据 | |||||
| const getGraphData = (data) => { | const getGraphData = (data) => { | ||||
| if (graph) { | if (graph) { | ||||
| // 修改历史数据有蓝色边框的问题 | // 修改历史数据有蓝色边框的问题 | ||||
| @@ -122,6 +134,8 @@ const EditPipeline = () => { | |||||
| }, 500); | }, 500); | ||||
| } | } | ||||
| }; | }; | ||||
| // 处理并行边,暂时没有用 | |||||
| const processParallelEdgesOnAnchorPoint = ( | const processParallelEdgesOnAnchorPoint = ( | ||||
| edges, | edges, | ||||
| offsetDiff = 15, | offsetDiff = 15, | ||||
| @@ -242,11 +256,11 @@ const EditPipeline = () => { | |||||
| } | } | ||||
| return false; | return false; | ||||
| }; | }; | ||||
| // 复制节点 | |||||
| const cloneElement = (item) => { | const cloneElement = (item) => { | ||||
| console.log(item); | |||||
| let data = graph.save(); | let data = graph.save(); | ||||
| const nodeId = s8(); | const nodeId = s8(); | ||||
| console.log(item.getModel()); | |||||
| data.nodes.push({ | data.nodes.push({ | ||||
| ...item.getModel(), | ...item.getModel(), | ||||
| label: item.getModel().label + '-copy', | label: item.getModel().label + '-copy', | ||||
| @@ -256,66 +270,22 @@ const EditPipeline = () => { | |||||
| }); | }); | ||||
| graph.changeData(data); | graph.changeData(data); | ||||
| }; | }; | ||||
| const getFirstWorkflow = (val) => { | |||||
| getWorkflowById(val).then((ret) => { | |||||
| if (ret && ret.data) { | |||||
| setGlobalParam(ret.data.global_param || []); | |||||
| } | |||||
| if (graph && ret.data && ret.data.dag) { | |||||
| getGraphData(JSON.parse(ret.data.dag)); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| // 上下文菜单 | |||||
| const initMenu = () => { | |||||
| // const selectedNodes = this.selectedNodes; | |||||
| contextMenu = new G6.Menu({ | |||||
| getContent(evt) { | |||||
| const type = evt.item.getType(); | |||||
| const cloneDisplay = type === 'node' ? 'block' : 'none'; | |||||
| return ` | |||||
| <ul style="position: absolute; | |||||
| width: 100px; | |||||
| padding-left:0; | |||||
| display:flex; | |||||
| flex-direction: column; | |||||
| align-items:center; | |||||
| left: 0px; | |||||
| top: 0px; | |||||
| background-color: #ffffff; | |||||
| font-size: 14px; | |||||
| color: #333333; | |||||
| overflow-y: auto;"> | |||||
| <li style="padding: 10px 20px;cursor: pointer; display: ${cloneDisplay}" code="clone">复制</li> | |||||
| <li style="padding: 10px 20px;cursor: pointer;" code="delete">删除</li> | |||||
| </ul>`; | |||||
| }, | |||||
| handleMenuClick: (target, item) => { | |||||
| switch (target.getAttribute('code')) { | |||||
| case 'delete': | |||||
| graph.removeItem(item); | |||||
| break; | |||||
| case 'clone': | |||||
| cloneElement(item); | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| }, | |||||
| // offsetX and offsetY include the padding of the parent container | |||||
| // 需要加上父级容器的 padding-left 16 与自身偏移量 10 | |||||
| offsetX: 16 + 10, | |||||
| // 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10 | |||||
| offsetY: 0, | |||||
| // the types of items that allow the menu show up | |||||
| // 在哪些类型的元素上响应 | |||||
| itemTypes: ['node', 'edge'], | |||||
| }); | |||||
| initGraph(); | |||||
| // 获取流水线详情 | |||||
| const getFirstWorkflow = async (val) => { | |||||
| const [res] = await to(getWorkflowById(val)); | |||||
| if (res && res.data) { | |||||
| const { global_param, dag } = res.data; | |||||
| setGlobalParam(global_param || []); | |||||
| if (dag) { | |||||
| getGraphData(JSON.parse(dag)); | |||||
| } | |||||
| } | |||||
| }; | }; | ||||
| // 初始化图 | |||||
| const initGraph = () => { | const initGraph = () => { | ||||
| const contextMenu = initMenu(); | |||||
| G6.registerNode( | G6.registerNode( | ||||
| 'rect-node', | 'rect-node', | ||||
| { | { | ||||
| @@ -515,6 +485,7 @@ const EditPipeline = () => { | |||||
| }, | }, | ||||
| cursor: 'pointer', | cursor: 'pointer', | ||||
| lineWidth: 1, | lineWidth: 1, | ||||
| lineAppendWidth: 4, | |||||
| opacity: 1, | opacity: 1, | ||||
| stroke: '#CDD0DC', | stroke: '#CDD0DC', | ||||
| radius: 1, | radius: 1, | ||||
| @@ -527,19 +498,13 @@ const EditPipeline = () => { | |||||
| }, | }, | ||||
| }, | }, | ||||
| }, | }, | ||||
| defaultCombo: { | |||||
| type: 'rect', | |||||
| fixCollapseSize: 70, | |||||
| style: { | |||||
| fill: '#00e0ff0d', | |||||
| stroke: '#00e0ff', | |||||
| lineDash: [5, 10], | |||||
| cursor: 'pointer', | |||||
| }, | |||||
| }, | |||||
| }); | }); | ||||
| bindEvents(); | |||||
| }; | |||||
| const bindEvents = () => { | |||||
| graph.on('node:click', (e) => { | graph.on('node:click', (e) => { | ||||
| e.stopPropagation(); | |||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | if (e.target.get('name') !== 'anchor-point' && e.item) { | ||||
| // 获取所有的上游节点 | // 获取所有的上游节点 | ||||
| const parentNodes = findAllParentNodes(graph, e.item); | const parentNodes = findAllParentNodes(graph, e.item); | ||||
| @@ -558,16 +523,6 @@ const EditPipeline = () => { | |||||
| type: | type: | ||||
| targetAnchorIdx === 0 || targetAnchorIdx === 1 ? 'cubic-vertical' : 'cubic-horizontal', | targetAnchorIdx === 0 || targetAnchorIdx === 1 ? 'cubic-vertical' : 'cubic-horizontal', | ||||
| }); | }); | ||||
| // update the curveOffset for parallel edges | |||||
| // const edges = graph.save().edges; | |||||
| // processParallelEdgesOnAnchorPoint(edges); | |||||
| // graph.getEdges().forEach((edge, i) => { | |||||
| // graph.updateItem(edge, { | |||||
| // curveOffset: edges[i].curveOffset, | |||||
| // curvePosition: edges[i].curvePosition, | |||||
| // }); | |||||
| // }); | |||||
| }); | }); | ||||
| // 删除边时,修改 anchor-point 的 links 值 | // 删除边时,修改 anchor-point 的 links 值 | ||||
| graph.on('afterremoveitem', (e) => { | graph.on('afterremoveitem', (e) => { | ||||
| @@ -639,13 +594,56 @@ const EditPipeline = () => { | |||||
| graph.setItemState(e.item, 'drop', false); | graph.setItemState(e.item, 'drop', false); | ||||
| dropAnchorIdx = undefined; | dropAnchorIdx = undefined; | ||||
| }); | }); | ||||
| window.onresize = () => { | |||||
| if (!graph || graph.get('destroyed')) return; | |||||
| if (!graphRef.current) return; | |||||
| graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); | |||||
| graph.fitView(); | |||||
| }; | |||||
| }; | }; | ||||
| // 上下文菜单 | |||||
| const initMenu = () => { | |||||
| const contextMenu = new G6.Menu({ | |||||
| getContent(evt) { | |||||
| const type = evt.item.getType(); | |||||
| const cloneDisplay = type === 'node' ? 'block' : 'none'; | |||||
| return ` | |||||
| <ul style="position: absolute; | |||||
| width: 100px; | |||||
| padding-left:0; | |||||
| display:flex; | |||||
| flex-direction: column; | |||||
| align-items:center; | |||||
| left: 0px; | |||||
| top: 0px; | |||||
| background-color: #ffffff; | |||||
| font-size: 14px; | |||||
| color: #333333; | |||||
| overflow-y: auto;"> | |||||
| <li style="padding: 10px 20px;cursor: pointer; display: ${cloneDisplay}" code="clone">复制</li> | |||||
| <li style="padding: 10px 20px;cursor: pointer;" code="delete">删除</li> | |||||
| </ul>`; | |||||
| }, | |||||
| handleMenuClick: (target, item) => { | |||||
| switch (target.getAttribute('code')) { | |||||
| case 'delete': | |||||
| graph.removeItem(item); | |||||
| break; | |||||
| case 'clone': | |||||
| cloneElement(item); | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| }, | |||||
| // offsetX and offsetY include the padding of the parent container | |||||
| // 需要加上父级容器的 padding-left 16 与自身偏移量 10 | |||||
| offsetX: 16 + 10, | |||||
| // 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10 | |||||
| offsetY: 0, | |||||
| // the types of items that allow the menu show up | |||||
| // 在哪些类型的元素上响应 | |||||
| itemTypes: ['node', 'edge'], | |||||
| }); | |||||
| return contextMenu; | |||||
| }; | |||||
| return ( | return ( | ||||
| <div className={styles['pipeline-container']}> | <div className={styles['pipeline-container']}> | ||||
| <ModelMenu onComponentDragEnd={onDragEnd}></ModelMenu> | <ModelMenu onComponentDragEnd={onDragEnd}></ModelMenu> | ||||
| @@ -687,7 +685,7 @@ const EditPipeline = () => { | |||||
| </div> | </div> | ||||
| <div className={styles['pipeline-container__workflow__graph']} ref={graphRef}></div> | <div className={styles['pipeline-container__workflow__graph']} ref={graphRef}></div> | ||||
| </div> | </div> | ||||
| <Props ref={propsRef} onParentChange={formChange}></Props> | |||||
| <Props ref={propsRef} onParentChange={handleFormChange}></Props> | |||||
| <GlobalParamsDrawer | <GlobalParamsDrawer | ||||
| ref={paramsDrawerRef} | ref={paramsDrawerRef} | ||||
| open={paramsDrawerOpen} | open={paramsDrawerOpen} | ||||
| @@ -130,7 +130,6 @@ export function deleteDataset(id) { | |||||
| method: 'DELETE', | method: 'DELETE', | ||||
| }); | }); | ||||
| } | } | ||||
| // 获取模型依赖 | // 获取模型依赖 | ||||
| export function getModelAtlasReq(data) { | export function getModelAtlasReq(data) { | ||||
| return request(`/api/mmp/modelDependency/queryModelAtlas`, { | return request(`/api/mmp/modelDependency/queryModelAtlas`, { | ||||
| @@ -138,3 +137,11 @@ export function getModelAtlasReq(data) { | |||||
| data | data | ||||
| }); | }); | ||||
| } | } | ||||
| // 实验结果导出到模型 | |||||
| export function exportModelReq(data) { | |||||
| return request(`/api/mmp/models/exportModel`, { | |||||
| method: 'POST', | |||||
| data | |||||
| }); | |||||
| } | |||||
| @@ -1,16 +0,0 @@ | |||||
| import { request } from '@umijs/max'; | |||||
| // 查询开发环境url | |||||
| export function getJupyterUrl(params) { | |||||
| return request(`/api/mmp/jupyter/getURL`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 查询 labelStudio url | |||||
| export function getLabelStudioUrl(params) { | |||||
| return request(`/api/mmp/labelStudio/getURL`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| @@ -0,0 +1,59 @@ | |||||
| import { request } from '@umijs/max'; | |||||
| // 查询开发环境url | |||||
| export function getJupyterUrl(params: any) { | |||||
| return request(`/api/mmp/jupyter/getURL`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 查询 labelStudio url | |||||
| export function getLabelStudioUrl(params: any) { | |||||
| return request(`/api/mmp/labelStudio/getURL`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 创建编辑器 | |||||
| export function createEditorReq(data: any) { | |||||
| return request(`/api/mmp/devEnvironment`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 获取编辑器列表 | |||||
| export function getEditorListReq(params: any) { | |||||
| return request(`/api/mmp/devEnvironment`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 获取编辑器详情 | |||||
| export function getEditorInfoReq(id: number) { | |||||
| return request(`/api/mmp/devEnvironment/${id}`, { | |||||
| method: 'GET', | |||||
| }); | |||||
| } | |||||
| // 创建编辑器 | |||||
| export function deleteEditorReq(id: number) { | |||||
| return request(`/api/mmp/devEnvironment/${id}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||
| // 启动编辑器 | |||||
| export function startEditorReq(id: number) { | |||||
| return request(`/api/mmp/jupyter/run/${id}`, { | |||||
| method: 'POST', | |||||
| }); | |||||
| } | |||||
| // 停止编辑器 | |||||
| export function stopEditorReq(id: number) { | |||||
| return request(`/api/mmp/jupyter/stop/${id}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||