| @@ -43,7 +43,6 @@ target/ | |||
| ._* | |||
| .Spotlight-V100 | |||
| .Trashes | |||
| Icon? | |||
| ehthumbs.db | |||
| Thumbs.db | |||
| .factorypath | |||
| @@ -1,14 +1,18 @@ | |||
| // 自定义 Modal | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-15 10:01:29 | |||
| * @Description: 自定义 Modal | |||
| */ | |||
| import ModalTitle from '@/components/ModalTitle'; | |||
| import { Modal, type ModalProps } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import './index.less'; | |||
| type KFModalProps = ModalProps & { | |||
| export interface KFModalProps extends ModalProps { | |||
| image: string; | |||
| }; | |||
| function KFModal({ title, image, children, className, ...rest }: KFModalProps) { | |||
| } | |||
| function KFModal({ title, image, children, className = '', ...rest }: KFModalProps) { | |||
| return ( | |||
| <Modal | |||
| className={classNames(['kf-modal', className])} | |||
| @@ -31,10 +31,10 @@ body { | |||
| -webkit-font-smoothing: antialiased; | |||
| -moz-osx-font-smoothing: grayscale; | |||
| } | |||
| a{ | |||
| color:#1664ff; | |||
| a { | |||
| color: #1664ff; | |||
| } | |||
| .ant-btn-link{ | |||
| .ant-btn-link { | |||
| color: #1664ff; | |||
| } | |||
| .ant-pro-layout .ant-pro-layout-content { | |||
| @@ -52,9 +52,8 @@ a{ | |||
| .ant-menu-light .ant-menu-item-selected { | |||
| background: rgba(197, 232, 255, 0.8) !important; | |||
| } | |||
| .ant-pro-layout .ant-pro-sider .ant-layout-sider-children{ | |||
| background:#f2f5f7; | |||
| .ant-pro-layout .ant-pro-sider .ant-layout-sider-children { | |||
| background: #f2f5f7; | |||
| } | |||
| .ant-pro-base-menu-inline { | |||
| // height: 87vh; | |||
| @@ -64,12 +63,12 @@ a{ | |||
| .ant-pro-layout .ant-pro-layout-content { | |||
| background-color: transparent; | |||
| } | |||
| .ant-table-wrapper .ant-table-pagination.ant-pagination{ | |||
| background-color: #fff; | |||
| .ant-table-wrapper .ant-table-pagination.ant-pagination { | |||
| margin: 0; | |||
| padding: 21px 16px; | |||
| background-color: #fff; | |||
| } | |||
| .ant-table-wrapper .ant-table{ | |||
| .ant-table-wrapper .ant-table { | |||
| height: 75vh; | |||
| } | |||
| .ant-pro-global-header-logo img { | |||
| @@ -81,81 +80,80 @@ a{ | |||
| .ant-pro-layout .ant-pro-layout-container { | |||
| height: 98vh; | |||
| } | |||
| .ant-modal-confirm .ant-modal-confirm-paragraph{ | |||
| .ant-modal-confirm .ant-modal-confirm-paragraph { | |||
| margin: 40px 0 auto; | |||
| text-align: center; | |||
| } | |||
| .ant-modal-confirm-confirm .ant-modal-confirm-body>.anticon{ | |||
| .ant-modal-confirm-confirm .ant-modal-confirm-body > .anticon { | |||
| display: none; | |||
| } | |||
| .ant-modal-confirm .ant-modal-confirm-btns { | |||
| margin-top: 30px; | |||
| text-align: center; | |||
| } | |||
| .ant-modal-confirm-btns .ant-btn-default{ | |||
| width:91px; | |||
| height:42px; | |||
| background:rgba(22, 100, 255, 0.06); | |||
| border-radius:10px; | |||
| color:#1d1d20; | |||
| font-size:16px; | |||
| margin-right: 10px; | |||
| } | |||
| .ant-modal-confirm-btns .ant-btn-default:hover{ | |||
| background:rgba(22, 100, 255, 0.06); | |||
| .ant-modal-confirm-btns .ant-btn-default { | |||
| width: 91px; | |||
| height: 42px; | |||
| margin-right: 10px; | |||
| color: #1d1d20; | |||
| font-size: 16px; | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border-radius: 10px; | |||
| } | |||
| .ant-modal-confirm-btns .ant-btn-default:hover { | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border-color: transparent; | |||
| } | |||
| .ant-modal-confirm-btns .ant-btn-primary{ | |||
| width:91px; | |||
| height:42px; | |||
| background:#1664ff; | |||
| border-radius:10px; | |||
| .ant-modal-confirm-btns .ant-btn-primary { | |||
| width: 91px; | |||
| height: 42px; | |||
| font-size: 16px; | |||
| background: #1664ff; | |||
| border-radius: 10px; | |||
| } | |||
| .ant-modal .ant-modal-close-x{ | |||
| border: 2px solid #272536; | |||
| border-radius: 50%; | |||
| .ant-modal .ant-modal-close-x { | |||
| width: 26px; | |||
| height: 26px; | |||
| color: #272536; | |||
| border: 2px solid #272536; | |||
| border-radius: 50%; | |||
| } | |||
| .ant-modal-content{ | |||
| margin-left: -130px; | |||
| .ant-modal-content { | |||
| margin-top: 50px; | |||
| margin-left: -130px; | |||
| } | |||
| .ant-modal .ant-modal-content{ | |||
| .ant-modal .ant-modal-content { | |||
| padding: 0; | |||
| } | |||
| .ant-modal-confirm-body-wrapper{ | |||
| height:303px; | |||
| border-radius:21px; | |||
| background-image: url(/assets/images/modal-back.png); | |||
| background-repeat:no-repeat; | |||
| background-size:100%; | |||
| background-position: top center; | |||
| .ant-modal-confirm-body-wrapper { | |||
| height: 303px; | |||
| background-image: url(/assets/images/modal-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100%; | |||
| border-radius: 21px; | |||
| } | |||
| .ant-modal .ant-modal-close:hover { | |||
| background-color: transparent; | |||
| } | |||
| .ant-modal .ant-modal-footer >.ant-btn+.ant-btn{ | |||
| .ant-modal .ant-modal-footer > .ant-btn + .ant-btn { | |||
| margin-left: 20px; | |||
| } | |||
| .ant-pagination .ant-pagination-item-active a { | |||
| color: #fff; | |||
| background: #1664ff; | |||
| border-color: #1664ff; | |||
| border-radius:6px; | |||
| border-radius: 6px; | |||
| } | |||
| .ant-pagination .ant-pagination-item-active:hover { | |||
| color: #fff; | |||
| background: rgba(22, 100, 255, 0.8); | |||
| border-color: rgba(22, 100, 255, 0.8); | |||
| border-radius:6px; | |||
| border-radius: 6px; | |||
| } | |||
| .ant-pagination .ant-pagination-item { | |||
| border: 1px solid #e6e6e6; | |||
| border-radius:6px; | |||
| border-radius: 6px; | |||
| } | |||
| // ::-webkit-scrollbar-button { | |||
| // background: #97a1bd; | |||
| @@ -195,3 +193,7 @@ ol { | |||
| } | |||
| } | |||
| } | |||
| .local-svg { | |||
| vertical-align: -1px; | |||
| } | |||
| @@ -24,7 +24,7 @@ export function useStateRef<T>(initialValue: T) { | |||
| * @param initialValue - The initial visibility state of the modal. | |||
| * @return An array containing the visibility state and functions to open and close the modal. | |||
| */ | |||
| export function useAntdModal(initialValue: boolean) { | |||
| export function useVisible(initialValue: boolean) { | |||
| const [visible, setVisible] = useState(initialValue); | |||
| const open = useCallback(() => { | |||
| @@ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="12.572" height="12.993" viewBox="0 0 12.572 12.993" fill="currentColor"><path class="a" d="M88.236,67l-5.66-2.951a.627.627,0,0,0-.579,0l-5.655,2.906a.627.627,0,0,0-.342.558v6.009a.629.629,0,0,0,.348.563l5.658,2.82a.627.627,0,0,0,.561,0l5.657-2.82a.629.629,0,0,0,.348-.563V67.555A.626.626,0,0,0,88.236,67Zm-6,2.842-2.124-1.046,4.811-2.577L87,67.3Zm.045-5,1.742.909-4.836,2.59-1.9-.934Zm-5.447,3.276,2.007.988v2.2a.419.419,0,0,0,.838,0V69.516l2.184,1.075V75.9l-5.029-2.509ZM82.705,75.9V70.54l5.029-2.681v5.531Z" transform="translate(-76 -63.975)"/></svg> | |||
| @@ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="11.246" height="11.246" viewBox="0 0 11.246 11.246" fill="currentColor"><path class="a" d="M70.025,64a.2.2,0,0,1,.2.2V75.045a.2.2,0,0,1-.2.2h-.8a.2.2,0,0,1-.2-.2V64.2a.2.2,0,0,1,.2-.2Zm-2.008,1.2a.2.2,0,0,1,.2.2v.8a.2.2,0,0,1-.2.2H65.2v6.426h2.812a.2.2,0,0,1,.2.2v.8a.2.2,0,0,1-.2.2H64.2a.2.2,0,0,1-.2-.2V65.406a.2.2,0,0,1,.2-.2Zm6.226,7.631v1.2h-1.2v-1.2Zm-2.008,0v1.2h-1.2v-1.2Zm3.012-1.807v1.2h-1.2v-1.2Zm0-2.008v1.2h-1.2v-1.2Zm0-2.008v1.2h-1.2v-1.2ZM72.234,65.2v1.2h-1.2V65.2Zm2.008,0v1.2h-1.2V65.2Z" transform="translate(-64 -64)"/></svg> | |||
| @@ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="currentColor"><path class="a" d="M872.8,755.637h0v-.012Z" transform="translate(-848.576 -735.009)"/><path class="a" d="M122.155,109.158a13,13,0,1,0-13,13,13.015,13.015,0,0,0,13-13m-13,11.135a11.134,11.134,0,1,1,11.134-11.135,11.149,11.149,0,0,1-11.134,11.135" transform="translate(-96.155 -96.158)"/><path class="a" d="M344.559,345.281l-4.141-4.154,4.137-4.091a.957.957,0,1,0-1.346-1.36l-4.141,4.1-4.08-4.092a.957.957,0,0,0-1.355,1.351l4.075,4.087-4.107,4.062a.956.956,0,0,0,1.345,1.36l4.113-4.068,4.145,4.16a.957.957,0,0,0,1.355-1.352" transform="translate(-326.072 -328.091)"/></svg> | |||
| @@ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="12.036" height="13.5" viewBox="0 0 12.036 13.5" fill="currentColor" stroke="currentColor"><defs><style>.a{stroke-width:0.1px;}</style></defs><g transform="translate(-205.25 -176.646)"><path class="a" d="M211.268,190.1a1.261,1.261,0,0,1-.626-.167l-4.717-2.721a1.255,1.255,0,0,1-.626-1.084v-5.442a1.258,1.258,0,0,1,.626-1.084l4.717-2.721a1.271,1.271,0,0,1,1.251,0l4.717,2.721a1.255,1.255,0,0,1,.626,1.084v5.442a1.255,1.255,0,0,1-.626,1.084l-4.717,2.721A1.276,1.276,0,0,1,211.268,190.1Zm0-12.6a.478.478,0,0,0-.232.062l-4.717,2.721a.466.466,0,0,0-.232.4v5.442a.464.464,0,0,0,.232.4l4.717,2.721a.469.469,0,0,0,.463,0l4.717-2.721a.466.466,0,0,0,.232-.4V180.68a.464.464,0,0,0-.232-.4l-4.717-2.721A.465.465,0,0,0,211.268,177.5Z" transform="translate(0 0)"/><path class="a" d="M282.168,379.033a1.242,1.242,0,0,1-.616-.163l-3.722-2.1a.394.394,0,1,1,.387-.685l3.722,2.1a.461.461,0,0,0,.451,0l3.847-2.105a.394.394,0,1,1,.377.691l-3.845,2.107A1.251,1.251,0,0,1,282.168,379.033Z" transform="translate(-70.894 -195.36)"/><path class="a" d="M486.392,383.935a.392.392,0,0,1-.393-.4l.016-4.236a1.252,1.252,0,0,1,.653-1.094l3.8-2.067a.393.393,0,1,1,.375.691l-3.8,2.067a.467.467,0,0,0-.242.405l-.016,4.236A.394.394,0,0,1,486.392,383.935Z" transform="translate(-275.124 -195.422)"/><path class="a" d="M282.23,383.9a.392.392,0,0,1-.393-.391l-.016-4.236a.463.463,0,0,0-.242-.407l-3.678-2.069a.394.394,0,0,1,.385-.687l3.676,2.067a1.245,1.245,0,0,1,.647,1.092l.016,4.234a.4.4,0,0,1-.4.4Z" transform="translate(-70.962 -195.384)"/></g></svg> | |||
| @@ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="14.893" height="13.83" viewBox="0 0 14.893 13.83" fill="currentColor"><path class="a" d="M60.718,92.155a.532.532,0,0,1,.532.532v1.064h2.66a.532.532,0,0,1,0,1.064H61.25v1.064a.532.532,0,0,1-1.064,0V92.687a.532.532,0,0,1,.532-.532Zm-2.128,1.6a.532.532,0,0,1,0,1.064H50.08a.532.532,0,1,1,0-1.064h8.51Zm-5.319-6.383a.532.532,0,0,1,.532.532v3.191a.532.532,0,1,1-1.064,0V90.027H50.08a.532.532,0,1,1,0-1.064h2.66V87.9a.532.532,0,0,1,.532-.532Zm10.638,1.6a.532.532,0,0,1,0,1.064H55.4a.532.532,0,1,1,0-1.064Zm-3.191-6.383a.532.532,0,0,1,.532.532v1.064h2.66a.532.532,0,0,1,0,1.064H61.25V86.3a.532.532,0,0,1-1.064,0V83.113a.532.532,0,0,1,.532-.532Zm-2.128,1.6a.532.532,0,0,1,0,1.064H50.08a.532.532,0,1,1,0-1.064h8.51Z" transform="translate(-49.548 -82.581)"/></svg> | |||
| @@ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="13.247" height="13.247" viewBox="0 0 13.247 13.247" fill="currentColor"><g transform="translate(-370.617 -114.129)"><path class="a" d="M93.531,82H83.716A1.718,1.718,0,0,0,82,83.716v9.815a1.718,1.718,0,0,0,1.716,1.716h9.815a1.718,1.718,0,0,0,1.716-1.716V83.716A1.718,1.718,0,0,0,93.531,82Zm.792,11.531a.793.793,0,0,1-.792.792H83.716a.793.793,0,0,1-.792-.792V83.716a.793.793,0,0,1,.792-.792h9.815a.793.793,0,0,1,.792.792ZM84,86.549a.462.462,0,0,1,.462-.462h4.148a.462.462,0,1,1,0,.924H84.465A.462.462,0,0,1,84,86.549Zm9.215,0a.462.462,0,0,1-.462.462h-1.7V87.8a.462.462,0,1,1-.924,0V85.3a.462.462,0,0,1,.924,0v.787h1.7a.462.462,0,0,1,.462.462Zm0,4.31a.462.462,0,0,1-.462.462H87.54a.462.462,0,1,1,0-.924h5.215A.462.462,0,0,1,93.217,90.859ZM85.926,89.61v2.5a.462.462,0,0,1-.924,0v-.787h-.537a.462.462,0,1,1,0-.924H85V89.61a.462.462,0,0,1,.924,0Z" transform="translate(288.617 32.129)"/></g></svg> | |||
| @@ -1,5 +1,5 @@ | |||
| import { ReactComponent as ViewParam } from '@/assets/svg/view-param.svg'; | |||
| import { useAntdModal } from '@/hooks'; | |||
| import { useVisible } from '@/hooks'; | |||
| import { getExperimentIns } from '@/services/experiment/index.js'; | |||
| import { getWorkflowById } from '@/services/pipeline/index.js'; | |||
| import { elapsedTime } from '@/utils/date'; | |||
| @@ -22,7 +22,7 @@ function ExperimentText() { | |||
| const navgite = useNavigate(); | |||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||
| let graph = null; | |||
| const [paramsModalOpen, openParamsModal, closeParamsModal] = useAntdModal(false); | |||
| const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); | |||
| const timers = (time) => { | |||
| let timer = new Date(time); | |||
| @@ -143,16 +143,16 @@ function Experiment() { | |||
| const [res] = await to(getTensorBoardStatusReq(params)); | |||
| if (res && res.data) { | |||
| setExperimentInList((prevList) => { | |||
| const newList = [...prevList]; | |||
| const index = prevList.findIndex((item) => item.id === experimentIn.id); | |||
| const preObj = prevList[index]; | |||
| const newObj = { | |||
| ...preObj, | |||
| tensorBoardStatus: res.data.status, | |||
| tensorboardUrl: res.data.url, | |||
| }; | |||
| newList.splice(index, 1, newObj); | |||
| return newList; | |||
| return prevList.map((item) => { | |||
| if (item.id === experimentIn.id) { | |||
| return { | |||
| ...item, | |||
| tensorBoardStatus: res.data.status, | |||
| tensorboardUrl: res.data.url, | |||
| }; | |||
| } | |||
| return item; | |||
| }); | |||
| }); | |||
| const timerId = setTimeout(() => { | |||
| getTensorBoardStatus(experimentIn); | |||
| @@ -0,0 +1,81 @@ | |||
| @import '@/styles/theme.less'; | |||
| .model-tabs { | |||
| :global { | |||
| .ant-tabs-tab { | |||
| padding-top: 0 !important; | |||
| } | |||
| .ant-tabs-nav::before, | |||
| div > .ant-tabs-nav::before { | |||
| border: none !important; | |||
| } | |||
| } | |||
| } | |||
| .model-selector { | |||
| display: flex; | |||
| align-items: flex-start; | |||
| &__left { | |||
| width: 488px; | |||
| height: 398px; | |||
| margin-right: 15px; | |||
| padding: 15px; | |||
| background-color: @background-color-primay; | |||
| border: 1px solid @border-color; | |||
| border-radius: 8px; | |||
| &__search { | |||
| margin-bottom: 14px; | |||
| background-color: transparent; | |||
| border-width: 0; | |||
| border-bottom: 1px solid @border-color-second; | |||
| border-radius: 0; | |||
| // &:hover { | |||
| // background-color: transparent; | |||
| // } | |||
| // &:active { | |||
| // background-color: transparent; | |||
| // } | |||
| // &:focus { | |||
| // background-color: transparent; | |||
| // } | |||
| } | |||
| } | |||
| &__right { | |||
| width: calc(100% - 488px - 15px); | |||
| height: 398px; | |||
| padding: 15px; | |||
| background-color: @background-color-primay; | |||
| border: 1px solid @border-color; | |||
| border-radius: 8px; | |||
| &__title { | |||
| margin-bottom: 15px; | |||
| padding: 3px 0 6px; | |||
| color: @text-color; | |||
| font-size: @font-size; | |||
| border-bottom: 1px solid @border-color-second; | |||
| } | |||
| &__files { | |||
| height: calc(100% - 75px); | |||
| overflow-y: auto; | |||
| &__file { | |||
| height: 24px; | |||
| margin-bottom: 10px; | |||
| padding-left: 10px; | |||
| overflow: hidden; | |||
| color: #575757; | |||
| font-size: 13px; | |||
| line-height: 24px; | |||
| white-space: nowrap; | |||
| text-overflow: ellipsis; | |||
| background: @background-color-gray; | |||
| border-radius: 4px; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,396 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-11 16:31:18 | |||
| * @Description: 选择数据集和模型 | |||
| */ | |||
| import datasetImg from '@/assets/img/modal-select-dataset.png'; | |||
| import modelImg from '@/assets/img/modal-select-model.png'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { | |||
| getDatasetList, | |||
| getDatasetVersionIdList, | |||
| getDatasetVersionsById, | |||
| getModelList, | |||
| getModelVersionIdList, | |||
| getModelVersionsById, | |||
| } from '@/services/dataset/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import type { GetRef, ModalProps, TabsProps, TreeDataNode, TreeProps } from 'antd'; | |||
| import { Input, Tabs, Tree } from 'antd'; | |||
| import React, { useEffect, useRef, useState } from 'react'; | |||
| import styles from './index.less'; | |||
| export enum ResourceSelectorType { | |||
| Model = 'Model', // 模型 | |||
| Dataset = 'Dataset', // 数据集 | |||
| } | |||
| type ResourceSelectorTypeKeys = keyof typeof ResourceSelectorType; | |||
| type ResourceSelectorTypeValues = (typeof ResourceSelectorType)[ResourceSelectorTypeKeys]; | |||
| type GetModelFilesReqParam = { | |||
| model_id: number; | |||
| version: string; | |||
| }; | |||
| type GetDatasetFilesReqParam = { | |||
| dataset_id: number; | |||
| version: string; | |||
| }; | |||
| type GetFilesReqParam = GetModelFilesReqParam | GetDatasetFilesReqParam; | |||
| export type SelectorTypeInfo = { | |||
| getList: (params: { page: number; size: number; available_range: string }) => Promise<any>; | |||
| getVersions: (params: number) => Promise<any>; | |||
| getFiles: (params: GetFilesReqParam) => Promise<any>; | |||
| modalIcon: string; | |||
| buttonIcon: string; | |||
| name: string; | |||
| fileReqParamKey: 'models_id' | 'dataset_id'; | |||
| tabItems: TabsProps['items']; | |||
| }; | |||
| export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeInfo> = { | |||
| Model: { | |||
| getList: getModelList, | |||
| getVersions: getModelVersionsById, | |||
| getFiles: getModelVersionIdList, | |||
| name: '模型', | |||
| modalIcon: modelImg, | |||
| buttonIcon: 'local:model-select-button', | |||
| fileReqParamKey: 'models_id', | |||
| tabItems: [ | |||
| { | |||
| key: '0', | |||
| label: '我的模型', | |||
| }, | |||
| { | |||
| key: '1', | |||
| label: '公开模型', | |||
| }, | |||
| ], | |||
| }, | |||
| Dataset: { | |||
| getList: getDatasetList, | |||
| getVersions: getDatasetVersionsById, | |||
| getFiles: getDatasetVersionIdList, | |||
| name: '数据集', | |||
| modalIcon: datasetImg, | |||
| buttonIcon: 'local:dataset-select-button', | |||
| fileReqParamKey: 'dataset_id', | |||
| tabItems: [ | |||
| { | |||
| key: '0', | |||
| label: '我的数据集', | |||
| }, | |||
| { | |||
| key: '1', | |||
| label: '公开数据集', | |||
| }, | |||
| ], | |||
| }, | |||
| }; | |||
| type ResourceSelectorResponse = { | |||
| id: number; | |||
| name: string; | |||
| path: string; | |||
| version: string; | |||
| activeTab: string; | |||
| }; | |||
| interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||
| type: ResourceSelectorType; // 模型 | 数据集 | |||
| defaultExpandedKeys: React.Key[]; | |||
| defaultCheckedKeys: React.Key[]; | |||
| defaultActiveTab: string; | |||
| onOk?: (params: ResourceSelectorResponse | null) => void; | |||
| } | |||
| type ResourceGroup = { | |||
| id: number; | |||
| name: string; | |||
| }; | |||
| type ResourceFile = { | |||
| id: number; | |||
| file_name: string; | |||
| }; | |||
| // list 转成 treeData | |||
| const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { | |||
| return list.map((v) => ({ | |||
| title: v.name, | |||
| key: v.id, | |||
| isLeaf: false, | |||
| checkable: false, | |||
| })); | |||
| }; | |||
| const updateChildren = (parentId: number, children: TreeDataNode[]) => { | |||
| return (node: TreeDataNode) => { | |||
| if (node.key === parentId) { | |||
| return { | |||
| ...node, | |||
| children, | |||
| }; | |||
| } | |||
| return node; | |||
| }; | |||
| }; | |||
| type TreeRef = GetRef<typeof Tree<TreeDataNode>>; | |||
| function ResourceSelectorModal({ | |||
| type, | |||
| defaultExpandedKeys = [], | |||
| defaultCheckedKeys = [], | |||
| defaultActiveTab = '0', | |||
| onOk, | |||
| ...rest | |||
| }: ResourceSelectorModalProps) { | |||
| const [activeTab, setActiveTab] = useState(defaultActiveTab); | |||
| const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]); | |||
| const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]); | |||
| const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]); | |||
| const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]); | |||
| const [treeData, setTreeData] = useState<TreeDataNode[]>([]); | |||
| const [files, setFiles] = useState<ResourceFile[]>([]); | |||
| const [versionPath, setVersionPath] = useState(''); | |||
| const [searchText, setSearchText] = useState(''); | |||
| const [fisrtLoadList, setFisrtLoadList] = useState(false); | |||
| const [fisrtLoadVersions, setFisrtLoadVersions] = useState(false); | |||
| const treeRef = useRef<TreeRef>(null); | |||
| useEffect(() => { | |||
| setExpandedKeys([]); | |||
| setCheckedKeys([]); | |||
| setLoadedKeys([]); | |||
| setFiles([]); | |||
| setVersionPath(''); | |||
| setSearchText(''); | |||
| getTreeData(); | |||
| }, [activeTab, type]); | |||
| useEffect(() => { | |||
| const treeData = originTreeData.filter((v) => | |||
| (v.title as string).toLowerCase()?.includes(searchText.toLowerCase()), | |||
| ); | |||
| setTreeData(treeData); | |||
| }, [originTreeData, searchText]); | |||
| // 获取数据集或模型列表 | |||
| const getTreeData = async () => { | |||
| const params = { | |||
| page: 0, | |||
| size: 200, | |||
| available_range: activeTab, | |||
| }; | |||
| const getListReq = selectorTypeData[type].getList; | |||
| const [res] = await to(getListReq(params)); | |||
| if (res) { | |||
| const list = res.data?.content || []; | |||
| const treeData = convertToTreeData(list); | |||
| setOriginTreeData(treeData); | |||
| // 恢复上一次的 Expand 操作 | |||
| restoreLastExpand(); | |||
| } else { | |||
| setOriginTreeData([]); | |||
| } | |||
| }; | |||
| // 获取数据集或模型版本列表 | |||
| const getVersions = async (parentId: number) => { | |||
| const getVersionsReq = selectorTypeData[type].getVersions; | |||
| const [res, error] = await to(getVersionsReq(parentId)); | |||
| if (res) { | |||
| const list = res.data || []; | |||
| const children = list.map((v: string) => ({ | |||
| title: v, | |||
| key: `${parentId}-${v}`, | |||
| isLeaf: true, | |||
| checkable: true, | |||
| })); | |||
| // 更新 treeData children | |||
| setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); | |||
| // 缓存 loadedKeys | |||
| const index = loadedKeys.find((v) => v === parentId); | |||
| if (!index) { | |||
| setLoadedKeys((prev) => prev.concat(parentId)); | |||
| } | |||
| // 恢复上一次的 Check 操作 | |||
| restoreLastCheck(parentId); | |||
| } else { | |||
| setExpandedKeys([]); | |||
| return Promise.reject(error); | |||
| } | |||
| }; | |||
| // 获取版本下的文件 | |||
| const getFiles = async (id: number, version: string) => { | |||
| const getFilesReq = selectorTypeData[type].getFiles; | |||
| const paramsKey = selectorTypeData[type].fileReqParamKey; | |||
| const params = { version: version, [paramsKey]: id } as GetFilesReqParam; | |||
| const [res] = await to(getFilesReq(params)); | |||
| if (res) { | |||
| setVersionPath(res.data?.path || ''); | |||
| setFiles(res.data?.content || []); | |||
| } else { | |||
| setVersionPath(''); | |||
| setFiles([]); | |||
| } | |||
| }; | |||
| // 动态加载 tree children | |||
| const onLoadData = ({ key, children }: TreeDataNode) => { | |||
| if (children) { | |||
| return Promise.resolve(); | |||
| } else { | |||
| return getVersions(key as number); | |||
| } | |||
| }; | |||
| // 扩展 | |||
| const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => { | |||
| const lastKeys = (expandedKeysValue as React.Key[]).slice(-1); | |||
| setExpandedKeys(lastKeys); | |||
| }; | |||
| // 选中 | |||
| const onCheck: TreeProps['onCheck'] = (checkedKeysValue) => { | |||
| const lastKeys = (checkedKeysValue as React.Key[]).slice(-1); | |||
| setCheckedKeys(lastKeys); | |||
| if (lastKeys.length) { | |||
| const last = lastKeys[0] as string; | |||
| const index = last.indexOf('-'); | |||
| const id = Number(last.slice(0, index)); | |||
| const version = last.slice(index + 1); | |||
| getFiles(id, version); | |||
| } else { | |||
| setFiles([]); | |||
| } | |||
| }; | |||
| // 恢复上一次的 Expand 操作 | |||
| // 判断是否有 defaultExpandedKeys,如果有,设置 expandedKeys | |||
| // fisrtLoadList 标志位 | |||
| const restoreLastExpand = () => { | |||
| if (!fisrtLoadList && defaultExpandedKeys.length > 0) { | |||
| setTimeout(() => { | |||
| setExpandedKeys(defaultExpandedKeys); | |||
| setFisrtLoadList(true); | |||
| setTimeout(() => { | |||
| treeRef.current?.scrollTo({ key: defaultExpandedKeys[0], align: 'bottom' }); | |||
| }, 100); | |||
| }, 0); | |||
| } | |||
| }; | |||
| // 恢复上一次的 Check 操作 | |||
| // 判断是否有 defaultCheckedKeys,如果有,设置 checkedKeys,并且调用获取文件列表接口 | |||
| // fisrtLoadVersions 标志位 | |||
| const restoreLastCheck = (parentId: number) => { | |||
| if (!fisrtLoadVersions && defaultCheckedKeys.length > 0) { | |||
| const last = defaultCheckedKeys[0] as string; | |||
| const index = last.indexOf('-'); | |||
| const id = Number(last.slice(0, index)); | |||
| const version = last.slice(index + 1); | |||
| // 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致 | |||
| if (id === parentId) { | |||
| setTimeout(() => { | |||
| setCheckedKeys(defaultCheckedKeys); | |||
| getFiles(id, version); | |||
| setFisrtLoadVersions(true); | |||
| setTimeout(() => { | |||
| treeRef?.current?.scrollTo({ | |||
| key: defaultCheckedKeys[0], | |||
| align: 'bottom', | |||
| }); | |||
| }, 100); | |||
| }, 0); | |||
| } | |||
| } | |||
| }; | |||
| // 提交 | |||
| const handleOk = () => { | |||
| if (checkedKeys.length > 0) { | |||
| const last = checkedKeys[0] as string; | |||
| const index = last.indexOf('-'); | |||
| const id = Number(last.slice(0, index)); | |||
| const version = last.slice(index + 1); | |||
| const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string; | |||
| const res = { | |||
| id, | |||
| name, | |||
| path: versionPath, | |||
| version, | |||
| activeTab, | |||
| }; | |||
| onOk?.(res); | |||
| } else { | |||
| onOk?.(null); | |||
| } | |||
| }; | |||
| const title = `选择${selectorTypeData[type].name}`; | |||
| const palceholder = `请输入${selectorTypeData[type].name}名称`; | |||
| const fileTitle = `已选${selectorTypeData[type].name}文件(${files.length})`; | |||
| const tabItems = selectorTypeData[type].tabItems; | |||
| const titleImg = selectorTypeData[type].modalIcon; | |||
| return ( | |||
| <KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose> | |||
| <div className={styles}> | |||
| <Tabs | |||
| activeKey={activeTab} | |||
| items={tabItems} | |||
| onChange={setActiveTab} | |||
| className={styles['model-tabs']} | |||
| /> | |||
| <div className={styles['model-selector']}> | |||
| <div className={styles['model-selector__left']}> | |||
| <Input | |||
| className={styles['model-selector__left__search']} | |||
| placeholder={palceholder} | |||
| allowClear | |||
| variant="borderless" | |||
| value={searchText} | |||
| onChange={(e) => setSearchText(e.target.value)} | |||
| /> | |||
| <Tree | |||
| ref={treeRef} | |||
| rootStyle={{ backgroundColor: 'transparent' }} | |||
| loadData={onLoadData} | |||
| treeData={treeData} | |||
| onCheck={onCheck} | |||
| checkedKeys={checkedKeys} | |||
| multiple={false} | |||
| selectable={false} | |||
| height={324} | |||
| loadedKeys={loadedKeys} | |||
| expandedKeys={expandedKeys} | |||
| onExpand={onExpand} | |||
| checkable | |||
| /> | |||
| </div> | |||
| <div className={styles['model-selector__right']}> | |||
| <div className={styles['model-selector__right__title']}>{fileTitle}</div> | |||
| <div className={styles['model-selector__right__files']}> | |||
| {files.map((v) => ( | |||
| <div key={v.id} className={styles['model-selector__right__files__file']}> | |||
| {v.file_name} | |||
| </div> | |||
| ))} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </KFModal> | |||
| ); | |||
| } | |||
| export default ResourceSelectorModal; | |||
| @@ -51,3 +51,19 @@ | |||
| color: #ffffff; | |||
| background-color: rgba(24, 144, 255, 0.3); | |||
| } | |||
| .ref-row { | |||
| display: flex; | |||
| align-items: center; | |||
| .select-button { | |||
| display: flex; | |||
| flex: none; | |||
| align-items: center; | |||
| justify-content: flex-start; | |||
| width: 100px; | |||
| margin-left: 10px; | |||
| padding-right: 0; | |||
| padding-left: 0; | |||
| } | |||
| } | |||
| @@ -1,5 +1,5 @@ | |||
| import { ReactComponent as ParameterIcon } from '@/assets/svg/parameter.svg'; | |||
| import { useAntdModal } from '@/hooks'; | |||
| import { useVisible } from '@/hooks'; | |||
| import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import { SaveOutlined } from '@ant-design/icons'; | |||
| @@ -60,7 +60,7 @@ const EditPipeline = () => { | |||
| }); | |||
| const graphRef = useRef(); | |||
| const paramsDrawerRef = useRef(); | |||
| const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useAntdModal(false); | |||
| const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false); | |||
| const [globalParam, setGlobalParam] = useState([]); | |||
| const onDragEnd = (val) => { | |||
| @@ -97,6 +97,7 @@ const EditPipeline = () => { | |||
| const [res, error] = await to(paramsDrawerRef.current.getFieldsValue()); | |||
| if (error) { | |||
| message.error('全局参数配置有误'); | |||
| openParamsDrawer(); | |||
| return; | |||
| } | |||
| const data = graph.save(); | |||
| @@ -1,11 +1,20 @@ | |||
| import { Drawer, Form, Input } from 'antd'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { Icon } from '@umijs/max'; | |||
| import { Button, Drawer, Form, Input } from 'antd'; | |||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||
| import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | |||
| import Styles from './editPipeline.less'; | |||
| const { TextArea } = Input; | |||
| const Props = forwardRef(({ onParentChange }, ref) => { | |||
| const [form] = Form.useForm(); | |||
| const [stagingItem, setStagingItem] = useState({}); | |||
| const [open, setOpen] = useState(false); | |||
| // const [modelSelectorOpen, openModelSelector, closeModelSelector] = useVisible(false); | |||
| // const [selectorType, setSelectorType] = useState(SelectorType.Model); | |||
| // const [formItemName, setFormItemName] = useState(''); | |||
| const [selectedModel, setSelectedModel] = useState(undefined); | |||
| const [selectedDataset, setSelectedDataset] = useState(undefined); | |||
| const afterOpenChange = () => { | |||
| if (!open) { | |||
| console.log(111, open); | |||
| @@ -76,6 +85,67 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| } | |||
| }, | |||
| })); | |||
| const selectResource = (name, item) => { | |||
| // setFormItemName(name); | |||
| // setSelectorType(item.item_type === 'dataset' ? SelectorType.Dataset : SelectorType.Model); | |||
| // openModelSelector(); | |||
| const type = | |||
| item.item_type === 'dataset' ? ResourceSelectorType.Dataset : ResourceSelectorType.Model; | |||
| const resource = type === ResourceSelectorType.Dataset ? selectedDataset : selectedModel; | |||
| const { destroy } = openAntdModal( | |||
| ResourceSelectorModal, | |||
| { | |||
| type, | |||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||
| defaultActiveTab: resource?.activeTab, | |||
| onOk: (res) => { | |||
| if (res) { | |||
| const value = JSON.stringify(res); | |||
| form.setFieldValue(name, value); | |||
| if (type === ResourceSelectorType.Dataset) { | |||
| setSelectedDataset(res); | |||
| } else { | |||
| setSelectedModel(res); | |||
| } | |||
| } else { | |||
| if (type === ResourceSelectorType.Dataset) { | |||
| setSelectedDataset(null); | |||
| } else { | |||
| setSelectedModel(null); | |||
| } | |||
| form.setFieldValue(name, ''); | |||
| } | |||
| destroy(); | |||
| }, | |||
| }, | |||
| true, | |||
| ); | |||
| }; | |||
| const handleModelSelect = (obj) => { | |||
| if (obj) { | |||
| const value = JSON.stringify(obj); | |||
| setSelectedModel(obj); | |||
| form.setFieldValue(formItemName, value); | |||
| } else { | |||
| form.setFieldValue(formItemName, ''); | |||
| } | |||
| closeModelSelector(); | |||
| }; | |||
| const getSelectBtnIcon = (item) => { | |||
| const type = item.item_type; | |||
| if (type === 'dataset') { | |||
| return <Icon icon="local:dataset-select-button" className="local-svg" />; | |||
| } else if (type === 'model') { | |||
| return <Icon icon="local:model-select-button" className="local-svg" />; | |||
| } else { | |||
| return <Icon icon="local:mirror-select-button" className="local-svg" />; | |||
| } | |||
| }; | |||
| return ( | |||
| <> | |||
| <Drawer | |||
| @@ -92,10 +162,10 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| form={form} | |||
| layout="vertical" | |||
| labelCol={{ | |||
| span: 16, | |||
| span: 24, | |||
| }} | |||
| wrapperCol={{ | |||
| span: 16, | |||
| span: 24, | |||
| }} | |||
| style={{ | |||
| maxWidth: 600, | |||
| @@ -188,7 +258,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| Object.keys(stagingItem.control_strategy) && | |||
| Object.keys(stagingItem.control_strategy).length > 0 | |||
| ? Object.keys(stagingItem.control_strategy).map((item) => ( | |||
| <Form.Item label={stagingItem.control_strategy[item].label} name={item}> | |||
| <Form.Item key={item} label={stagingItem.control_strategy[item].label} name={item}> | |||
| <Input /> | |||
| </Form.Item> | |||
| )) | |||
| @@ -206,11 +276,28 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| Object.keys(stagingItem.in_parameters).length > 0 | |||
| ? Object.keys(stagingItem.in_parameters).map((item) => ( | |||
| <Form.Item | |||
| key={item} | |||
| label={stagingItem.in_parameters[item].label + '(' + item + ')'} | |||
| name={item} | |||
| rules={[{ required: stagingItem.in_parameters[item].require ? true : false }]} | |||
| > | |||
| <Input /> | |||
| <div className={Styles['ref-row']}> | |||
| <Form.Item | |||
| name={item} | |||
| noStyle | |||
| rules={[{ required: stagingItem.in_parameters[item].require ? true : false }]} | |||
| > | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.Item noStyle> | |||
| <Button | |||
| type="link" | |||
| icon={getSelectBtnIcon(stagingItem.in_parameters[item])} | |||
| onClick={() => selectResource(item, stagingItem.in_parameters[item])} | |||
| className={Styles['select-button']} | |||
| > | |||
| {stagingItem.in_parameters[item].label} | |||
| </Button> | |||
| </Form.Item> | |||
| </div> | |||
| </Form.Item> | |||
| )) | |||
| : ''} | |||
| @@ -227,6 +314,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| Object.keys(stagingItem.out_parameters).length > 0 | |||
| ? Object.keys(stagingItem.out_parameters).map((item) => ( | |||
| <Form.Item | |||
| key={item} | |||
| label={stagingItem.out_parameters[item].label + '(' + item + ')'} | |||
| rules={[{ required: stagingItem.out_parameters[item].require ? true : false }]} | |||
| name={item} | |||
| @@ -236,6 +324,18 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| )) | |||
| : ''} | |||
| </Form> | |||
| {/* {modelSelectorOpen && ( | |||
| <ResourceSelectorModal | |||
| open={modelSelectorOpen} | |||
| onCancel={closeModelSelector} | |||
| defaultExpandedKeys={selectedModel ? [selectedModel.id] : []} | |||
| defaultCheckedKeys={ | |||
| selectedModel ? [`${selectedModel.id}-${selectedModel.version}`] : [] | |||
| } | |||
| type={selectorType} | |||
| onOk={handleModelSelect} | |||
| /> | |||
| )} */} | |||
| </Drawer> | |||
| </> | |||
| ); | |||
| @@ -1,4 +1,9 @@ | |||
| import type { RequestConfig } from '@umijs/max'; | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-03-25 13:52:54 | |||
| * @Description: | |||
| */ | |||
| import type { RequestConfig } from '@umijs/max'; | |||
| import { message } from 'antd'; | |||
| import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access'; | |||
| @@ -0,0 +1,88 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-13 10:08:35 | |||
| * @Description: | |||
| */ | |||
| import { type ModalProps } from 'antd'; | |||
| import React, { useState } from 'react'; | |||
| import { createRoot } from 'react-dom/client'; | |||
| /** | |||
| * Function to open an Ant Design modal. | |||
| * | |||
| * @param modal - The function that renders the modal content. | |||
| * @param modalProps - The modal properties. | |||
| * @return An object with a destroy method to close the modal. | |||
| */ | |||
| export const openAntdModal = <T extends ModalProps>( | |||
| modal: (props: T) => React.ReactNode, | |||
| modalProps: T, | |||
| ) => { | |||
| const CustomModel = modal; | |||
| const element = document.createElement('div'); | |||
| element.id = 'modal-container'; | |||
| document.body.appendChild(element); | |||
| const root = createRoot(element); | |||
| const { afterClose, onCancel } = modalProps; | |||
| function destroy() { | |||
| root.unmount(); | |||
| document.body.removeChild(element); | |||
| } | |||
| function handleAfterClose() { | |||
| afterClose?.(); | |||
| setTimeout(() => { | |||
| destroy(); | |||
| }, 0); | |||
| } | |||
| function handleCancel(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) { | |||
| if (onCancel) { | |||
| onCancel(e); | |||
| } else { | |||
| close(); | |||
| } | |||
| } | |||
| function render(props: T) { | |||
| root.render(<CustomModel {...props} onCancel={handleCancel}></CustomModel>); | |||
| } | |||
| function close() { | |||
| render({ ...modalProps, open: false, afterClose: handleAfterClose }); | |||
| } | |||
| render({ ...modalProps, open: true }); | |||
| return { | |||
| destroy: close, | |||
| }; | |||
| }; | |||
| /** | |||
| * Generates a custom hook for managing an Ant Design modal. | |||
| * | |||
| * @param modal - The function that renders the modal content. | |||
| * @param key - The key for the modal. | |||
| * @return The modal component, open function, and close function. | |||
| */ | |||
| export const useAntdModal = <T extends ModalProps>( | |||
| modal: (props: T) => React.ReactNode, | |||
| key: React.Key, | |||
| ) => { | |||
| const [visible, setVisible] = useState(false); | |||
| const [props, setProps] = useState<T>({} as T); | |||
| const CustomModel = modal; | |||
| const open = (props: T) => { | |||
| setProps(props); | |||
| setVisible(true); | |||
| }; | |||
| const close = () => { | |||
| setVisible(false); | |||
| }; | |||
| return [<CustomModel key={key} open={visible} {...props} />, open, close] as const; | |||
| }; | |||