# Conflicts: # ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/CodeConfigServiceImpl.java # ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageServiceImpl.java # ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageVersionServiceImpl.java # ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java # ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.javadev-active_learn
| @@ -60,3 +60,8 @@ mvnw | |||
| **/node_modules | |||
| *storybook.log | |||
| /react-ui/docs | |||
| /react-ui/types/tsconfig.tsbuildinfo | |||
| /react-ui/storybook-static | |||
| /react-ui/.storybook/scripts | |||
| @@ -61,7 +61,7 @@ if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then | |||
| exit 1 | |||
| fi | |||
| valid_envs=("dev" "test") | |||
| valid_envs=("dev" "test" "test1") | |||
| if [[ ! " ${valid_envs[@]} " =~ " $env " ]]; then | |||
| echo "Invalid environment: $env" >&2 | |||
| echo "Valid environments are: ${valid_envs[*]}" | |||
| @@ -34,7 +34,7 @@ if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then | |||
| exit 1 | |||
| fi | |||
| valid_envs=("dev" "test") | |||
| valid_envs=("dev" "test" "test1") | |||
| if [[ ! " ${valid_envs[@]} " =~ " $env " ]]; then | |||
| echo "Invalid environment: $env" >&2 | |||
| echo "Valid environments are: ${valid_envs[*]}" | |||
| @@ -46,6 +46,8 @@ if [ "$env" == "dev" ]; then | |||
| remote_ip="172.20.32.197" | |||
| elif [ "$env" == "test" ]; then | |||
| remote_ip="172.20.32.185" | |||
| elif [ "$env" == "test1" ]; then | |||
| remote_ip="172.20.32.235" | |||
| else | |||
| echo "Invalid environment - $env" | |||
| exit 1 | |||
| @@ -18,7 +18,11 @@ spec: | |||
| image: ${k8s-6system-image} | |||
| ports: | |||
| - containerPort: 9201 | |||
| env: | |||
| - name: TZ | |||
| value: Asia/Shanghai | |||
| - name: JAVA_TOOL_OPTIONS | |||
| value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005" | |||
| --- | |||
| apiVersion: v1 | |||
| kind: Service | |||
| @@ -28,9 +32,15 @@ metadata: | |||
| spec: | |||
| type: NodePort | |||
| ports: | |||
| - port: 9201 | |||
| - name: http | |||
| port: 9201 | |||
| nodePort: 31207 | |||
| protocol: TCP | |||
| - name: debug | |||
| nodePort: 31220 | |||
| port: 5005 | |||
| protocol: TCP | |||
| targetPort: 5005 | |||
| selector: | |||
| app: ci4s-system | |||
| @@ -4,7 +4,6 @@ export default function(babel) { | |||
| visitor: { | |||
| ImportDeclaration(path) { | |||
| const source = path.node.source.value; | |||
| // console.log("zzzz", source); | |||
| if (source.endsWith('.less')) { | |||
| if (path.node.specifiers.length > 0) { | |||
| path.node.source.value += "?modules"; | |||
| @@ -16,7 +16,11 @@ const config: StorybookConfig = { | |||
| name: '@storybook/react-webpack5', | |||
| options: {}, | |||
| }, | |||
| staticDirs: ['../public'], | |||
| staticDirs: [ | |||
| '../public', | |||
| { from: '../docs', to: '/docs' }, | |||
| { from: '../docs/index.html', to: '/docs/index.html' }, | |||
| ], | |||
| docs: { | |||
| defaultName: 'Documentation', | |||
| }, | |||
| @@ -0,0 +1,3 @@ | |||
| # Dockerfile | |||
| FROM nginx:alpine | |||
| COPY storybook-static/ /usr/share/nginx/html | |||
| @@ -1,133 +1 @@ | |||
| Language : 🇺🇸 | [🇨🇳](./README.zh-CN.md) | [🇷🇺](./README.ru-RU.md) | [🇹🇷](./README.tr-TR.md) | [🇯🇵](./README.ja-JP.md) | [🇫🇷](./README.fr-FR.md) | [🇵🇹](./README.pt-BR.md) | [🇸🇦](./README.ar-DZ.md) | |||
| <h1 align="center">Ant Design Pro</h1> | |||
| <div align="center"> | |||
| An out-of-box UI solution for enterprise applications as a React boilerplate. | |||
| [](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)   | |||
| [](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](http://umijs.org/)  | |||
|  | |||
| </div> | |||
| - Preview: http://preview.pro.ant.design | |||
| - Home Page: http://pro.ant.design | |||
| - Documentation: http://pro.ant.design/docs/getting-started | |||
| - ChangeLog: http://pro.ant.design/docs/changelog | |||
| - FAQ: http://pro.ant.design/docs/faq | |||
| - Mirror Site in China: http://ant-design-pro.gitee.io | |||
| ## 5.0 is out! 🎉🎉🎉 | |||
| [Ant Design Pro 5.0.0](https://github.com/ant-design/ant-design-pro/issues/8656) | |||
| ## Translation Recruitment :loudspeaker: | |||
| We need your help: https://github.com/ant-design/ant-design-pro/issues/120 | |||
| ## Features | |||
| - :bulb: **TypeScript**: A language for application-scale JavaScript | |||
| - :scroll: **Blocks**: Build page with block template | |||
| - :gem: **Neat Design**: Follow [Ant Design specification](http://ant.design/) | |||
| - :triangular_ruler: **Common Templates**: Typical templates for enterprise applications | |||
| - :rocket: **State of The Art Development**: Newest development stack of React/umi/dva/antd | |||
| - :iphone: **Responsive**: Designed for variable screen sizes | |||
| - :art: **Theming**: Customizable theme with simple config | |||
| - :globe_with_meridians: **International**: Built-in i18n solution | |||
| - :gear: **Best Practices**: Solid workflow to make your code healthy | |||
| - :1234: **Mock development**: Easy to use mock development solution | |||
| - :white_check_mark: **UI Test**: Fly safely with unit and e2e tests | |||
| ## Templates | |||
| ``` | |||
| - Dashboard | |||
| - Analytic | |||
| - Monitor | |||
| - Workspace | |||
| - Form | |||
| - Basic Form | |||
| - Step Form | |||
| - Advanced From | |||
| - List | |||
| - Standard Table | |||
| - Standard List | |||
| - Card List | |||
| - Search List (Project/Applications/Article) | |||
| - Profile | |||
| - Simple Profile | |||
| - Advanced Profile | |||
| - Account | |||
| - Account Center | |||
| - Account Settings | |||
| - Result | |||
| - Success | |||
| - Failed | |||
| - Exception | |||
| - 403 | |||
| - 404 | |||
| - 500 | |||
| - User | |||
| - Login | |||
| - Register | |||
| - Register Result | |||
| ``` | |||
| ## Usage | |||
| ### Use bash | |||
| We provide pro-cli to quickly initialize scaffolding. | |||
| ```bash | |||
| # use npm | |||
| npm i @ant-design/pro-cli -g | |||
| pro create myapp | |||
| ``` | |||
| select umi version | |||
| ```shell | |||
| 🐂 Use umi@4 or umi@3 ? (Use arrow keys) | |||
| ❯ umi@4 | |||
| umi@3 | |||
| ``` | |||
| > If the umi@4 version is selected, full blocks are not yet supported. | |||
| If you choose umi@3, you can also choose the pro template. Pro is the basic template, which only provides the basic content of the framework operation. Complete contains all blocks, which is not suitable for secondary development as a basic template. | |||
| ```shell | |||
| ? 🚀 Full or a simple scaffold? (Use arrow keys) | |||
| ❯ simple | |||
| complete | |||
| ``` | |||
| Install dependencies: | |||
| ```shell | |||
| $ cd myapp && tyarn | |||
| // or | |||
| $ cd myapp && npm install | |||
| ``` | |||
| ## Browsers support | |||
| Modern browsers. | |||
| | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | | |||
| | --- | --- | --- | --- | --- | | |||
| | Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions | | |||
| ## Contributing | |||
| Any type of contribution is welcome, here are some examples of how you may contribute to this project: | |||
| - Use Ant Design Pro in your daily work. | |||
| - Submit [issues](http://github.com/ant-design/ant-design-pro/issues) to report bugs or ask questions. | |||
| - Propose [pull requests](http://github.com/ant-design/ant-design-pro/pulls) to improve our code. | |||
| # Documentation | |||
| @@ -143,6 +143,11 @@ export default [ | |||
| path: 'compare', | |||
| component: './Experiment/Comparison/index', | |||
| }, | |||
| { | |||
| name: '实验可视化对比', | |||
| path: 'compare-visual', | |||
| component: './Experiment/Aim/index', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| @@ -271,7 +276,18 @@ export default [ | |||
| { | |||
| name: '镜像详情', | |||
| path: 'info/:id', | |||
| component: './Mirror/Info', | |||
| routes: [ | |||
| { | |||
| name: '镜像详情', | |||
| path: '', | |||
| component: './Mirror/Info', | |||
| }, | |||
| { | |||
| name: '新增镜像版本', | |||
| path: 'add-version', | |||
| component: './Mirror/Create', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: '创建镜像', | |||
| @@ -345,7 +361,6 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: '应用开发', | |||
| path: '/appsDeployment', | |||
| @@ -498,6 +513,18 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: '算力积分', | |||
| path: '/points', | |||
| routes: [ | |||
| { | |||
| name: '算力积分', | |||
| path: '', | |||
| key: 'points', | |||
| component: './Points/index', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| path: '*', | |||
| layout: false, | |||
| @@ -1,8 +1,8 @@ | |||
| { | |||
| "name": "ant-design-pro", | |||
| "version": "6.0.0", | |||
| "name": "cl-model", | |||
| "version": "1.0.0", | |||
| "private": true, | |||
| "description": "An out-of-box UI solution for enterprise applications", | |||
| "description": "", | |||
| "scripts": { | |||
| "analyze": "cross-env ANALYZE=1 max build", | |||
| "build": "max build", | |||
| @@ -16,6 +16,7 @@ | |||
| "docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up", | |||
| "docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro", | |||
| "docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro", | |||
| "docs": "typedoc", | |||
| "gh-pages": "gh-pages -d dist", | |||
| "i18n-remove": "pro i18n-remove --locale=zh-CN --write", | |||
| "postinstall": "max setup", | |||
| @@ -39,6 +40,7 @@ | |||
| "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", | |||
| "storybook": "storybook dev -p 6006", | |||
| "storybook-build": "storybook build", | |||
| "storybook-deploy": "./.storybook/scripts/upload-deploy.sh", | |||
| "storybook-docs": "storybook dev --docs", | |||
| "storybook-docs-build": "storybook build --docs", | |||
| "test": "jest", | |||
| @@ -66,6 +68,7 @@ | |||
| "@types/crypto-js": "^4.2.2", | |||
| "@umijs/route-utils": "^4.0.1", | |||
| "antd": "~5.21.4", | |||
| "caniuse-lite": "~1.0.30001707", | |||
| "classnames": "^2.3.2", | |||
| "crypto-js": "^4.2.0", | |||
| "echarts": "^5.5.0", | |||
| @@ -132,6 +135,7 @@ | |||
| "swagger-ui-dist": "^4.18.2", | |||
| "ts-loader": "~9.5.2", | |||
| "ts-node": "^10.9.1", | |||
| "typedoc": "~0.28.1", | |||
| "typescript": "^5.0.4", | |||
| "umi-presets-pro": "^2.0.0" | |||
| }, | |||
| @@ -1,5 +1,13 @@ | |||
| @font-face { | |||
| font-family: Alibaba; | |||
| src: url('./ALIBABA-PUHUITI-MEDIUM.TTF'); | |||
| font-display: swap; | |||
| } | |||
| font-family: Alibaba; | |||
| src: url('./ALIBABA-PUHUITI-MEDIUM.TTF'); | |||
| font-display: swap; | |||
| } | |||
| @font-face { | |||
| font-family: 'DingTalk-JinBuTi'; | |||
| src: url('./DingTalk-JinBuTi.woff2') format('woff2'), /* 最优先使用 woff2 */ | |||
| url('./DingTalk-JinBuTi.woff') format('woff'), /* 兼容性较好的 woff */ | |||
| url('./DingTalk-JinBuTi.ttf') format('truetype'); /* ttf 作为备选 */ | |||
| font-display: swap; /* 优化页面加载时的字体显示 */ | |||
| } | |||
| @@ -1,13 +1,16 @@ | |||
| import RightContent from '@/components/RightContent'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { type GlobalInitialState } from '@/types'; | |||
| import { menuItemRender } from '@/utils/menuRender'; | |||
| import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | |||
| import { RuntimeConfig, history } from '@umijs/max'; | |||
| import { RuntimeAntdConfig } from 'umi'; | |||
| import defaultSettings from '../config/defaultSettings'; | |||
| import '../public/fonts/font.css'; | |||
| import { getAccessToken } from './access'; | |||
| import ErrorBoundary from './components/ErrorBoundary'; | |||
| import './dayjsConfig'; | |||
| import { removeAllPageCacheState } from './hooks/pageCacheState'; | |||
| import { removeAllPageCacheState } from './hooks/useCacheState'; | |||
| import { | |||
| getRemoteMenu, | |||
| getRoutersInfo, | |||
| @@ -16,14 +19,9 @@ import { | |||
| setRemoteMenu, | |||
| } from './services/session'; | |||
| import './styles/menu.less'; | |||
| export { requestConfig as request } from './requestConfig'; | |||
| // const isDev = process.env.NODE_ENV === 'development'; | |||
| import { type GlobalInitialState } from '@/types'; | |||
| // import '@/utils/clipboard'; | |||
| import { menuItemRender } from '@/utils/menuRender'; | |||
| import ErrorBoundary from './components/ErrorBoundary'; | |||
| import { needAuth } from './utils'; | |||
| import { gotoLoginPage } from './utils/ui'; | |||
| export { requestConfig as request } from './requestConfig'; | |||
| /** | |||
| * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | |||
| @@ -139,7 +137,6 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| onClick: () => { | |||
| // 点击菜单项,删除所有的页面 state 缓存 | |||
| removeAllPageCacheState(); | |||
| // console.log('click menu'); | |||
| }, | |||
| }, | |||
| ...initialState?.settings, | |||
| @@ -2,6 +2,7 @@ import { AvailableRange } from '@/enums'; | |||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | |||
| import { Flex, Typography } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useState } from 'react'; | |||
| import styles from './index.less'; | |||
| type CodeConfigItemProps = { | |||
| @@ -10,6 +11,7 @@ type CodeConfigItemProps = { | |||
| }; | |||
| function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | |||
| const [isEllipsis, setIsEllipsis] = useState(false); | |||
| return ( | |||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | |||
| <Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}> | |||
| @@ -32,11 +34,20 @@ function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | |||
| </Flex> | |||
| <Typography.Paragraph | |||
| className={styles['code-config-item__url']} | |||
| ellipsis={{ rows: 2, tooltip: item.git_url }} | |||
| ellipsis={{ | |||
| rows: 2, | |||
| tooltip: isEllipsis ? item.git_url : false, // 仅当省略时显示 tooltip | |||
| onEllipsis: (ellipsis) => setIsEllipsis(ellipsis), | |||
| }} | |||
| > | |||
| {item.git_url} | |||
| </Typography.Paragraph> | |||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||
| <Typography.Paragraph | |||
| className={styles['code-config-item__branch']} | |||
| ellipsis={{ tooltip: item.git_branch }} | |||
| > | |||
| {item.git_branch} | |||
| </Typography.Paragraph> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { Tooltip } from 'antd'; | |||
| import styles from './index.less'; | |||
| export type CopyingTextProps = { | |||
| text: string; | |||
| }; | |||
| function CopyingText({ text }: CopyingTextProps) { | |||
| return ( | |||
| <div className={styles['copying-text']}> | |||
| <span className={styles['copying-text__text']}>{text}</span> | |||
| <Tooltip title="复制"> | |||
| <KFIcon | |||
| id="copying" | |||
| data-clipboard-text={text} | |||
| type="icon-fuzhi2" | |||
| className={styles['copying-text__icon']} | |||
| color="#606b7a" | |||
| /> | |||
| </Tooltip> | |||
| </div> | |||
| ); | |||
| } | |||
| export default CopyingText; | |||
| @@ -3,6 +3,7 @@ import KFSpin from '@/components/KFSpin'; | |||
| import { getLabelStudioUrl } from '@/services/developmentEnvironment'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import { FloatButton } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { createPortal } from 'react-dom'; | |||
| @@ -13,6 +14,7 @@ export enum IframePageType { | |||
| AppDevelopment = 'AppDevelopment', // 应用开发 | |||
| DevEnv = 'DevEnv', // 开发环境 | |||
| GitLink = 'GitLink', // git link | |||
| Aim = 'Aim', // 实验对比 | |||
| } | |||
| const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||
| @@ -29,12 +31,20 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||
| }); | |||
| case IframePageType.GitLink: // git link | |||
| return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' }); | |||
| case IframePageType.Aim: // Aim | |||
| return () => | |||
| Promise.resolve({ | |||
| code: 200, | |||
| data: SessionStorage.getItem(SessionStorage.aimUrlKey) || '', | |||
| }); | |||
| } | |||
| }; | |||
| type IframePageProps = { | |||
| /** 子系统 */ | |||
| type: IframePageType; | |||
| /** 是否可以在页签上打开 */ | |||
| openInTab?: boolean; | |||
| /** 自定义样式类名 */ | |||
| className?: string; | |||
| /** 自定义样式 */ | |||
| @@ -42,18 +52,15 @@ type IframePageProps = { | |||
| }; | |||
| /** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */ | |||
| function IframePage({ type, className, style }: IframePageProps) { | |||
| function IframePage({ type, openInTab = false, className, style }: IframePageProps) { | |||
| const [iframeUrl, setIframeUrl] = useState(''); | |||
| const [loading, setLoading] = useState(false); | |||
| useEffect(() => { | |||
| const requestIframeUrl = async () => { | |||
| setLoading(true); | |||
| const [res] = await to(getRequestAPI(type)()); | |||
| if (res && res.data) { | |||
| setIframeUrl(res.data); | |||
| } else { | |||
| setLoading(false); | |||
| } | |||
| }; | |||
| @@ -68,6 +75,7 @@ function IframePage({ type, className, style }: IframePageProps) { | |||
| <div className={classNames('kf-iframe-page', className)} style={style}> | |||
| {loading && createPortal(<KFSpin size="large" />, document.body)} | |||
| <FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} /> | |||
| {openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />} | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -22,7 +22,7 @@ | |||
| border-radius: 4px; | |||
| &__value { | |||
| .singleLine(); | |||
| //.singleLine(); | |||
| margin-right: 8px; | |||
| font-size: @font-size-input; | |||
| line-height: 1.5714285714285714; | |||
| @@ -6,7 +6,7 @@ | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { CloseOutlined } from '@ant-design/icons'; | |||
| import { ConfigProvider, Form, Input } from 'antd'; | |||
| import { ConfigProvider, Form, Input, Typography } from 'antd'; | |||
| import { RuleObject } from 'antd/es/form'; | |||
| import classNames from 'classnames'; | |||
| import './index.less'; | |||
| @@ -120,7 +120,12 @@ function ParameterInput({ | |||
| > | |||
| {valueObj?.showValue ? ( | |||
| <div className="parameter-input__content"> | |||
| <span className="parameter-input__content__value">{valueObj?.showValue}</span> | |||
| <Typography.Text | |||
| className="parameter-input__content__value" | |||
| ellipsis={{ tooltip: valueObj.showValue }} | |||
| > | |||
| {valueObj.showValue} | |||
| </Typography.Text> | |||
| <CloseOutlined | |||
| className="parameter-input__content__close-icon" | |||
| onClick={handleRemove} | |||
| @@ -1,4 +1,4 @@ | |||
| import { filterResourceStandard, resourceFieldNames } from '@/hooks/resource'; | |||
| import { filterResourceStandard, resourceFieldNames } from '@/hooks/useComputingResource'; | |||
| import { ServiceData } from '@/pages/ModelDeployment/types'; | |||
| import { getDatasetList, getModelList } from '@/services/dataset/index.js'; | |||
| import { getServiceListReq } from '@/services/modelDeployment'; | |||
| @@ -4,7 +4,7 @@ | |||
| * @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务 | |||
| */ | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Select, type SelectProps } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| @@ -16,13 +16,17 @@ export type ParameterSelectObject = { | |||
| [key: string]: any; | |||
| }; | |||
| export type ParameterSelectDataType = 'dataset' | 'model' | 'service' | 'resource'; | |||
| export interface ParameterSelectProps extends SelectProps { | |||
| /** 类型 */ | |||
| dataType: 'dataset' | 'model' | 'service' | 'resource'; | |||
| dataType: ParameterSelectDataType; | |||
| /** 是否只是展示信息 */ | |||
| display?: boolean; | |||
| /** 值,支持对象,对象必须包含 value */ | |||
| value?: string | ParameterSelectObject; | |||
| /** 用于流水线, 流水线资源规格要求 id 为字符串 */ | |||
| isPipeline?: boolean; | |||
| /** 修改后回调 */ | |||
| onChange?: (value: string | ParameterSelectObject) => void; | |||
| } | |||
| @@ -32,6 +36,7 @@ function ParameterSelect({ | |||
| dataType, | |||
| display = false, | |||
| value, | |||
| isPipeline = false, | |||
| onChange, | |||
| ...rest | |||
| }: ParameterSelectProps) { | |||
| @@ -39,6 +44,12 @@ function ParameterSelect({ | |||
| const propsConfig = paramSelectConfig[dataType]; | |||
| const valueText = typeof value === 'object' && value !== null ? value.value : value; | |||
| const [resourceStandardList] = useComputingResource(); | |||
| const computingResource = isPipeline | |||
| ? resourceStandardList.map((v) => ({ | |||
| ...v, | |||
| id: String(v.id), | |||
| })) | |||
| : resourceStandardList; | |||
| useEffect(() => { | |||
| // 获取下拉数据 | |||
| @@ -56,7 +67,7 @@ function ParameterSelect({ | |||
| getSelectOptions(); | |||
| }, [propsConfig]); | |||
| const selectOptions = dataType === 'resource' ? resourceStandardList : options; | |||
| const selectOptions = dataType === 'resource' ? computingResource : options; | |||
| const handleChange = (text: string) => { | |||
| if (typeof value === 'object' && value !== null) { | |||
| @@ -6,7 +6,7 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import ResourceSelectorModal, { | |||
| ResourceSelectorResponse, | |||
| type ResourceSelectorResponse, | |||
| ResourceSelectorType, | |||
| selectorTypeConfig, | |||
| } from '@/components/ResourceSelectorModal'; | |||
| @@ -224,6 +224,8 @@ export class MirrorSelector implements SelectorTypeInfo { | |||
| image_id: parentKey, | |||
| page: 0, | |||
| size: 2000, | |||
| status: 'available', | |||
| state: 1, | |||
| }); | |||
| if (res && res.data) { | |||
| const list = res.data.content || []; | |||
| @@ -3,7 +3,7 @@ import { setRemoteMenu } from '@/services/session'; | |||
| import { logout } from '@/services/system/auth'; | |||
| import { ClientInfo } from '@/types'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import { gotoLoginPage } from '@/utils/ui'; | |||
| import { gotoLoginPage, oauthLogout } from '@/utils/ui'; | |||
| import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; | |||
| import { setAlpha } from '@ant-design/pro-components'; | |||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||
| @@ -62,6 +62,7 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||
| * 退出登录,并且将当前的 url 保存 | |||
| */ | |||
| const loginOut = async () => { | |||
| oauthLogout('http://172.20.32.197:31209/oauth/logout'); | |||
| await logout(); | |||
| clearSessionToken(); | |||
| setRemoteMenu(null); | |||
| @@ -160,3 +160,8 @@ ol { | |||
| input:-webkit-autofill { | |||
| transition: background-color 5000s ease-in-out 0s; | |||
| } | |||
| .ant-typography { | |||
| color: inherit; | |||
| font-size: inherit; | |||
| } | |||
| @@ -1,202 +0,0 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-15 10:01:29 | |||
| * @Description: 自定义 hooks | |||
| */ | |||
| import { FormInstance } from 'antd'; | |||
| import { debounce } from 'lodash'; | |||
| import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; | |||
| /** | |||
| * 生成具有初始值的状态引用 | |||
| * | |||
| * @param initialValue - 状态的初始值 | |||
| * @return 包含状态值、状态设置函数和可变引用对象的数组 | |||
| */ | |||
| export function useStateRef<T>(initialValue: T) { | |||
| const [value, setValue] = useState(initialValue); | |||
| const ref = useRef(value); | |||
| useEffect(() => { | |||
| ref.current = value; | |||
| }, [value]); | |||
| return [value, setValue, ref] as const; | |||
| } | |||
| /** | |||
| * 生成一个自定义钩子,用于管理模态框的可见性状态。 | |||
| * | |||
| * @param initialValue - 模态框的初始可见性状态。 | |||
| * @return 一个数组,包含可见性状态和打开和关闭模态框的函数。 | |||
| */ | |||
| export function useVisible(initialValue: boolean) { | |||
| const [visible, setVisible] = useState(initialValue); | |||
| const ref = useRef(initialValue); | |||
| const open = useCallback(() => { | |||
| setVisible(true); | |||
| }, []); | |||
| const close = useCallback(() => { | |||
| setVisible(false); | |||
| }, []); | |||
| useEffect(() => { | |||
| ref.current = visible; | |||
| }, [visible]); | |||
| return [visible, open, close, ref] as const; | |||
| } | |||
| type Callback<T> = (state: T) => void; | |||
| /** | |||
| * 生成一个具有回调机制的可变状态值和更新它的函数。 | |||
| * | |||
| * @param initialValue - 初始状态值。 | |||
| * @return 一个元组,包含当前状态值和用于更新状态的函数。 | |||
| */ | |||
| export function useCallbackState<T>(initialValue: T) { | |||
| const [state, _setState] = useState<T>(initialValue); | |||
| const callbackQueue = useRef<Callback<T>[]>([]); | |||
| useEffect(() => { | |||
| callbackQueue.current.forEach((cb) => cb(state)); | |||
| callbackQueue.current = []; | |||
| }, [state]); | |||
| const setState = (newValue: T | ((prevState: T) => T), callback?: Callback<T>) => { | |||
| _setState(newValue); | |||
| if (callback && typeof callback === 'function') { | |||
| callbackQueue.current.push(callback); | |||
| } | |||
| }; | |||
| return [state, setState] as const; | |||
| } | |||
| /** | |||
| * 用于追踪 DOM 元素尺寸的 hook。 | |||
| * | |||
| * @param initialWidth - 初始宽度。 | |||
| * @param initialHeight - 初始高度。 | |||
| * @param deps - 依赖列表。 | |||
| * @return 一个元组,包含 DOM 元素的 ref、当前宽度和当前高度。 | |||
| */ | |||
| export function useDomSize<T extends HTMLElement>( | |||
| initialWidth: number, | |||
| initialHeight: number, | |||
| deps: React.DependencyList = [], | |||
| ) { | |||
| const domRef = useRef<T>(null); | |||
| const [width, setWidth] = useState(initialWidth); | |||
| const [height, setHeight] = useState(initialHeight); | |||
| useEffect(() => { | |||
| const setDomHeight = () => { | |||
| if (domRef.current) { | |||
| setHeight(domRef.current.offsetHeight); | |||
| setWidth(domRef.current.offsetWidth); | |||
| } | |||
| }; | |||
| const debounceFunc = debounce(setDomHeight, 100); | |||
| setDomHeight(); | |||
| window.addEventListener('resize', debounceFunc); | |||
| return () => { | |||
| window.removeEventListener('resize', debounceFunc); | |||
| }; | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| }, [domRef, ...deps]); | |||
| return [domRef, { width, height }] as const; | |||
| } | |||
| /** | |||
| * 用于在 modal 关闭时重置 Form 表单的 hook。 | |||
| * | |||
| * @param form - Ant Design Form 表单实例 | |||
| * @param open - modal 是否打开 | |||
| */ | |||
| export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => { | |||
| const prevOpenRef = useRef<boolean>(); | |||
| useEffect(() => { | |||
| prevOpenRef.current = open; | |||
| }, [open]); | |||
| const prevOpen = prevOpenRef.current; | |||
| useEffect(() => { | |||
| if (!open && prevOpen) { | |||
| form.resetFields(); | |||
| } | |||
| }, [form, prevOpen, open]); | |||
| }; | |||
| /** | |||
| * Executes the effect function when the specified condition is true. | |||
| * | |||
| * @param effect - The effect function to execute. | |||
| * @param when - The condition to trigger the effect. | |||
| * @param deps - The dependencies for the effect. | |||
| */ | |||
| export const useEffectWhen = (effect: () => void, when: boolean, deps: React.DependencyList) => { | |||
| const requestFns = useRef<(() => void)[]>([]); | |||
| useEffect(() => { | |||
| if (when) { | |||
| effect(); | |||
| } else { | |||
| requestFns.current.splice(0, 1, effect); | |||
| } | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| }, deps); | |||
| useEffect(() => { | |||
| if (when) { | |||
| const fn = requestFns.current.pop(); | |||
| fn?.(); | |||
| } | |||
| }, [when]); | |||
| }; | |||
| // 选择、全选操作 | |||
| export const useCheck = <T>(list: T[]) => { | |||
| const [selected, setSelected] = useState<T[]>([]); | |||
| const checked = useMemo(() => { | |||
| return selected.length === list.length && selected.length > 0; | |||
| }, [selected, list]); | |||
| const indeterminate = useMemo(() => { | |||
| return selected.length > 0 && selected.length < list.length; | |||
| }, [selected, list]); | |||
| const checkAll = useCallback(() => { | |||
| setSelected(checked ? [] : list); | |||
| }, [list, checked]); | |||
| const isSingleChecked = useCallback((item: T) => selected.includes(item), [selected]); | |||
| const checkSingle = useCallback( | |||
| (item: T) => { | |||
| setSelected((prev) => { | |||
| if (isSingleChecked(item)) { | |||
| return prev.filter((i) => i !== item); | |||
| } else { | |||
| return [...prev, item]; | |||
| } | |||
| }); | |||
| }, | |||
| [isSingleChecked], | |||
| ); | |||
| return [ | |||
| selected, | |||
| setSelected, | |||
| checked, | |||
| indeterminate, | |||
| checkAll, | |||
| isSingleChecked, | |||
| checkSingle, | |||
| ] as const; | |||
| }; | |||
| @@ -29,13 +29,18 @@ const removeCacheState = (key: string) => { | |||
| } | |||
| }; | |||
| // 移除所有页面 state 缓存 | |||
| /** | |||
| * 移除所有页面 state 缓存 | |||
| */ | |||
| export const removeAllPageCacheState = () => { | |||
| pageKeys.forEach((key) => { | |||
| sessionStorage.removeItem(key); | |||
| }); | |||
| }; | |||
| /** | |||
| * 缓存页面数据 | |||
| */ | |||
| export const useCacheState = () => { | |||
| const { pathname } = window.location; | |||
| const key = 'pagecache:' + pathname; | |||
| @@ -0,0 +1,25 @@ | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| type Callback<T> = (state: T) => void; | |||
| /** | |||
| * 生成一个具有回调机制的可变状态值和更新它的函数。谨慎使用 | |||
| * | |||
| * @param initialValue - 初始状态值。 | |||
| * @return 一个元组,包含当前状态值和用于更新状态的函数。 | |||
| */ | |||
| export function useCallbackState<T>(initialValue: T) { | |||
| const [state, _setState] = useState<T>(initialValue); | |||
| const callbackQueue = useRef<Callback<T>[]>([]); | |||
| useEffect(() => { | |||
| callbackQueue.current.forEach((cb) => cb(state)); | |||
| callbackQueue.current = []; | |||
| }, [state]); | |||
| const setState = (newValue: T | ((prevState: T) => T), callback?: Callback<T>) => { | |||
| _setState(newValue); | |||
| if (callback && typeof callback === 'function') { | |||
| callbackQueue.current.push(callback); | |||
| } | |||
| }; | |||
| return [state, setState] as const; | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| import { useCallback, useMemo, useState } from 'react'; | |||
| /** | |||
| * 选择、全选操作 | |||
| * @param list - 需要进行选择的列表 | |||
| * @return [选中的项, 设置选中的方法, 是否全选, 是否部分选中, 全选方法,是否单个选中,选中单个方法] | |||
| */ | |||
| export const useCheck = <T>(list: T[]) => { | |||
| const [selected, setSelected] = useState<T[]>([]); | |||
| const checked = useMemo(() => { | |||
| return selected.length === list.length && selected.length > 0; | |||
| }, [selected, list]); | |||
| const indeterminate = useMemo(() => { | |||
| return selected.length > 0 && selected.length < list.length; | |||
| }, [selected, list]); | |||
| const checkAll = useCallback(() => { | |||
| setSelected(checked ? [] : list); | |||
| }, [list, checked]); | |||
| const isSingleChecked = useCallback((item: T) => selected.includes(item), [selected]); | |||
| const checkSingle = useCallback( | |||
| (item: T) => { | |||
| setSelected((prev) => { | |||
| if (isSingleChecked(item)) { | |||
| return prev.filter((i) => i !== item); | |||
| } else { | |||
| return [...prev, item]; | |||
| } | |||
| }); | |||
| }, | |||
| [isSingleChecked], | |||
| ); | |||
| return [ | |||
| selected, | |||
| setSelected, | |||
| checked, | |||
| indeterminate, | |||
| checkAll, | |||
| isSingleChecked, | |||
| checkSingle, | |||
| ] as const; | |||
| }; | |||
| @@ -12,7 +12,7 @@ import { useCallback, useEffect, useState } from 'react'; | |||
| const computingResource: ComputingResource[] = []; | |||
| // 过滤资源规格 | |||
| /** 过滤资源规格 */ | |||
| export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | |||
| input: string, | |||
| option?: ComputingResource, | |||
| @@ -22,13 +22,13 @@ export const filterResourceStandard: SelectProps<string, ComputingResource>['fil | |||
| ); | |||
| }; | |||
| // 资源规格字段 | |||
| /** 资源规格字段 */ | |||
| export const resourceFieldNames = { | |||
| label: 'description', | |||
| value: 'id', | |||
| }; | |||
| // 获取资源规格 | |||
| /** 获取资源规格 */ | |||
| export function useComputingResource() { | |||
| const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | |||
| @@ -0,0 +1,40 @@ | |||
| import { debounce } from 'lodash'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| /** | |||
| * 用于追踪 DOM 元素尺寸的 hook。 | |||
| * | |||
| * @param initialWidth - 初始宽度。 | |||
| * @param initialHeight - 初始高度。 | |||
| * @param deps - 依赖列表。 | |||
| * @return 一个元组,包含 DOM 元素的 ref、当前宽度和当前高度。 | |||
| */ | |||
| export function useDomSize<T extends HTMLElement>( | |||
| initialWidth: number, | |||
| initialHeight: number, | |||
| deps: React.DependencyList = [], | |||
| ) { | |||
| const domRef = useRef<T>(null); | |||
| const [width, setWidth] = useState(initialWidth); | |||
| const [height, setHeight] = useState(initialHeight); | |||
| useEffect(() => { | |||
| const setDomHeight = () => { | |||
| if (domRef.current) { | |||
| setHeight(domRef.current.offsetHeight); | |||
| setWidth(domRef.current.offsetWidth); | |||
| } | |||
| }; | |||
| const debounceFunc = debounce(setDomHeight, 100); | |||
| setDomHeight(); | |||
| window.addEventListener('resize', debounceFunc); | |||
| return () => { | |||
| window.removeEventListener('resize', debounceFunc); | |||
| }; | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| }, deps); | |||
| return [domRef, { width, height }] as const; | |||
| } | |||
| @@ -1,6 +1,8 @@ | |||
| // 处理 react-draggable 组件拖动结束时,响应了点击事件的 | |||
| import { useState } from 'react'; | |||
| /** | |||
| * 处理 react-draggable 组件拖动结束时,响应了点击事件的 | |||
| */ | |||
| export const useDraggable = (onClick: () => void) => { | |||
| const [isDragging, setIsDragging] = useState(false); | |||
| @@ -0,0 +1,24 @@ | |||
| import { useEffect, useRef } from 'react'; | |||
| /** | |||
| * 当指定的条件为真时执行 Effect 函数。 | |||
| * | |||
| * @param effect - The effect function to execute. | |||
| * @param when - The condition to trigger the effect. | |||
| * @param deps - The dependencies for the effect. | |||
| */ | |||
| export const useEffectWhen = (effect: () => void, when: boolean, deps: React.DependencyList) => { | |||
| const requestFn = useRef<(() => void) | undefined>(effect); | |||
| useEffect(() => { | |||
| requestFn.current = effect; | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| }, [...deps, effect]); | |||
| useEffect(() => { | |||
| if (when && requestFn.current) { | |||
| requestFn.current(); | |||
| requestFn.current = undefined; | |||
| } | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| }, [...deps, when]); | |||
| }; | |||
| @@ -0,0 +1,24 @@ | |||
| import { FormInstance } from 'antd'; | |||
| import { useEffect, useRef } from 'react'; | |||
| /** | |||
| * 用于在 modal 关闭时重置 Form 表单的 hook。 | |||
| * | |||
| * @param form - Ant Design Form 表单实例 | |||
| * @param open - modal 是否打开 | |||
| */ | |||
| export const useResetForm = (form: FormInstance, open: boolean) => { | |||
| const prevOpenRef = useRef<boolean>(); | |||
| useEffect(() => { | |||
| prevOpenRef.current = open; | |||
| }, [open]); | |||
| const prevOpen = prevOpenRef.current; | |||
| useEffect(() => { | |||
| if (!open && prevOpen) { | |||
| form.resetFields(); | |||
| } | |||
| }, [form, prevOpen, open]); | |||
| }; | |||
| @@ -0,0 +1,46 @@ | |||
| import { parseJsonText } from '@/utils'; | |||
| import { useCallback, useRef } from 'react'; | |||
| export const useSSE = (onMessage: (data: any) => void) => { | |||
| const evtSourceRef = useRef<EventSource | null>(null); | |||
| const setupSSE = useCallback( | |||
| (name: string, namespace: string) => { | |||
| const { origin } = location; | |||
| const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); | |||
| const evtSource = new EventSource( | |||
| `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | |||
| { withCredentials: false }, | |||
| ); | |||
| evtSource.onmessage = (event) => { | |||
| const data = event?.data; | |||
| if (!data) { | |||
| return; | |||
| } | |||
| const dataJson = parseJsonText(data); | |||
| if (dataJson) { | |||
| const nodes = dataJson?.result?.object?.status?.nodes; | |||
| if (nodes) { | |||
| onMessage(nodes); | |||
| } | |||
| } | |||
| }; | |||
| evtSource.onerror = (error) => { | |||
| console.error('SSE error: ', error); | |||
| }; | |||
| evtSourceRef.current = evtSource; | |||
| }, | |||
| [onMessage], | |||
| ); | |||
| const closeSSE = useCallback(() => { | |||
| if (evtSourceRef.current) { | |||
| evtSourceRef.current.close(); | |||
| evtSourceRef.current = null; | |||
| } | |||
| }, []); | |||
| return [setupSSE, closeSSE]; | |||
| }; | |||
| @@ -0,0 +1,19 @@ | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| /** | |||
| * 生成具有初始值的状态引用 | |||
| * | |||
| * @param initialValue - 状态的初始值 | |||
| * @return 包含状态值、状态设置函数和可变引用对象的数组 | |||
| */ | |||
| export function useStateRef<T>(initialValue: T) { | |||
| const [value, setValue] = useState(initialValue); | |||
| const ref = useRef(value); | |||
| useEffect(() => { | |||
| ref.current = value; | |||
| }, [value]); | |||
| return [value, setValue, ref] as const; | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||
| /** | |||
| * 生成一个自定义钩子,用于管理模态框的可见性状态。 | |||
| * | |||
| * @param initialValue - 模态框的初始可见性状态。 | |||
| * @return 一个数组,包含 visible、打开函数、关闭函数和 visible ref。 | |||
| */ | |||
| export function useVisible(initialValue: boolean) { | |||
| const [visible, setVisible] = useState(initialValue); | |||
| const ref = useRef(initialValue); | |||
| const open = useCallback(() => { | |||
| setVisible(true); | |||
| }, []); | |||
| const close = useCallback(() => { | |||
| setVisible(false); | |||
| }, []); | |||
| useEffect(() => { | |||
| ref.current = visible; | |||
| }, [visible]); | |||
| return [visible, open, close, ref] as const; | |||
| } | |||
| @@ -261,8 +261,3 @@ | |||
| } | |||
| } | |||
| } | |||
| .ant-typography { | |||
| color: inherit; | |||
| font-size: inherit; | |||
| } | |||
| @@ -28,7 +28,6 @@ function AutoMLInstance() { | |||
| const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined); | |||
| const [instanceInfo, setInstanceInfo] = useState<AutoMLInstanceData | undefined>(undefined); | |||
| const params = useParams(); | |||
| // const autoMLId = safeInvoke(Number)(params.autoMLId); | |||
| const instanceId = safeInvoke(Number)(params.id); | |||
| const evtSourceRef = useRef<EventSource | null>(null); | |||
| @@ -187,6 +186,7 @@ function AutoMLInstance() { | |||
| icon: <KFIcon type="icon-Trialliebiao" />, | |||
| children: ( | |||
| <ExperimentHistory | |||
| calcMetrics={autoMLInfo?.scoring_functions} | |||
| fileUrl={instanceInfo?.run_history_path} | |||
| isClassification={autoMLInfo?.task_type === AutoMLTaskType.Classification} | |||
| /> | |||
| @@ -1,26 +0,0 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { Typography } from 'antd'; | |||
| import styles from './index.less'; | |||
| export type CopyingTextProps = { | |||
| text: string; | |||
| }; | |||
| function CopyingText({ text }: CopyingTextProps) { | |||
| return ( | |||
| <div className={styles['copying-text']}> | |||
| <Typography.Text ellipsis={{ tooltip: text }} className={styles['copying-text__text']}> | |||
| {text} | |||
| </Typography.Text> | |||
| <KFIcon | |||
| id="copying" | |||
| data-clipboard-text={text} | |||
| type="icon-fuzhi2" | |||
| className={styles['copying-text__icon']} | |||
| color="#606b7a" | |||
| /> | |||
| </div> | |||
| ); | |||
| } | |||
| export default CopyingText; | |||
| @@ -8,8 +8,9 @@ import TrialStatusCell from '../TrialStatusCell'; | |||
| import styles from './index.less'; | |||
| type ExperimentHistoryProps = { | |||
| fileUrl?: string; | |||
| isClassification: boolean; | |||
| calcMetrics?: string; // 计算指标 | |||
| fileUrl?: string; // 文件url | |||
| isClassification: boolean; // 是否是分类 | |||
| }; | |||
| type TableData = { | |||
| @@ -22,7 +23,7 @@ type TableData = { | |||
| althorithm?: string; | |||
| }; | |||
| function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) { | |||
| function ExperimentHistory({ calcMetrics, fileUrl, isClassification }: ExperimentHistoryProps) { | |||
| const [tableData, setTableData] = useState<TableData[]>([]); | |||
| useEffect(() => { | |||
| // 获取实验运行历史记录 | |||
| @@ -33,7 +34,7 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||
| const list: TableData[] = data.map((item) => { | |||
| return { | |||
| id: item[0]?.[0], | |||
| accuracy: item[1]?.[5]?.accuracy, | |||
| accuracy: calcMetrics ? item[1]?.[5]?.[calcMetrics] : undefined, | |||
| duration: item[1]?.[5]?.duration, | |||
| train_loss: item[1]?.[5]?.train_loss, | |||
| status: item[1]?.[2]?.['__enum__']?.split('.')?.[1], | |||
| @@ -64,12 +65,6 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||
| width: 80, | |||
| render: tableCellRender(false), | |||
| }, | |||
| { | |||
| title: '准确率', | |||
| dataIndex: 'accuracy', | |||
| key: 'accuracy', | |||
| render: tableCellRender(true), | |||
| }, | |||
| { | |||
| title: '耗时', | |||
| dataIndex: 'duration', | |||
| @@ -103,6 +98,15 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||
| }, | |||
| ]; | |||
| if (calcMetrics) { | |||
| columns.splice(0, 0, { | |||
| title: `指标:${calcMetrics}`, | |||
| dataIndex: 'accuracy', | |||
| key: 'accuracy', | |||
| render: tableCellRender(true), | |||
| }); | |||
| } | |||
| return ( | |||
| <div className={styles['experiment-history']}> | |||
| <div className={styles['experiment-history__content']}> | |||
| @@ -1,6 +1,6 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import { useCheck } from '@/hooks'; | |||
| import { useCheck } from '@/hooks/useCheck'; | |||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { type ExperimentInstance } from '@/types'; | |||
| @@ -58,7 +58,8 @@ function ExperimentInstanceComponent({ | |||
| // 删除实验实例确认 | |||
| const handleRemove = (instance: ExperimentInstance) => { | |||
| modalConfirm({ | |||
| title: '确定删除该条实例吗?', | |||
| title: '删除后,该实验实例将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteExperimentInstance(instance.id); | |||
| }, | |||
| @@ -96,6 +97,18 @@ function ExperimentInstanceComponent({ | |||
| } | |||
| }; | |||
| // 终止实验实例 | |||
| const handleTerminate = (instance: ExperimentInstance) => { | |||
| modalConfirm({ | |||
| title: '终止后,该次实验运行将不可恢复', | |||
| content: '是否确认终止?', | |||
| isDelete: false, | |||
| onOk: () => { | |||
| terminateExperimentInstance(instance); | |||
| }, | |||
| }); | |||
| }; | |||
| // 终止实验实例 | |||
| const terminateExperimentInstance = async (instance: ExperimentInstance) => { | |||
| const request = config.stopInsReq; | |||
| @@ -107,7 +120,7 @@ function ExperimentInstanceComponent({ | |||
| }; | |||
| if (!experimentInsList || experimentInsList.length === 0) { | |||
| return <div style={{ textAlign: 'center' }}>暂无实验实例</div>; | |||
| return <div style={{ textAlign: 'center' }}>暂无数据</div>; | |||
| } | |||
| return ( | |||
| @@ -188,7 +201,7 @@ function ExperimentInstanceComponent({ | |||
| item.status === ExperimentStatus.Terminated | |||
| } | |||
| icon={<KFIcon type="icon-zhongzhi" />} | |||
| onClick={() => terminateExperimentInstance(item)} | |||
| onClick={() => handleTerminate(item)} | |||
| > | |||
| 终止 | |||
| </Button> | |||
| @@ -7,7 +7,7 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { AutoMLData } from '@/pages/AutoML/types'; | |||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import themes from '@/styles/theme.less'; | |||
| @@ -93,17 +93,14 @@ function ExperimentList({ type }: ExperimentListProps) { | |||
| const [res] = await to(request(record.id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| setPagination((prev) => { | |||
| return { | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getAutoMLList(); | |||
| } | |||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||
| }; | |||
| }); | |||
| } | |||
| }; | |||
| @@ -188,6 +185,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||
| if (expanded) { | |||
| setExpandedRowKeys([record.id]); | |||
| getExperimentInsList(record.id, 0); | |||
| refreshExperimentList(); | |||
| } else { | |||
| setExpandedRowKeys([]); | |||
| } | |||
| @@ -75,8 +75,15 @@ function CodeConfigList() { | |||
| const deleteRecord = async (id: number) => { | |||
| const [res] = await to(deleteCodeConfigReq(id)); | |||
| if (res) { | |||
| getDataList(); | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| setPagination((prev) => { | |||
| return { | |||
| ...prev, | |||
| current: dataList!.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||
| }; | |||
| }); | |||
| } | |||
| }; | |||
| @@ -70,7 +70,12 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps | |||
| > | |||
| {item.git_url} | |||
| </Typography.Paragraph> | |||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||
| <Typography.Paragraph | |||
| className={styles['code-config-item__branch']} | |||
| ellipsis={{ tooltip: item.git_branch }} | |||
| > | |||
| {item.git_branch} | |||
| </Typography.Paragraph> | |||
| </div> | |||
| <Flex justify="space-between"> | |||
| <div className={styles['code-config-item__user']}> | |||
| @@ -4,7 +4,12 @@ import KFModal from '@/components/KFModal'; | |||
| import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||
| import { addDataset } from '@/services/dataset/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getFileListFromEvent, limitUploadFileType, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| getFileListFromEvent, | |||
| limitUploadFileType, | |||
| removeUploadedFile, | |||
| validateUploadFiles, | |||
| } from '@/utils/ui'; | |||
| import { | |||
| Button, | |||
| Form, | |||
| @@ -29,11 +34,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | |||
| const [uuid] = useState(Date.now()); | |||
| // const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | |||
| // useEffect(() => { | |||
| // getClusterOptions(); | |||
| // }, []); | |||
| // 上传组件参数 | |||
| const uploadProps: UploadProps = { | |||
| @@ -44,16 +44,9 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| defaultFileList: [], | |||
| accept: '.zip,.tgz', | |||
| beforeUpload: limitUploadFileType('zip,tgz'), | |||
| onRemove: removeUploadedFile, | |||
| }; | |||
| // 获取集群版本数据 | |||
| // const getClusterOptions = async () => { | |||
| // const [res] = await to(getDictSelectOption('available_cluster')); | |||
| // if (res) { | |||
| // setClusterOptions(res); | |||
| // } | |||
| // }; | |||
| // 上传请求 | |||
| const createDataset = async (params: any) => { | |||
| const [res] = await to(addDataset(params)); | |||
| @@ -113,7 +106,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={50} /> | |||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={40} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="数据集版本" | |||
| @@ -159,27 +152,42 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| showSearch | |||
| /> | |||
| </Form.Item> | |||
| {/* <Form.Item label="集群版本" name="available_cluster"> | |||
| <Select allowClear placeholder="请选择集群版本" options={clusterOptions} /> | |||
| </Form.Item> */} | |||
| <Form.Item | |||
| label="数据集简介" | |||
| label="数据集描述" | |||
| name="description" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入数据集简介', | |||
| message: '请输入数据集描述', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| placeholder="请输入数据集简介" | |||
| placeholder="请输入数据集描述" | |||
| showCount | |||
| maxLength={200} | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="版本描述" | |||
| name="version_desc" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入版本描述', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| placeholder="请输入版本描述" | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| maxLength={200} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="可见性" | |||
| name="is_public" | |||
| @@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal'; | |||
| import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||
| import { addModel } from '@/services/dataset/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||
| import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| Button, | |||
| Form, | |||
| @@ -37,6 +37,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| Authorization: getAccessToken() || '', | |||
| }, | |||
| defaultFileList: [], | |||
| onRemove: removeUploadedFile, | |||
| }; | |||
| // 上传请求 | |||
| @@ -96,7 +97,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={50} /> | |||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={40} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="模型版本" | |||
| @@ -143,23 +144,41 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="模型简介" | |||
| label="模型描述" | |||
| name="description" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入模型简介', | |||
| message: '请输入模型描述', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| placeholder="请输入模型简介" | |||
| placeholder="请输入模型描述" | |||
| maxLength={200} | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="版本描述" | |||
| name="version_desc" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入版本描述', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| placeholder="请输入版本描述" | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| maxLength={200} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="可见性" | |||
| name="is_public" | |||
| @@ -3,7 +3,7 @@ import KFIcon from '@/components/KFIcon'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||
| import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| Button, | |||
| Form, | |||
| @@ -50,6 +50,7 @@ function AddVersionModal({ | |||
| defaultFileList: [], | |||
| beforeUpload: config.beforeUpload, | |||
| accept: config.uploadAccept, | |||
| onRemove: removeUploadedFile, | |||
| }; | |||
| // 上传请求 | |||
| @@ -13,6 +13,7 @@ import { | |||
| } from '@/pages/Dataset/config'; | |||
| import GraphLegend from '@/pages/Model/components/GraphLegend'; | |||
| import ModelEvolution from '@/pages/Model/components/ModelEvolution'; | |||
| import { VersionChangedMessage } from '@/utils/constant'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| @@ -124,6 +125,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||
| onOk: () => { | |||
| getVersionList(true); | |||
| close(); | |||
| window.postMessage({ type: VersionChangedMessage }); | |||
| }, | |||
| }); | |||
| }; | |||
| @@ -170,6 +172,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| getVersionList(true); | |||
| window.postMessage({ type: VersionChangedMessage }); | |||
| } | |||
| }; | |||
| @@ -21,7 +21,7 @@ const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [ | |||
| value: data.name, | |||
| }, | |||
| { | |||
| label: '版本', | |||
| label: '数据集版本', | |||
| value: data.version, | |||
| }, | |||
| { | |||
| @@ -64,7 +64,7 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [ | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| label: '版本', | |||
| label: '模型版本', | |||
| value: data.version, | |||
| ellipsis: true, | |||
| }, | |||
| @@ -107,8 +107,15 @@ function ResourceList( | |||
| const request = config.deleteRecord; | |||
| const [res] = await to(request(params)); | |||
| if (res) { | |||
| getDataList(); | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| setPagination((prev) => { | |||
| return { | |||
| ...prev, | |||
| current: dataList!.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||
| }; | |||
| }); | |||
| } | |||
| }; | |||
| @@ -1,5 +1,5 @@ | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { getAssetIcon } from '@/services/dataset/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Flex, Tabs, type TabsProps } from 'antd'; | |||
| @@ -1,3 +1,9 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2025-03-24 15:41:42 | |||
| * @Description: 版本文件列表 | |||
| */ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { | |||
| ResourceData, | |||
| @@ -11,7 +11,6 @@ | |||
| text-align: center; | |||
| background: @background; | |||
| border-radius: 4px 4px 0 0; | |||
| .singleLine(); | |||
| } | |||
| .text() { | |||
| @@ -20,7 +19,6 @@ | |||
| font-size: 13px; | |||
| line-height: 22px; | |||
| word-break: break-all; | |||
| .singleLine(); | |||
| } | |||
| .version-container(@background) { | |||
| @@ -88,7 +88,7 @@ function VersionCompareModal({ | |||
| format: formatProject, | |||
| }, | |||
| { | |||
| key: 'description', | |||
| key: 'version_desc', | |||
| text: '版本描述', | |||
| }, | |||
| ] | |||
| @@ -123,7 +123,7 @@ function VersionCompareModal({ | |||
| format: formatTrainTask, | |||
| }, | |||
| { | |||
| key: 'description', | |||
| key: 'version_desc', | |||
| text: '版本描述', | |||
| }, | |||
| ], | |||
| @@ -193,7 +193,14 @@ function VersionCompareModal({ | |||
| ))} | |||
| </div> | |||
| <div className={styles['version-compare__left']}> | |||
| <div className={styles['version-compare__left__title']}>{v1.version}</div> | |||
| <div className={styles['version-compare__left__title']}> | |||
| <Typography.Text | |||
| ellipsis={{ tooltip: v1.version }} | |||
| style={{ width: '100%', lineHeight: 'inherit' }} | |||
| > | |||
| {v1.version} | |||
| </Typography.Text> | |||
| </div> | |||
| {fields.map(({ key, format }) => { | |||
| const text = getValue(v1, key as keyof typeof v1, format); | |||
| return ( | |||
| @@ -203,7 +210,7 @@ function VersionCompareModal({ | |||
| [styles['version-compare__left__text--different']]: isDifferent(key), | |||
| })} | |||
| > | |||
| <Typography.Text ellipsis={{ tooltip: text }}> | |||
| <Typography.Text ellipsis={{ tooltip: text }} style={{ width: '100%' }}> | |||
| {isEmpty(text) ? '--' : text} | |||
| </Typography.Text> | |||
| </div> | |||
| @@ -211,7 +218,14 @@ function VersionCompareModal({ | |||
| })} | |||
| </div> | |||
| <div className={styles['version-compare__right']}> | |||
| <div className={styles['version-compare__right__title']}>{v2.version}</div> | |||
| <div className={styles['version-compare__right__title']}> | |||
| <Typography.Text | |||
| ellipsis={{ tooltip: v2.version }} | |||
| style={{ width: '100%', lineHeight: 'inherit' }} | |||
| > | |||
| {v2.version} | |||
| </Typography.Text> | |||
| </div> | |||
| {fields.map(({ key, format }) => { | |||
| const text = getValue(v2, key as keyof typeof v2, format); | |||
| return ( | |||
| @@ -221,7 +235,7 @@ function VersionCompareModal({ | |||
| [styles['version-compare__right__text--different']]: isDifferent(key), | |||
| })} | |||
| > | |||
| <Typography.Text ellipsis={{ tooltip: text }}> | |||
| <Typography.Text ellipsis={{ tooltip: text }} style={{ width: '100%' }}> | |||
| {isEmpty(text) ? '--' : text} | |||
| </Typography.Text> | |||
| </div> | |||
| @@ -104,16 +104,16 @@ function EditorCreate() { | |||
| <Row gutter={10}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="任务名称" | |||
| label="编辑器名称" | |||
| name="name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入任务名称', | |||
| message: '请输入编辑器名称', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入任务名称" maxLength={64} showCount allowClear /> | |||
| <Input placeholder="请输入编辑器名称" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| @@ -137,7 +137,7 @@ function EditorCreate() { | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="standard" | |||
| name="computing_resource_id" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| @@ -6,7 +6,9 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { DevEditorStatus } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { DatasetData, ModelData } from '@/pages/Dataset/config'; | |||
| import { | |||
| deleteEditorReq, | |||
| getEditorListReq, | |||
| @@ -14,6 +16,7 @@ import { | |||
| stopEditorReq, | |||
| } from '@/services/developmentEnvironment'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { parseJsonText } from '@/utils'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| @@ -42,6 +45,10 @@ export type EditorData = { | |||
| update_by: string; | |||
| create_time: string; | |||
| url: string; | |||
| computing_resource_id: number; | |||
| dataset?: string | DatasetData; | |||
| model?: string | ModelData; | |||
| image?: string; | |||
| }; | |||
| function EditorList() { | |||
| @@ -56,6 +63,7 @@ function EditorList() { | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| // 获取编辑器列表 | |||
| const getEditorList = useCallback(async () => { | |||
| @@ -66,6 +74,10 @@ function EditorList() { | |||
| const [res] = await to(getEditorListReq(params)); | |||
| if (res && res.data) { | |||
| const { content = [], totalElements = 0 } = res.data; | |||
| content.forEach((item: EditorData) => { | |||
| item.dataset = typeof item.dataset === 'string' ? parseJsonText(item.dataset) : null; | |||
| item.model = typeof item.model === 'string' ? parseJsonText(item.model) : null; | |||
| }); | |||
| setTableData(content); | |||
| setTotal(totalElements); | |||
| } | |||
| @@ -80,17 +92,14 @@ function EditorList() { | |||
| const [res] = await to(deleteEditorReq(id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| setPagination((prev) => { | |||
| return { | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getEditorList(); | |||
| } | |||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||
| }; | |||
| }); | |||
| } | |||
| }; | |||
| @@ -105,11 +114,18 @@ function EditorList() { | |||
| // 停止编辑器 | |||
| const stopEditor = async (id: number) => { | |||
| const [res] = await to(stopEditorReq(id)); | |||
| if (res) { | |||
| message.success('操作成功'); | |||
| getEditorList(); | |||
| } | |||
| modalConfirm({ | |||
| title: '停止后,该编辑器将不可使用', | |||
| content: '是否确认停止?', | |||
| isDelete: false, | |||
| onOk: async () => { | |||
| const [res] = await to(stopEditorReq(id)); | |||
| if (res) { | |||
| message.success('操作成功'); | |||
| getEditorList(); | |||
| } | |||
| }, | |||
| }); | |||
| }; | |||
| // 制作镜像 | |||
| @@ -168,44 +184,72 @@ function EditorList() { | |||
| title: '编辑器名称', | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| width: '30%', | |||
| render: (text, record) => | |||
| record.url && record.status === DevEditorStatus.Running ? ( | |||
| <a className="kf-table-row-link" onClick={(e) => gotoEditorPage(e, record)}> | |||
| {text} | |||
| </a> | |||
| ) : ( | |||
| <span>{text ?? '--'}</span> | |||
| ), | |||
| }, | |||
| { | |||
| title: '状态', | |||
| dataIndex: 'status', | |||
| key: 'status', | |||
| width: '10%', | |||
| render: EditorStatusCell, | |||
| width: '20%', | |||
| render: (text, record, index) => | |||
| record.url && record.status === DevEditorStatus.Running | |||
| ? tableCellRender<EditorData>(true, TableCellValueType.Link, { | |||
| onClick: (record, e) => gotoEditorPage(e, record), | |||
| })(text, record, index) | |||
| : tableCellRender<EditorData>(true, TableCellValueType.Text)(text, record, index), | |||
| }, | |||
| { | |||
| title: '资源', | |||
| title: '计算资源', | |||
| dataIndex: 'computing_resource', | |||
| key: 'computing_resource', | |||
| width: '20%', | |||
| width: 100, | |||
| render: tableCellRender(), | |||
| }, | |||
| { | |||
| title: '资源规格', | |||
| dataIndex: 'computing_resource_id', | |||
| key: 'computing_resource_id', | |||
| width: '20%', | |||
| render: tableCellRender(true, TableCellValueType.Custom, { | |||
| format: getResourceDescription, | |||
| }), | |||
| }, | |||
| { | |||
| title: '数据集', | |||
| dataIndex: ['dataset', 'showValue'], | |||
| key: 'dataset', | |||
| width: '15%', | |||
| render: tableCellRender(true), | |||
| }, | |||
| { | |||
| title: '模型', | |||
| dataIndex: ['model', 'showValue'], | |||
| key: 'model', | |||
| width: '15%', | |||
| render: tableCellRender(true), | |||
| }, | |||
| { | |||
| title: '镜像', | |||
| dataIndex: ['image'], | |||
| key: 'image', | |||
| width: '15%', | |||
| render: tableCellRender(true), | |||
| }, | |||
| { | |||
| title: '创建者', | |||
| dataIndex: 'update_by', | |||
| key: 'update_by', | |||
| width: '20%', | |||
| render: tableCellRender(), | |||
| width: '15%', | |||
| render: tableCellRender(true), | |||
| }, | |||
| { | |||
| title: '创建时间', | |||
| dataIndex: 'create_time', | |||
| key: 'create_time', | |||
| width: '20%', | |||
| width: 180, | |||
| render: tableCellRender(false, TableCellValueType.Date), | |||
| }, | |||
| { | |||
| title: '状态', | |||
| dataIndex: 'status', | |||
| key: 'status', | |||
| width: 80, | |||
| render: EditorStatusCell, | |||
| }, | |||
| { | |||
| title: '操作', | |||
| dataIndex: 'operation', | |||
| @@ -20,7 +20,7 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) { | |||
| }), | |||
| ); | |||
| if (res) { | |||
| message.success('创建成功,请到 “AI资产” - “个人镜像” 中查看'); | |||
| message.success('创建成功,请到 “多形态资源库” - “个人镜像” 中查看'); | |||
| onOk?.(); | |||
| } | |||
| }; | |||
| @@ -51,20 +51,20 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) { | |||
| message: '请输入镜像名称', | |||
| }, | |||
| { | |||
| pattern: /^[a-z0-9/_-]*$/, | |||
| message: '只支持小写字母、数字、下划线(_)、中横线(-)、斜杠(/)', | |||
| pattern: /^[a-z0-9/._-]*$/, | |||
| message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入镜像名称" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="镜像Tag" | |||
| label="镜像版本" | |||
| name="tag_name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像Tag', | |||
| message: '请输入镜像版本', | |||
| }, | |||
| { | |||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||
| @@ -72,7 +72,7 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) { | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear /> | |||
| <Input placeholder="请输入镜像版本" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="镜像描述" | |||
| @@ -87,7 +87,7 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) { | |||
| <Input.TextArea | |||
| placeholder="请输入镜像描述" | |||
| autoSize={{ minRows: 3, maxRows: 6 }} | |||
| maxLength={256} | |||
| maxLength={128} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| @@ -0,0 +1,12 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2025-03-31 16:38:59 | |||
| * @Description: 实验对比 Aim | |||
| */ | |||
| import IframePage, { IframePageType } from '@/components/IFramePage'; | |||
| function AimPage() { | |||
| return <IframePage type={IframePageType.Aim}></IframePage>; | |||
| } | |||
| export default AimPage; | |||
| @@ -12,8 +12,9 @@ import { | |||
| } from '@/services/experiment'; | |||
| import { tableSorter } from '@/utils'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||
| import { useSearchParams } from '@umijs/max'; | |||
| import { useNavigate, useSearchParams } from '@umijs/max'; | |||
| import { App, Button, Table, TablePaginationConfig, TableProps } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useMemo, useState } from 'react'; | |||
| @@ -46,6 +47,7 @@ function ExperimentComparison() { | |||
| }); | |||
| const { message } = App.useApp(); | |||
| const navigate = useNavigate(); | |||
| const config = comparisonConfig[comparisonType]; | |||
| useEffect(() => { | |||
| @@ -73,7 +75,9 @@ function ExperimentComparison() { | |||
| const [res] = await to(getExpMetricsReq(selectedRowKeys)); | |||
| if (res && res.data) { | |||
| const url = res.data; | |||
| window.open(url, '_blank'); | |||
| // window.open(url, '_blank'); | |||
| SessionStorage.setItem(SessionStorage.aimUrlKey, url); | |||
| navigate('../compare-visual'); | |||
| } | |||
| }; | |||
| @@ -1,5 +1,6 @@ | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import { useStateRef, useVisible } from '@/hooks'; | |||
| import { useStateRef } from '@/hooks/useStateRef'; | |||
| import { useVisible } from '@/hooks/useVisible'; | |||
| import { getExperimentIns } from '@/services/experiment/index.js'; | |||
| import { getWorkflowById } from '@/services/pipeline/index.js'; | |||
| import themes from '@/styles/theme.less'; | |||
| @@ -179,11 +180,12 @@ function ExperimentText() { | |||
| if (!statusNode) { | |||
| return; | |||
| } | |||
| const { finishedAt, startedAt, phase, id } = statusNode; | |||
| const { finishedAt, startedAt, phase, id, message } = statusNode; | |||
| workflowNode.experimentStartTime = startedAt; | |||
| workflowNode.experimentEndTime = finishedAt; | |||
| workflowNode.experimentStatus = phase; | |||
| workflowNode.workflowId = id; | |||
| workflowNode.message = message; | |||
| workflowNode.img = phase | |||
| ? `${workflowNode.imgName}-${phase}.png` | |||
| : `${workflowNode.imgName}.png`; | |||
| @@ -3,7 +3,7 @@ import editExperimentIcon from '@/assets/img/edit-experiment.png'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { type PipelineGlobalParam } from '@/types'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Button, Form, Input, Radio, Select, type FormRule } from 'antd'; | |||
| import { Button, Form, Input, Radio, Select, Typography, type FormRule } from 'antd'; | |||
| import { useState } from 'react'; | |||
| import styles from './index.less'; | |||
| @@ -63,13 +63,14 @@ export const getParamRules = (paramType: number, required: boolean = false): For | |||
| }; | |||
| // 根据参数设置 label | |||
| export const getParamType = (param: PipelineGlobalParam): string => { | |||
| export const getParamLabel = (param: PipelineGlobalParam): React.ReactNode => { | |||
| const paramTypes: Readonly<Record<number, string>> = { | |||
| 1: '字符串', | |||
| 2: '整型', | |||
| 3: '布尔类型', | |||
| }; | |||
| return param.param_name + `(${paramTypes[param.param_type]})`; | |||
| const label = param.param_name + `(${paramTypes[param.param_type]})`; | |||
| return <Typography.Text ellipsis={{ tooltip: label }}>{label}</Typography.Text>; | |||
| }; | |||
| function AddExperimentModal({ | |||
| @@ -99,8 +100,8 @@ function AddExperimentModal({ | |||
| }; | |||
| const paramLayout = { | |||
| labelCol: { span: 8 }, | |||
| wrapperCol: { span: 16 }, | |||
| labelCol: { span: 6 }, | |||
| wrapperCol: { span: 18 }, | |||
| }; | |||
| // 除了流水线选择发生变化 | |||
| @@ -157,7 +158,6 @@ function AddExperimentModal({ | |||
| form={form} | |||
| {...layout} | |||
| labelAlign="left" | |||
| labelWrap | |||
| > | |||
| <Form.Item | |||
| label="实验名称" | |||
| @@ -215,9 +215,9 @@ function AddExperimentModal({ | |||
| {...restField} | |||
| {...paramLayout} | |||
| key={key} | |||
| label={getParamType(globalParam[name])} | |||
| label={getParamLabel(globalParam[name])} | |||
| name={[name, 'param_value']} | |||
| rules={getParamRules(globalParam[name]['param_type'])} | |||
| rules={getParamRules(globalParam[name]['param_type'], true)} | |||
| > | |||
| {getParamComponent( | |||
| globalParam[name]['param_type'], | |||
| @@ -13,7 +13,6 @@ | |||
| } | |||
| &__tabs { | |||
| height: calc(100% - 169px); | |||
| :global { | |||
| .ant-tabs-nav { | |||
| padding-left: 24px; | |||
| @@ -35,7 +34,7 @@ | |||
| display: flex; | |||
| align-items: center; | |||
| margin-bottom: 15px; | |||
| padding-left: 24px; | |||
| padding: 0 24px; | |||
| color: @text-color; | |||
| font-size: 15px; | |||
| } | |||
| @@ -3,7 +3,7 @@ import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import { PipelineNodeModelSerialize } from '@/types'; | |||
| import { elapsedTime, formatDate } from '@/utils/date'; | |||
| import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | |||
| import { Drawer, Tabs } from 'antd'; | |||
| import { Drawer, Tabs, Typography } from 'antd'; | |||
| import { useMemo } from 'react'; | |||
| import ExperimentParameter from '../ExperimentParameter'; | |||
| import ExperimentResult from '../ExperimentResult'; | |||
| @@ -129,6 +129,14 @@ const ExperimentDrawer = ({ | |||
| '--' | |||
| )} | |||
| </div> | |||
| {instanceNodeData.message && ( | |||
| <div className={styles['experiment-drawer__info']}> | |||
| <div style={{ flex: 'none' }}>消息:</div> | |||
| <Typography.Text ellipsis={{ tooltip: instanceNodeData.message }}> | |||
| {instanceNodeData.message ?? '--'} | |||
| </Typography.Text> | |||
| </div> | |||
| )} | |||
| <div className={styles['experiment-drawer__info']}> | |||
| 启动时间:{formatDate(instanceNodeStartTime)} | |||
| </div> | |||
| @@ -137,7 +145,14 @@ const ExperimentDrawer = ({ | |||
| {elapsedTime(instanceNodeStartTime, instanceNodeEndTime)} | |||
| </div> | |||
| </div> | |||
| <Tabs defaultActiveKey="1" items={items} className={styles['experiment-drawer__tabs']} /> | |||
| <Tabs | |||
| defaultActiveKey="1" | |||
| items={items} | |||
| className={styles['experiment-drawer__tabs']} | |||
| style={{ | |||
| height: instanceNodeData.message ? 'calc(100% - 169px - 39px)' : 'calc(100% - 169px)', | |||
| }} | |||
| /> | |||
| </Drawer> | |||
| ); | |||
| }; | |||
| @@ -1,6 +1,6 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import { useCheck } from '@/hooks'; | |||
| import { useCheck } from '@/hooks/useCheck'; | |||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import { | |||
| deleteManyExperimentIns, | |||
| @@ -62,7 +62,8 @@ function ExperimentInstanceComponent({ | |||
| // 删除实验实例确认 | |||
| const handleRemove = (instance: ExperimentInstance) => { | |||
| modalConfirm({ | |||
| title: '确定删除该条实例吗?', | |||
| title: '删除后,该实验实例将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteExperimentInstance(instance.id); | |||
| }, | |||
| @@ -101,7 +102,8 @@ function ExperimentInstanceComponent({ | |||
| // 终止实验实例 | |||
| const handleTerminate = (instance: ExperimentInstance) => { | |||
| modalConfirm({ | |||
| title: '确定要终止此次实验运行吗?', | |||
| title: '终止后,该次实验运行将不可恢复', | |||
| content: '是否确认终止?', | |||
| isDelete: false, | |||
| onOk: () => { | |||
| terminateExperimentInstance(instance); | |||
| @@ -5,7 +5,7 @@ | |||
| */ | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import { useStateRef } from '@/hooks'; | |||
| import { useStateRef } from '@/hooks/useStateRef'; | |||
| import { getExperimentPodsLog } from '@/services/experiment/index.js'; | |||
| import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | |||
| import { Button } from 'antd'; | |||
| @@ -1,31 +1,14 @@ | |||
| .params_container { | |||
| max-height: 230px; | |||
| padding: 15px 15px 0; | |||
| .params-container { | |||
| max-height: calc(100vh - 300px); | |||
| padding: 24px 24px 0; | |||
| overflow-y: auto; | |||
| border: 1px solid #e6e6e6; | |||
| border-radius: 8px; | |||
| &_line { | |||
| display: flex; | |||
| align-items: center; | |||
| margin-bottom: 15px; | |||
| &_label { | |||
| width: 180px; | |||
| color: @text-color; | |||
| font-size: 15px; | |||
| } | |||
| &_value { | |||
| flex: 1; | |||
| width: 100px; | |||
| margin-left: 15px; | |||
| padding: 10px 20px; | |||
| color: @text-color; | |||
| font-size: @font-size; | |||
| line-height: 20px; | |||
| background: #f6f6f6; | |||
| border: 1px solid #e0e0e1; | |||
| border-radius: 4px; | |||
| } | |||
| .params-empty { | |||
| :global { | |||
| .kf-empty__image { | |||
| width: 300px; | |||
| } | |||
| } | |||
| } | |||
| @@ -4,9 +4,11 @@ | |||
| * @Description: 查看实验使用的参数 | |||
| */ | |||
| import parameterImg from '@/assets/img/modal-parameter.png'; | |||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { type PipelineGlobalParam } from '@/types'; | |||
| import { getParamType } from '../AddExperimentModal'; | |||
| import { Form } from 'antd'; | |||
| import { getParamComponent, getParamLabel } from '../AddExperimentModal'; | |||
| import styles from './index.less'; | |||
| type ParamsModalProps = { | |||
| @@ -26,14 +28,44 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||
| cancelButtonProps={{ style: { display: 'none' } }} | |||
| width={825} | |||
| > | |||
| <div className={styles.params_container}> | |||
| {globalParam?.map((item) => ( | |||
| <div key={item.param_name} className={styles.params_container_line}> | |||
| <span className={styles.params_container_line_label}>{getParamType(item)}</span> | |||
| <span className={styles.params_container_line_value}>{item.param_value}</span> | |||
| </div> | |||
| ))} | |||
| </div> | |||
| {Array.isArray(globalParam) && globalParam.length > 0 ? ( | |||
| <div className={styles['params-container']}> | |||
| <Form | |||
| name="view_params_form" | |||
| labelCol={{ span: 6 }} | |||
| wrapperCol={{ span: 18 }} | |||
| initialValues={{ global_param: globalParam }} | |||
| labelAlign="left" | |||
| disabled | |||
| > | |||
| <Form.List name="global_param"> | |||
| {(fields) => | |||
| fields.map(({ key, name, ...restField }) => ( | |||
| <Form.Item | |||
| {...restField} | |||
| key={key} | |||
| name={[name, 'param_value']} | |||
| label={getParamLabel(globalParam[name])} | |||
| > | |||
| {getParamComponent( | |||
| globalParam[name]['param_type'], | |||
| globalParam[name]['is_sensitive'], | |||
| )} | |||
| </Form.Item> | |||
| )) | |||
| } | |||
| </Form.List> | |||
| </Form> | |||
| </div> | |||
| ) : ( | |||
| <KFEmpty | |||
| className={styles['params-empty']} | |||
| type={EmptyType.NoData} | |||
| title="暂无数据" | |||
| content="该流水线没有设置全局参数" | |||
| hasFooter={false} | |||
| /> | |||
| )} | |||
| </KFModal> | |||
| ); | |||
| } | |||
| @@ -1,7 +1,7 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { ExperimentStatus, TensorBoardStatus } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { | |||
| deleteExperimentById, | |||
| getExperiment, | |||
| @@ -206,6 +206,7 @@ function Experiment() { | |||
| setExpandedRowKeys(null); | |||
| } else { | |||
| getQueryByExperiment(record.id, 0); | |||
| refreshExperimentList(); | |||
| } | |||
| }; | |||
| @@ -285,8 +286,6 @@ function Experiment() { | |||
| message.success('运行成功'); | |||
| refreshExperimentList(); | |||
| refreshExperimentIns(id); | |||
| } else { | |||
| message.error('运行失败'); | |||
| } | |||
| }; | |||
| @@ -377,6 +376,31 @@ function Experiment() { | |||
| getQueryByExperiment(expandedRowKeys, page); | |||
| }; | |||
| // 处理删除 | |||
| const handleExperimentDelete = (record) => { | |||
| modalConfirm({ | |||
| title: '删除后,该实验将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteExperimentById(record.id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| setPagination((prev) => { | |||
| return { | |||
| ...prev, | |||
| current: experimentList.length === 1 ? Math.max(1, prev.current - 1) : prev.current, | |||
| }; | |||
| }); | |||
| } else { | |||
| message.error(ret.msg); | |||
| } | |||
| }); | |||
| }, | |||
| }); | |||
| }; | |||
| const columns = [ | |||
| { | |||
| title: '实验名称', | |||
| @@ -475,22 +499,7 @@ function Experiment() { | |||
| size="small" | |||
| key="batchRemove" | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| onClick={() => { | |||
| modalConfirm({ | |||
| title: '删除后,该实验将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteExperimentById(record.id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('删除成功'); | |||
| getExperimentList(); | |||
| } else { | |||
| message.error(ret.msg); | |||
| } | |||
| }); | |||
| }, | |||
| }); | |||
| }} | |||
| onClick={() => handleExperimentDelete(record)} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| @@ -499,6 +508,7 @@ function Experiment() { | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['experiment-list']}> | |||
| <PageTitle title="实验列表"></PageTitle> | |||
| @@ -1,6 +1,6 @@ | |||
| import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | |||
| import { hyperParameterOptimizedMode } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import { | |||
| schedulerAlgorithms, | |||
| @@ -44,7 +44,7 @@ const mirrorRadioItems: KFRadioItem[] = [ | |||
| function MirrorCreate() { | |||
| const navigate = useNavigate(); | |||
| const [form] = Form.useForm(); | |||
| const [nameDisabled, setNameDisabled] = useState(false); | |||
| const [isAddVersion, setIsAddVersion] = useState(false); // 是制作镜像还是新增镜像版本 | |||
| const { message } = App.useApp(); | |||
| const uploadProps: UploadProps = { | |||
| @@ -60,7 +60,7 @@ function MirrorCreate() { | |||
| const name = SessionStorage.getItem(SessionStorage.mirrorNameKey); | |||
| if (name) { | |||
| form.setFieldValue('name', name); | |||
| setNameDisabled(true); | |||
| setIsAddVersion(true); | |||
| } | |||
| return () => { | |||
| SessionStorage.removeItem(SessionStorage.mirrorNameKey); | |||
| @@ -70,32 +70,37 @@ function MirrorCreate() { | |||
| // 创建公网、本地镜像 | |||
| const createPublicMirror = async (formData: FormData) => { | |||
| const upload_type = formData['upload_type']; | |||
| let params; | |||
| if (upload_type === CommonTabKeys.Public) { | |||
| params = { | |||
| const params = { | |||
| ...omit(formData, ['upload_type']), | |||
| upload_type: 0, | |||
| image_type: 0, | |||
| }; | |||
| const [res] = await to(createMirrorReq(params)); | |||
| if (res) { | |||
| message.success('创建成功'); | |||
| navigate(-1); | |||
| } | |||
| } else { | |||
| const fileList = formData['fileList'] ?? []; | |||
| if (validateUploadFiles(fileList)) { | |||
| const file = fileList[0]; | |||
| params = { | |||
| const params = { | |||
| ...omit(formData, ['fileList', 'upload_type']), | |||
| path: file.response.data.url, | |||
| file_size: file.response.data.fileSize, | |||
| file_name: file.response.data.fileName, | |||
| upload_type: 1, | |||
| image_type: 0, | |||
| }; | |||
| const [res] = await to(createMirrorReq(params)); | |||
| if (res) { | |||
| message.success('创建成功'); | |||
| navigate(-1); | |||
| } | |||
| } | |||
| } | |||
| const [res] = await to(createMirrorReq(params)); | |||
| if (res) { | |||
| message.success('创建成功'); | |||
| navigate(-1); | |||
| } | |||
| }; | |||
| // 提交 | |||
| @@ -118,14 +123,16 @@ function MirrorCreate() { | |||
| return true; | |||
| }; | |||
| const descTitle = isAddVersion ? '版本描述' : '镜像描述'; | |||
| return ( | |||
| <div className={styles['mirror-create']}> | |||
| <PageTitle title="创建镜像"></PageTitle> | |||
| <PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle> | |||
| <div className={styles['mirror-create__content']}> | |||
| <div> | |||
| <Form | |||
| name="mirror-create" | |||
| labelCol={{ flex: '130px' }} | |||
| labelCol={{ flex: '135px' }} | |||
| wrapperCol={{ flex: 1 }} | |||
| labelAlign="left" | |||
| form={form} | |||
| @@ -142,7 +149,7 @@ function MirrorCreate() { | |||
| <Row gutter={10}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="镜像名称及Tag" | |||
| label="镜像名称和版本" | |||
| name="name" | |||
| rules={[ | |||
| { | |||
| @@ -150,15 +157,15 @@ function MirrorCreate() { | |||
| message: '请输入镜像名称', | |||
| }, | |||
| { | |||
| pattern: /^[a-z0-9/_-]*$/, | |||
| message: '只支持小写字母、数字、下划线(_)、中横线(-)、斜杠(/)', | |||
| pattern: /^[a-z0-9/._-]*$/, | |||
| message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input | |||
| placeholder="请输入镜像名称" | |||
| maxLength={64} | |||
| disabled={nameDisabled} | |||
| disabled={isAddVersion} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| @@ -174,33 +181,33 @@ function MirrorCreate() { | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像Tag', | |||
| message: '请输入镜像版本', | |||
| }, | |||
| { | |||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||
| message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||
| message: '镜像版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear /> | |||
| <Input placeholder="请输入镜像版本" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={10}> | |||
| <Col span={20}> | |||
| <Form.Item | |||
| label="镜像描述" | |||
| label={descTitle} | |||
| name="description" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像描述', | |||
| message: `请输入${descTitle}`, | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| placeholder="请输入镜像描述,最长128字符" | |||
| placeholder={`请输入${descTitle}`} | |||
| maxLength={128} | |||
| showCount | |||
| allowClear | |||
| @@ -283,7 +290,7 @@ function MirrorCreate() { | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请上传镜像地址', | |||
| message: '请上传镜像文件', | |||
| }, | |||
| ]} | |||
| > | |||
| @@ -303,7 +310,7 @@ function MirrorCreate() { | |||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||
| <Button type="primary" htmlType="submit"> | |||
| 创建镜像 | |||
| 确定 | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| @@ -7,8 +7,8 @@ import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { MirrorVersionStatus } from '@/enums'; | |||
| import { useDomSize } from '@/hooks'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { useDomSize } from '@/hooks/useDomSize'; | |||
| import { | |||
| deleteMirrorVersionReq, | |||
| getMirrorInfoReq, | |||
| @@ -117,17 +117,14 @@ function MirrorInfo() { | |||
| const [res] = await to(deleteMirrorVersionReq(id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length === 1) { | |||
| setPagination((prev) => ({ | |||
| setPagination((prev) => { | |||
| return { | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getMirrorVersionList(); | |||
| } | |||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||
| }; | |||
| }); | |||
| } | |||
| }; | |||
| @@ -155,7 +152,7 @@ function MirrorInfo() { | |||
| }; | |||
| const createMirrorVersion = () => { | |||
| navigate(`/dataset/mirror/create`); | |||
| navigate(`add-version`); | |||
| SessionStorage.setItem(SessionStorage.mirrorNameKey, mirrorInfo.name || ''); | |||
| setCacheState({ | |||
| pagination, | |||
| @@ -174,20 +171,27 @@ function MirrorInfo() { | |||
| title: '镜像地址', | |||
| dataIndex: 'url', | |||
| key: 'url', | |||
| render: tableCellRender(), | |||
| width: '25%', | |||
| render: tableCellRender('auto', TableCellValueType.Text, { copyable: true }), | |||
| }, | |||
| { | |||
| title: '版本描述', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| render: tableCellRender(true), | |||
| }, | |||
| { | |||
| title: '状态', | |||
| dataIndex: 'status', | |||
| key: 'status', | |||
| width: 150, | |||
| width: 100, | |||
| render: MirrorStatusCell, | |||
| }, | |||
| { | |||
| title: '镜像大小', | |||
| dataIndex: 'file_size', | |||
| key: 'file_size', | |||
| width: 150, | |||
| width: 120, | |||
| render: tableCellRender(), | |||
| }, | |||
| { | |||
| @@ -200,7 +204,7 @@ function MirrorInfo() { | |||
| { | |||
| title: '操作', | |||
| dataIndex: 'operation', | |||
| width: 150, | |||
| width: 120, | |||
| key: 'operation', | |||
| hidden: isPublic, | |||
| render: (_: any, record: MirrorVersionData) => ( | |||
| @@ -5,7 +5,7 @@ | |||
| */ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| @@ -103,17 +103,14 @@ function MirrorList() { | |||
| const [res] = await to(deleteMirrorReq(id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| setPagination((prev) => { | |||
| return { | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getMirrorList(); | |||
| } | |||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||
| }; | |||
| }); | |||
| } | |||
| }; | |||
| @@ -128,7 +125,7 @@ function MirrorList() { | |||
| // 查看详情 | |||
| const toDetail = (record: MirrorData) => { | |||
| navigate(`/dataset/mirror/info/${record.id}`); | |||
| navigate(`info/${record.id}`); | |||
| setCacheState({ | |||
| activeTab, | |||
| pagination, | |||
| @@ -149,7 +146,7 @@ function MirrorList() { | |||
| // 创建镜像 | |||
| const createMirror = () => { | |||
| navigate(`/dataset/mirror/create`); | |||
| navigate(`create`); | |||
| SessionStorage.setItem(SessionStorage.mirrorNameKey, ''); | |||
| setCacheState({ | |||
| activeTab, | |||
| @@ -262,7 +259,7 @@ function MirrorList() { | |||
| onClick={createMirror} | |||
| icon={<KFIcon type="icon-xinjian2" />} | |||
| > | |||
| 制作镜像 | |||
| 创建镜像 | |||
| </Button> | |||
| )} | |||
| <Button | |||
| @@ -4,12 +4,12 @@ | |||
| * @Description: 模型演化 | |||
| */ | |||
| import { useEffectWhen } from '@/hooks'; | |||
| import { useEffectWhen } from '@/hooks/useEffectWhen'; | |||
| import { getModelAtlasReq } from '@/services/dataset/index.js'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import G6, { G6GraphEvent, Graph, INode } from '@antv/g6'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||
| import NodeTooltips from '../NodeTooltips'; | |||
| import styles from './index.less'; | |||
| @@ -73,17 +73,45 @@ function ModelEvolution({ | |||
| }; | |||
| }, []); | |||
| useEffectWhen( | |||
| () => { | |||
| if (version) { | |||
| getModelAtlas(); | |||
| } else { | |||
| clearGraphData(); | |||
| } | |||
| }, | |||
| isActive, | |||
| [resourceId, version], | |||
| ); | |||
| const getModelAtlas = useCallback(async () => { | |||
| // 请求失败或者版本不存在时,清除图形 | |||
| function clearGraphData() { | |||
| graph.data({ | |||
| nodes: [], | |||
| edges: [], | |||
| }); | |||
| graph.render(); | |||
| graph.fitView(); | |||
| } | |||
| if (!resourceId || !identifier || !version) { | |||
| clearGraphData(); | |||
| return; | |||
| } | |||
| const params = { | |||
| id: resourceId, | |||
| identifier, | |||
| version, | |||
| }; | |||
| const [res] = await to(getModelAtlasReq(params)); | |||
| if (res && res.data) { | |||
| const data = normalizeTreeData(res.data); | |||
| apiData.current = data; | |||
| hierarchyNodes.current = traverseHierarchically(data); | |||
| const graphData = getGraphData(data, hierarchyNodes.current); | |||
| graph.data(graphData); | |||
| graph.render(); | |||
| graph.fitView(); | |||
| setShowNodeTooltip(false); | |||
| setEnterTooltip(false); | |||
| } else { | |||
| clearGraphData(); | |||
| } | |||
| }, [resourceId, identifier, version]); | |||
| useEffectWhen(getModelAtlas, isActive, [resourceId, identifier, version]); | |||
| // 初始化图 | |||
| const initGraph = () => { | |||
| @@ -249,40 +277,6 @@ function ModelEvolution({ | |||
| }, 100); | |||
| }; | |||
| // 获取模型依赖 | |||
| const getModelAtlas = async () => { | |||
| const params = { | |||
| id: resourceId, | |||
| identifier, | |||
| version, | |||
| }; | |||
| const [res] = await to(getModelAtlasReq(params)); | |||
| if (res && res.data) { | |||
| const data = normalizeTreeData(res.data); | |||
| apiData.current = data; | |||
| hierarchyNodes.current = traverseHierarchically(data); | |||
| const graphData = getGraphData(data, hierarchyNodes.current); | |||
| graph.data(graphData); | |||
| graph.render(); | |||
| graph.fitView(); | |||
| setShowNodeTooltip(false); | |||
| setEnterTooltip(false); | |||
| } else { | |||
| clearGraphData(); | |||
| } | |||
| }; | |||
| // 请求失败或者版本不存在时,清除图形 | |||
| function clearGraphData() { | |||
| graph.data({ | |||
| nodes: [], | |||
| edges: [], | |||
| }); | |||
| graph.render(); | |||
| graph.fitView(); | |||
| } | |||
| return ( | |||
| <div className={styles['model-evolution']}> | |||
| <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | |||
| @@ -1,8 +1,9 @@ | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import TableColTitle from '@/components/TableColTitle'; | |||
| import { useCheck } from '@/hooks'; | |||
| import { useCheck } from '@/hooks/useCheck'; | |||
| import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset'; | |||
| import { tableSorter } from '@/utils'; | |||
| import { VersionChangedMessage } from '@/utils/constant'; | |||
| import { to } from '@/utils/promise'; | |||
| import tableCellRender from '@/utils/table'; | |||
| import { Checkbox, Flex, Table, type TablePaginationConfig, type TableProps } from 'antd'; | |||
| @@ -27,10 +28,10 @@ type ModelMetricsProps = { | |||
| resourceId: number; | |||
| identifier: string; | |||
| owner: string; | |||
| version: string; | |||
| version: string; // 当前版本 | |||
| }; | |||
| function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsProps) { | |||
| function ModelMetrics({ resourceId, identifier, owner, version, refreshTag }: ModelMetricsProps) { | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | |||
| current: 1, | |||
| pageSize: 10, | |||
| @@ -59,6 +60,24 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||
| checkSingleMetrics, | |||
| ] = useCheck(allMetricsNames); | |||
| // 新增,删除版本时,重置分页,然后刷新版本列表 | |||
| useEffect(() => { | |||
| const handleMessage = (e: MessageEvent) => { | |||
| const { type } = e.data; | |||
| if (type === VersionChangedMessage) { | |||
| setPagination({ | |||
| current: 1, | |||
| pageSize: 10, | |||
| }); | |||
| } | |||
| }; | |||
| window.addEventListener('message', handleMessage); | |||
| return () => { | |||
| window.removeEventListener('message', handleMessage); | |||
| }; | |||
| }, []); | |||
| useEffect(() => { | |||
| // 获取模型版本列表,带有参数和指标数据 | |||
| const getModelPageVersions = async () => { | |||
| @@ -128,6 +147,7 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||
| } | |||
| }; | |||
| // 行勾选 | |||
| const rowSelection: TableProps<TableData>['rowSelection'] = { | |||
| type: 'checkbox', | |||
| fixed: 'left', | |||
| @@ -140,6 +160,7 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||
| }), | |||
| }; | |||
| // 计算的表格数据 | |||
| const showTableData = useMemo(() => { | |||
| const index = tableData.findIndex((item) => item.name === version); | |||
| if (index !== -1) { | |||
| @@ -8,11 +8,11 @@ import PageTitle from '@/components/PageTitle'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys, serviceTypeOptions } from '@/enums'; | |||
| import { createServiceReq, getServiceInfoReq, updateServiceReq } from '@/services/modelDeployment'; | |||
| import { ServiceCreatedMessage } from '@/utils/constant'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate, useParams } from '@umijs/max'; | |||
| import { App, Button, Col, Form, Input, Row, Select } from 'antd'; | |||
| import { useEffect } from 'react'; | |||
| import { createServiceVersionMessage } from '../types'; | |||
| import styles from './index.less'; | |||
| // 表单数据 | |||
| @@ -63,7 +63,7 @@ function CreateService() { | |||
| navigate(-1); | |||
| if (!serviceId) { | |||
| setTimeout(() => { | |||
| window.postMessage({ type: createServiceVersionMessage, payload: res.data.id }); | |||
| window.postMessage({ type: ServiceCreatedMessage, payload: res.data.id }); | |||
| }, 500); | |||
| } | |||
| } | |||
| @@ -347,7 +347,7 @@ function CreateServiceVersion() { | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="resource" | |||
| name="computing_resource_id" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| @@ -6,7 +6,7 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { serviceTypeOptions } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| @@ -31,9 +31,9 @@ import { | |||
| CreateServiceVersionFrom, | |||
| ServiceData, | |||
| ServiceOperationType, | |||
| createServiceVersionMessage, | |||
| } from '../types'; | |||
| import styles from './index.less'; | |||
| import { ServiceCreatedMessage } from '@/utils/constant'; | |||
| const allServiceTypeOptions = [{ label: '全部', value: '' }, ...serviceTypeOptions]; | |||
| @@ -95,7 +95,7 @@ function ModelDeployment() { | |||
| useEffect(() => { | |||
| const handleMessage = (e: MessageEvent) => { | |||
| const { type, payload } = e.data; | |||
| if (type === createServiceVersionMessage) { | |||
| if (type === ServiceCreatedMessage) { | |||
| modalConfirm({ | |||
| title: '创建服务成功', | |||
| content: '是否创建服务版本?', | |||
| @@ -119,17 +119,14 @@ function ModelDeployment() { | |||
| const [res] = await to(deleteServiceReq(record.id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| setPagination((prev) => { | |||
| return { | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getServiceList(); | |||
| } | |||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||
| }; | |||
| }); | |||
| } | |||
| }; | |||
| @@ -18,6 +18,7 @@ | |||
| &__table { | |||
| flex: 1; | |||
| min-height: 0; | |||
| margin-top: 24px; | |||
| } | |||
| } | |||
| @@ -8,8 +8,8 @@ import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { ServiceRunStatus, serviceStatusOptions } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { | |||
| deleteServiceVersionReq, | |||
| getServiceInfoReq, | |||
| @@ -132,18 +132,15 @@ function ServiceInfo() { | |||
| const [res] = await to(deleteServiceVersionReq(record.id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| setPagination((prev) => { | |||
| return { | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getServiceInfo(); | |||
| getServiceVersions(); | |||
| } | |||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||
| }; | |||
| }); | |||
| getServiceInfo(); | |||
| } | |||
| }; | |||
| @@ -309,8 +306,8 @@ function ServiceInfo() { | |||
| }, | |||
| { | |||
| title: '资源规格', | |||
| dataIndex: 'resource', | |||
| key: 'resource', | |||
| dataIndex: 'computing_resource_id', | |||
| key: 'computing_resource_id', | |||
| width: '20%', | |||
| render: tableCellRender(true, TableCellValueType.Custom, { | |||
| format: getResourceDescription, | |||
| @@ -432,7 +429,7 @@ function ServiceInfo() { | |||
| onClick={() => createServiceVersion(ServiceOperationType.Create)} | |||
| icon={<KFIcon type="icon-xinjian2" />} | |||
| > | |||
| 新增版本 | |||
| 新增服务版本 | |||
| </Button> | |||
| <Button style={{ marginRight: '15px' }} type="default" onClick={handleVersionCompare}> | |||
| 版本对比 | |||
| @@ -6,14 +6,13 @@ | |||
| flex-direction: column; | |||
| height: calc(100% - 60px); | |||
| margin-top: 10px; | |||
| padding: 30px 30px 0; | |||
| padding: 10px 30px 0; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| &__tabs { | |||
| flex: 1; | |||
| min-height: 0; | |||
| margin-top: 20px; | |||
| padding-bottom: 10px; | |||
| :global { | |||
| @@ -3,9 +3,9 @@ | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 服务版本详情 | |||
| */ | |||
| import FullScreenFrame from '@/components/FullScreenFrame'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { getServiceVersionInfoReq } from '@/services/modelDeployment'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useParams } from '@umijs/max'; | |||
| @@ -18,6 +18,7 @@ import { ServiceVersionData } from '../types'; | |||
| import styles from './index.less'; | |||
| export enum ModelDeploymentTabKey { | |||
| Basic = 'Basic', // 基本信息 | |||
| Predict = 'Predict', // 预测 | |||
| Guide = 'Guide', // 调用指南 | |||
| Log = 'Log', // 服务日志 | |||
| @@ -43,10 +44,23 @@ function ServiceVersionInfo() { | |||
| }, [id]); | |||
| const tabItems = [ | |||
| { | |||
| key: ModelDeploymentTabKey.Basic, | |||
| label: '基本信息', | |||
| icon: <KFIcon type="icon-jibenxinxi" />, | |||
| children: <VersionBasicInfo info={versionInfo} />, | |||
| }, | |||
| { | |||
| key: ModelDeploymentTabKey.Predict, | |||
| label: '预测', | |||
| icon: <KFIcon type="icon-yuce" />, | |||
| children: ( | |||
| <div style={{ height: '100%', width: '100%' }}> | |||
| {versionInfo?.page_path && ( | |||
| <FullScreenFrame url={versionInfo?.page_path}></FullScreenFrame> | |||
| )} | |||
| </div> | |||
| ), | |||
| }, | |||
| { | |||
| key: ModelDeploymentTabKey.Guide, | |||
| @@ -66,12 +80,6 @@ function ServiceVersionInfo() { | |||
| <div className={styles['service-version-info']}> | |||
| <PageTitle title="服务版本详情"></PageTitle> | |||
| <div className={styles['service-version-info__content']}> | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| image={require('@/assets/img/mirror-basic.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| ></SubAreaTitle> | |||
| <VersionBasicInfo info={versionInfo} /> | |||
| <div className={styles['service-version-info__content__tabs']}> | |||
| <Tabs items={tabItems} /> | |||
| </div> | |||
| @@ -1,6 +1,6 @@ | |||
| import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; | |||
| import { ServiceRunStatus } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { ServiceVersionData } from '@/pages/ModelDeployment/types'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { formatCodeConfig, formatModel } from '@/utils/format'; | |||
| @@ -68,7 +68,7 @@ function VersionBasicInfo({ info }: BasicInfoProps) { | |||
| }, | |||
| { | |||
| label: '资源规格', | |||
| value: info?.resource, | |||
| value: info?.computing_resource_id, | |||
| format: getResourceDescription, | |||
| }, | |||
| { | |||
| @@ -79,6 +79,10 @@ function VersionBasicInfo({ info }: BasicInfoProps) { | |||
| label: 'API URL', | |||
| value: info?.url, | |||
| }, | |||
| { | |||
| label: '文档地址', | |||
| value: info?.doc_path, | |||
| }, | |||
| { | |||
| label: '副本数量', | |||
| value: info?.replicas, | |||
| @@ -104,7 +108,14 @@ function VersionBasicInfo({ info }: BasicInfoProps) { | |||
| }, | |||
| ]; | |||
| return <BasicInfo datas={datas} labelWidth={66} labelAlign="justify"></BasicInfo>; | |||
| return ( | |||
| <BasicInfo | |||
| datas={datas} | |||
| labelWidth={66} | |||
| labelAlign="justify" | |||
| style={{ marginTop: 10 }} | |||
| ></BasicInfo> | |||
| ); | |||
| } | |||
| export default VersionBasicInfo; | |||
| @@ -1,6 +1,6 @@ | |||
| import KFModal from '@/components/KFModal'; | |||
| import { ServiceRunStatus } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { type ServiceVersionData } from '@/pages/ModelDeployment/types'; | |||
| import { getServiceVersionCompareReq } from '@/services/modelDeployment'; | |||
| import { isEmpty } from '@/utils'; | |||
| @@ -24,7 +24,7 @@ export type ServiceVersionData = { | |||
| run_state: ServiceRunStatus; // 运行状态 | |||
| image: string; // 镜像 | |||
| replicas: number; // 副本数 | |||
| resource: string; // 资源 | |||
| computing_resource_id: number; // 资源 | |||
| mount_path: string; // 挂载路径 | |||
| model: { | |||
| // 模型 | |||
| @@ -49,6 +49,8 @@ export type ServiceVersionData = { | |||
| update_time: string; | |||
| create_time: string; | |||
| created_by: string; | |||
| doc_path?: string; // 文档地址 | |||
| page_path?: string; // 预测地址 | |||
| }; | |||
| // 操作类型 | |||
| @@ -63,6 +65,3 @@ export enum CreateServiceVersionFrom { | |||
| CreateService = 'CreateService', // 来自创建服务 | |||
| ServiceInfo = 'ServiceInfo', // 来自服务详情 | |||
| } | |||
| // 去创建服务版本消息 | |||
| export const createServiceVersionMessage = 'createServiceVersion'; | |||
| @@ -1,5 +1,6 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { useStateRef, useVisible } from '@/hooks'; | |||
| import { useStateRef } from '@/hooks/useStateRef'; | |||
| import { useVisible } from '@/hooks/useVisible'; | |||
| import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { fittingString, parseJsonText, s8 } from '@/utils'; | |||
| @@ -136,31 +136,35 @@ const GlobalParamsDrawer = forwardRef( | |||
| cur.global_param?.[name]?.param_type | |||
| } | |||
| > | |||
| {({ getFieldValue }) => ( | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, 'param_value']} | |||
| label="值" | |||
| rules={getParamRules( | |||
| getFieldValue(['global_param', name, 'param_type']), | |||
| true, | |||
| )} | |||
| > | |||
| {getParamComponent(getFieldValue(['global_param', name, 'param_type']))} | |||
| </Form.Item> | |||
| )} | |||
| </Form.Item> | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, 'is_sensitive']} | |||
| label="脱敏显示" | |||
| rules={[{ required: true, message: '请选择' }]} | |||
| tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示" | |||
| > | |||
| <Radio.Group> | |||
| <Radio value={1}>是</Radio> | |||
| <Radio value={0}>否</Radio> | |||
| </Radio.Group> | |||
| {({ getFieldValue }) => { | |||
| const type = getFieldValue(['global_param', name, 'param_type']); | |||
| return ( | |||
| <> | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, 'param_value']} | |||
| label="值" | |||
| rules={getParamRules(type, true)} | |||
| > | |||
| {getParamComponent(type)} | |||
| </Form.Item> | |||
| {type !== 3 && ( | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, 'is_sensitive']} | |||
| label="脱敏显示" | |||
| rules={[{ required: true, message: '请选择' }]} | |||
| tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示" | |||
| > | |||
| <Radio.Group> | |||
| <Radio value={1}>是</Radio> | |||
| <Radio value={0}>否</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| )} | |||
| </> | |||
| ); | |||
| }} | |||
| </Form.Item> | |||
| <Tooltip title="删除参数"> | |||
| <Button | |||
| @@ -1,7 +1,7 @@ | |||
| import CodeSelectorModal from '@/components/CodeSelectorModal'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import ParameterInput, { requiredValidator } from '@/components/ParameterInput'; | |||
| import ParameterSelect from '@/components/ParameterSelect'; | |||
| import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect'; | |||
| import ResourceSelectorModal, { | |||
| ResourceSelectorType, | |||
| selectorTypeConfig, | |||
| @@ -520,7 +520,8 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| {item.value.type === 'select' ? ( | |||
| ['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | |||
| <ParameterSelect | |||
| dataType={item.value.item_type as any} | |||
| isPipeline | |||
| dataType={item.value.item_type as ParameterSelectDataType} | |||
| placeholder={item.value.placeholder} | |||
| /> | |||
| ) : null | |||
| @@ -1,7 +1,7 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { | |||
| addWorkflow, | |||
| cloneWorkflow, | |||
| @@ -11,6 +11,7 @@ import { | |||
| removeWorkflow, | |||
| } from '@/services/pipeline/index.js'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { App, Button, ConfigProvider, Form, Input, Space, Table } from 'antd'; | |||
| @@ -127,6 +128,47 @@ const Pipeline = () => { | |||
| } | |||
| }; | |||
| // 处理删除 | |||
| const handlePipelineDelete = (record) => { | |||
| modalConfirm({ | |||
| title: '删除后,该流水线将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: async () => { | |||
| const { id } = record; | |||
| const [res] = await to(removeWorkflow(id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除后,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| setPagination((prev) => { | |||
| return { | |||
| ...prev, | |||
| current: pipeList.length === 1 ? Math.max(1, prev.current - 1) : prev.current, | |||
| }; | |||
| }); | |||
| } | |||
| }, | |||
| }); | |||
| }; | |||
| // 处理复制 | |||
| const handlePipelineCopy = (record) => { | |||
| modalConfirm({ | |||
| title: '确定复制该条流水线吗?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| isDelete: false, | |||
| onOk: async () => { | |||
| const { id } = record; | |||
| const [res] = await to(cloneWorkflow(id)); | |||
| if (res) { | |||
| message.success('复制成功'); | |||
| getList(); | |||
| } | |||
| }, | |||
| }); | |||
| }; | |||
| // 当前页面切换 | |||
| const paginationChange = async (current, pageSize) => { | |||
| setPagination({ | |||
| @@ -199,30 +241,7 @@ const Pipeline = () => { | |||
| size="small" | |||
| key="clone" | |||
| icon={<KFIcon type="icon-fuzhi" />} | |||
| onClick={async () => { | |||
| modalConfirm({ | |||
| title: '确定复制该条流水线吗?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| isDelete: false, | |||
| onOk: () => { | |||
| cloneWorkflow(record.id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('复制成功'); | |||
| getList(); | |||
| } else { | |||
| message.error('复制失败'); | |||
| } | |||
| }); | |||
| // if (success) { | |||
| // if (actionRef.current) { | |||
| // actionRef.current.reload(); | |||
| // } | |||
| // } | |||
| }, | |||
| }); | |||
| }} | |||
| onClick={() => handlePipelineCopy(record)} | |||
| > | |||
| 复制 | |||
| </Button> | |||
| @@ -238,28 +257,7 @@ const Pipeline = () => { | |||
| size="small" | |||
| key="batchRemove" | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| onClick={() => { | |||
| modalConfirm({ | |||
| title: '删除后,该流水线将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| removeWorkflow(record.id).then((ret) => { | |||
| if (ret.code === 200) { | |||
| message.success('删除成功'); | |||
| getList(); | |||
| } else { | |||
| message.error(ret.msg); | |||
| } | |||
| }); | |||
| // if (success) { | |||
| // if (actionRef.current) { | |||
| // actionRef.current.reload(); | |||
| // } | |||
| // } | |||
| }, | |||
| }); | |||
| }} | |||
| onClick={() => handlePipelineDelete(record)} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| @@ -0,0 +1,31 @@ | |||
| .statistics { | |||
| display: flex; | |||
| align-items: center; | |||
| width: 100%; | |||
| padding: 20px 0; | |||
| background-color: @background-color; | |||
| border-radius: 8px; | |||
| &__item { | |||
| display: flex; | |||
| flex: 1; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| &--border { | |||
| border-right: 1px solid @border-color; | |||
| } | |||
| &__value { | |||
| margin-bottom: 8px; | |||
| color: @text-color; | |||
| font-weight: bold; | |||
| font-size: 30px; | |||
| } | |||
| &__title { | |||
| color: #999999; | |||
| font-size: @font-size-input; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,38 @@ | |||
| import classNames from 'classnames'; | |||
| import styles from './index.less'; | |||
| type StatisticsProps = { | |||
| remaining?: number; | |||
| consuming?: number; | |||
| }; | |||
| function Statistics({ remaining, consuming }: StatisticsProps) { | |||
| const items = [ | |||
| { | |||
| title: '当前可用算力积分(分)', | |||
| value: remaining ?? '-', | |||
| }, | |||
| { | |||
| title: '总消耗算力积分(分)', | |||
| value: consuming ?? '-', | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['statistics']}> | |||
| {items.map((item, index) => ( | |||
| <div | |||
| key={item.title} | |||
| className={classNames(styles['statistics__item'], { | |||
| [styles['statistics__item--border']]: index === 0, | |||
| })} | |||
| > | |||
| <span className={styles['statistics__item__value']}>{item.value}</span> | |||
| <span className={styles['statistics__item__title']}>{item.title}</span> | |||
| </div> | |||
| ))} | |||
| </div> | |||
| ); | |||
| } | |||
| export default Statistics; | |||
| @@ -0,0 +1,19 @@ | |||
| .points-detail { | |||
| height: 100%; | |||
| &__content { | |||
| height: calc(100% - 60px); | |||
| margin-top: 10px; | |||
| padding: 20px 30px 0; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| &__top { | |||
| width: 100%; | |||
| } | |||
| &__table { | |||
| height: calc(100% - 117px - 28px); | |||
| margin-top: 28px; | |||
| } | |||
| } | |||
| } | |||