Compare commits
282 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
d779e31761 | |
|
|
b7429538a2 | |
|
|
67764ad21c | |
|
|
a006b6deed | |
|
|
8f015d4d3f | |
|
|
08a1d46102 | |
|
|
3949bea38e | |
|
|
36b50365a1 | |
|
|
f76dc346e2 | |
|
|
af78389c11 | |
|
|
156c791d57 | |
|
|
4fe42ede4e | |
|
|
c286cd5870 | |
|
|
3a8ae0e131 | |
|
|
e91aade6af | |
|
|
ea5abbfbba | |
|
|
8ed4e3d75d | |
|
|
fdad983d9a | |
|
|
124d970003 | |
|
|
b4be088a85 | |
|
|
35f7f4aa5e | |
|
|
18edf9d512 | |
|
|
b2bc1ec7bc | |
|
|
6d2a84aa93 | |
|
|
284115b499 | |
|
|
b11b4d9f78 | |
|
|
fb863bf1de | |
|
|
94555dea40 | |
|
|
089b13aafb | |
|
|
163e9bab8d | |
|
|
2f618703e4 | |
|
|
24798fa4dd | |
|
|
982034e707 | |
|
|
879f1b7966 | |
|
|
dfefc792c4 | |
|
|
7fad3190eb | |
|
|
cc567ce45a | |
|
|
7985f9f1a1 | |
|
|
e8cfaaea05 | |
|
|
ec6dadd55c | |
|
|
561d414514 | |
|
|
bf77aff1dd | |
|
|
4670b2bb5e | |
|
|
3ea8756d61 | |
|
|
fcb6664e09 | |
|
|
b75dbf88df | |
|
|
05860a9325 | |
|
|
e8af394523 | |
|
|
1abf90da28 | |
|
|
eafca94b60 | |
|
|
bb26e7291f | |
|
|
cd4071149b | |
|
|
f11582bc64 | |
|
|
a1f9d10617 | |
|
|
fddb63d293 | |
|
|
6b8eb0fb33 | |
|
|
5a402acd44 | |
|
|
8803f78bde | |
|
|
9baf28b312 | |
|
|
0f316b2b80 | |
|
|
0b0ccd26d5 | |
|
|
fd7f0008c8 | |
|
|
7257c82f0e | |
|
|
531beedac6 | |
|
|
0e8efbb692 | |
|
|
742be0bb03 | |
|
|
512aa61b05 | |
|
|
b6273098c6 | |
|
|
921bc1d49d | |
|
|
78eb6ee12f | |
|
|
57a0ec1040 | |
|
|
154c834223 | |
|
|
560ff3411c | |
|
|
bd269cffbf | |
|
|
6646aff405 | |
|
|
944c3cc919 | |
|
|
1b497e1743 | |
|
|
ff69531b22 | |
|
|
399d611607 | |
|
|
8e024c9813 | |
|
|
5c9f59887c | |
|
|
b3e5eb08e9 | |
|
|
6efe25a3f0 | |
|
|
548016091e | |
|
|
5d61ade863 | |
|
|
458621fe91 | |
|
|
55b49047c3 | |
|
|
597e16f81f | |
|
|
aef75f4a37 | |
|
|
153b7de3ec | |
|
|
4f415719f8 | |
|
|
aa410acdfe | |
|
|
4252f1710d | |
|
|
23af257179 | |
|
|
f0b70feca7 | |
|
|
b014c9ce92 | |
|
|
976fb1dce5 | |
|
|
3a5845b623 | |
|
|
b811bb51cd | |
|
|
90c958b974 | |
|
|
19a4d6aed3 | |
|
|
6e27e5da0d | |
|
|
3d0ea6603f | |
|
|
dc7e8dc801 | |
|
|
8e1f5fc587 | |
|
|
746314e5d2 | |
|
|
d169d3d9db | |
|
|
7a939f3f29 | |
|
|
dc6cf41651 | |
|
|
17c1c86043 | |
|
|
a46a948760 | |
|
|
40ca029363 | |
|
|
767a208732 | |
|
|
b314476ac3 | |
|
|
39c0c2c01a | |
|
|
873dd0ed5e | |
|
|
2a101d7f47 | |
|
|
bcff86269b | |
|
|
b2b74686ca | |
|
|
316cca31a4 | |
|
|
d1c41934b0 | |
|
|
274b8612e9 | |
|
|
df7460a01b | |
|
|
48d46a0723 | |
|
|
8f72953683 | |
|
|
62bd8049ba | |
|
|
1ec43a60cf | |
|
|
c42cf77939 | |
|
|
85c8a3e6dc | |
|
|
96bf3351db | |
|
|
b2ac5877d0 | |
|
|
0bace90a23 | |
|
|
4c73d4339d | |
|
|
ef9a78b167 | |
|
|
68ad21fadf | |
|
|
696f939295 | |
|
|
cc98a699d4 | |
|
|
c11c728c71 | |
|
|
31334c3a11 | |
|
|
3066853aeb | |
|
|
f3f9846dff | |
|
|
7715ee272d | |
|
|
65c588ac8a | |
|
|
34e2b8bb05 | |
|
|
ff07d4c4a8 | |
|
|
6ad1910cb0 | |
|
|
879c2e5802 | |
|
|
50dce4003f | |
|
|
e4385544b1 | |
|
|
4a6f4d2120 | |
|
|
8ef236f5b4 | |
|
|
a5914e67c8 | |
|
|
cccb2ae8c2 | |
|
|
e5978fde0c | |
|
|
e76a0d7544 | |
|
|
d19f0cfa82 | |
|
|
d3508e6eba | |
|
|
b42a63f209 | |
|
|
603b943699 | |
|
|
c8fc258089 | |
|
|
53fe983462 | |
|
|
1b63a74ce0 | |
|
|
c4a3275358 | |
|
|
fc026e9d15 | |
|
|
9d3e0ad5f3 | |
|
|
a2555f6ac3 | |
|
|
fefc545dad | |
|
|
b419ab9485 | |
|
|
16d4b476f2 | |
|
|
b8049721df | |
|
|
272ec6ef97 | |
|
|
46eff25e01 | |
|
|
dcc591cacd | |
|
|
4da7042f61 | |
|
|
236ac4da0f | |
|
|
2b0c11525b | |
|
|
67a2b41240 | |
|
|
7de2295191 | |
|
|
0ac624ed2a | |
|
|
277ed0a710 | |
|
|
2aaa8a9fe7 | |
|
|
19b797189d | |
|
|
2d5538251e | |
|
|
b142d5985d | |
|
|
14a9629167 | |
|
|
48fdf61dff | |
|
|
48851a2d4b | |
|
|
5fe010f52d | |
|
|
e6f74dc513 | |
|
|
e4ffcea914 | |
|
|
44673f39ed | |
|
|
88227a85cb | |
|
|
a0d17e6dd3 | |
|
|
adf3b8d02a | |
|
|
596aa80315 | |
|
|
7b146651c7 | |
|
|
eb50e76a54 | |
|
|
a2f1c0532b | |
|
|
694f142b3f | |
|
|
cdceefcb24 | |
|
|
1cfbd5185c | |
|
|
bee9f43762 | |
|
|
aa3909a047 | |
|
|
111ade2f49 | |
|
|
6670aa0658 | |
|
|
6748c46d6f | |
|
|
d6ed84ac59 | |
|
|
5a7a188478 | |
|
|
5d641cb61b | |
|
|
6809f62b9c | |
|
|
8110739002 | |
|
|
1fd40f927c | |
|
|
b02d15662f | |
|
|
20b1e78df7 | |
|
|
d7ddcefb96 | |
|
|
1f08241ca9 | |
|
|
b4873208c2 | |
|
|
25381c26ae | |
|
|
567263b11a | |
|
|
cdb3fafd14 | |
|
|
25dec31eaa | |
|
|
5b8a10006d | |
|
|
b0d8a19975 | |
|
|
f5d4158532 | |
|
|
dcb73eacf9 | |
|
|
84dbb1e8fb | |
|
|
95cfbefda4 | |
|
|
ae86b2a835 | |
|
|
68ee173591 | |
|
|
71edeb6922 | |
|
|
a4b82fb9f5 | |
|
|
9a89988e95 | |
|
|
b9f4c48ea6 | |
|
|
8270406d3c | |
|
|
870fbce684 | |
|
|
7a4852908b | |
|
|
ce0d898af8 | |
|
|
b5fd8eb031 | |
|
|
b9b0db8442 | |
|
|
a2cf8fe75e | |
|
|
6bb5a9e8e6 | |
|
|
aa980985d5 | |
|
|
b4d99f8e5c | |
|
|
5d49e2d1b5 | |
|
|
20ce4e4758 | |
|
|
b994fb3f31 | |
|
|
c0320a2a68 | |
|
|
41c2faf9cb | |
|
|
e92ac40694 | |
|
|
45990fa2b7 | |
|
|
9c62812424 | |
|
|
d1928d702b | |
|
|
eea826de2c | |
|
|
55fd9d7271 | |
|
|
3905841db7 | |
|
|
0a7be4e261 | |
|
|
47f2c80a00 | |
|
|
9def5a4d7a | |
|
|
7fc016c3a0 | |
|
|
260812125a | |
|
|
22f85fb3ae | |
|
|
765f37ab7c | |
|
|
57296d364e | |
|
|
04a9f8f125 | |
|
|
640c49f507 | |
|
|
85c92e8fae | |
|
|
7b51ed5bc6 | |
|
|
b78b0075cd | |
|
|
e4dd26c87d | |
|
|
f5f3aca3e5 | |
|
|
5aa9cdc62f | |
|
|
5d60751a66 | |
|
|
0e54f6e95d | |
|
|
bc18fb14c2 | |
|
|
c103375c8c | |
|
|
ebd1d8680a | |
|
|
ac95c975a5 | |
|
|
c16c9d75a3 | |
|
|
b6f0ffbfe1 | |
|
|
039ad81fc7 | |
|
|
e553a21c2f | |
|
|
c719141676 |
|
|
@ -36,7 +36,7 @@ http {
|
|||
|
||||
location /minio/ {
|
||||
# rewrite ^/label-studio/(.*)$ /$1 break;
|
||||
proxy_pass http://juicefs-s3-gateway.juicefs.svc:9000/;
|
||||
proxy_pass http://juicefs-gateway-flint-storage.default.svc:30090/;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options ALLOWALL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ spec:
|
|||
image: ${k8s-5auth-image}
|
||||
ports:
|
||||
- containerPort: 9200
|
||||
env:
|
||||
- name: TZ
|
||||
value: Asia/Shanghai
|
||||
- name: JAVA_TOOL_OPTIONS
|
||||
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
|
|
@ -28,9 +33,15 @@ metadata:
|
|||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 9200
|
||||
- name: http
|
||||
port: 9200
|
||||
nodePort: 31206
|
||||
protocol: TCP
|
||||
- name: debug
|
||||
nodePort: 31221
|
||||
port: 5005
|
||||
protocol: TCP
|
||||
targetPort: 5005
|
||||
selector:
|
||||
app: ci4s-auth
|
||||
|
||||
|
|
|
|||
|
|
@ -141,12 +141,23 @@ export default [
|
|||
{
|
||||
name: '实验对比',
|
||||
path: 'compare',
|
||||
component: './Experiment/Comparison/index',
|
||||
routes: [
|
||||
{
|
||||
name: '实验对比',
|
||||
path: '',
|
||||
component: './Experiment/Comparison/index',
|
||||
},
|
||||
{
|
||||
name: '可视化对比',
|
||||
path: 'compare-visual',
|
||||
component: './Experiment/Aim/index',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '实验可视化对比',
|
||||
path: 'compare-visual',
|
||||
component: './Experiment/Aim/index',
|
||||
name: '可视化',
|
||||
path: 'visual',
|
||||
component: './Experiment/Tensorboard/index',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -218,7 +229,18 @@ export default [
|
|||
{
|
||||
name: '实验实例详情',
|
||||
path: 'instance/:experimentId/:id',
|
||||
component: './HyperParameter/Instance/index',
|
||||
routes: [
|
||||
{
|
||||
name: '实验实例详情',
|
||||
path: '',
|
||||
component: './HyperParameter/Instance/index',
|
||||
},
|
||||
{
|
||||
name: '可视化对比',
|
||||
path: 'compare-visual',
|
||||
component: './HyperParameter/Aim/index',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -21,6 +21,7 @@ import {
|
|||
} from './services/session';
|
||||
import './styles/menu.less';
|
||||
import { needAuth } from './utils';
|
||||
// import { closeAllModals } from './utils/modal';
|
||||
import { gotoLoginPage } from './utils/ui';
|
||||
export { requestConfig as request } from './requestConfig';
|
||||
|
||||
|
|
@ -96,6 +97,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
|
|||
},
|
||||
onPageChange: () => {
|
||||
const { location } = history;
|
||||
// closeAllModals();
|
||||
// 如果没有登录,重定向到 login
|
||||
if (!initialState?.currentUser && needAuth(location.pathname)) {
|
||||
gotoLoginPage();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import FullScreenFrame from '@/components/FullScreenFrame';
|
||||
import KFSpin from '@/components/KFSpin';
|
||||
import { getKnowledgeGraphUrl, getLabelStudioUrl } from '@/services/developmentEnvironment';
|
||||
import Loading from '@/utils/loading';
|
||||
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';
|
||||
import './index.less';
|
||||
|
||||
export enum IframePageType {
|
||||
|
|
@ -54,9 +53,13 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
|
|||
|
||||
type IframePageProps = {
|
||||
/** 子系统 */
|
||||
type: IframePageType;
|
||||
type?: IframePageType;
|
||||
/** url */
|
||||
url?: string;
|
||||
/** 是否可以在页签上打开 */
|
||||
openInTab?: boolean;
|
||||
/** 是否显示加载 */
|
||||
showLoading?: boolean;
|
||||
/** 自定义样式类名 */
|
||||
className?: string;
|
||||
/** 自定义样式 */
|
||||
|
|
@ -64,32 +67,60 @@ type IframePageProps = {
|
|||
};
|
||||
|
||||
/** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */
|
||||
function IframePage({ type, openInTab = false, className, style }: IframePageProps) {
|
||||
function IframePage({
|
||||
type,
|
||||
url,
|
||||
showLoading = true,
|
||||
openInTab = false,
|
||||
className,
|
||||
style,
|
||||
}: IframePageProps) {
|
||||
const [iframeUrl, setIframeUrl] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
// const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const requestIframeUrl = async () => {
|
||||
setLoading(true);
|
||||
const requestIframeUrl = async (type: IframePageType) => {
|
||||
if (showLoading) {
|
||||
Loading.show();
|
||||
}
|
||||
const [res] = await to(getRequestAPI(type)());
|
||||
if (res && res.data) {
|
||||
setIframeUrl(res.data);
|
||||
} else {
|
||||
setLoading(false);
|
||||
if (showLoading) {
|
||||
Loading.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
requestIframeUrl();
|
||||
}, [type]);
|
||||
if (type) {
|
||||
requestIframeUrl(type);
|
||||
} else if (url) {
|
||||
if (showLoading) {
|
||||
Loading.show();
|
||||
}
|
||||
|
||||
const hideLoading = () => {
|
||||
setLoading(false);
|
||||
setIframeUrl(url);
|
||||
}
|
||||
}, [type, url, showLoading]);
|
||||
|
||||
const handleLoad = () => {
|
||||
if (showLoading) {
|
||||
Loading.hide();
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = (error?: React.SyntheticEvent<HTMLIFrameElement, Event>) => {
|
||||
console.log('error', error);
|
||||
if (showLoading) {
|
||||
Loading.hide();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames('kf-iframe-page', className)} style={style}>
|
||||
{loading && createPortal(<KFSpin size="large" />, document.body)}
|
||||
<FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} />
|
||||
{/* {loading && createPortal(<KFSpin size="large" />, document.body)} */}
|
||||
{iframeUrl && <FullScreenFrame url={iframeUrl} onLoad={handleLoad} onError={handleError} />}
|
||||
{openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import datasetImg from '@/assets/img/modal-select-dataset.png';
|
||||
import mirrorImg from '@/assets/img/modal-select-mirror.png';
|
||||
import modelImg from '@/assets/img/modal-select-model.png';
|
||||
import { AvailableRange, CommonTabKeys } from '@/enums';
|
||||
import { AvailableRange, CommonTabKeys, MirrorVersionStatus } from '@/enums';
|
||||
import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config';
|
||||
import { MirrorVersionData } from '@/pages/Mirror/Info';
|
||||
import { MirrorData } from '@/pages/Mirror/List';
|
||||
|
|
@ -24,11 +24,11 @@ export enum ResourceSelectorType {
|
|||
}
|
||||
|
||||
// 数据集、模型列表转为树形结构
|
||||
const convertDatasetToTreeData = (list: ResourceData[]): TreeDataNode[] => {
|
||||
const convertDatasetToTreeData = (list: ResourceData[], isPublic: boolean): TreeDataNode[] => {
|
||||
return list.map((v) => ({
|
||||
...v,
|
||||
key: `${v.id}`,
|
||||
title: v.name,
|
||||
title: isPublic ? `${v.name} (${v.owner})` : v.name,
|
||||
isLeaf: false,
|
||||
checkable: false,
|
||||
}));
|
||||
|
|
@ -106,7 +106,7 @@ export class DatasetSelector implements SelectorTypeInfo {
|
|||
const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 });
|
||||
if (res && res.data) {
|
||||
const list = res.data.content || [];
|
||||
return convertDatasetToTreeData(list);
|
||||
return convertDatasetToTreeData(list, isPublic);
|
||||
} else {
|
||||
return Promise.reject('获取数据集列表失败');
|
||||
}
|
||||
|
|
@ -158,7 +158,7 @@ export class ModelSelector implements SelectorTypeInfo {
|
|||
const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 });
|
||||
if (res && res.data) {
|
||||
const list = res.data.content || [];
|
||||
return convertDatasetToTreeData(list);
|
||||
return convertDatasetToTreeData(list, isPublic);
|
||||
} else {
|
||||
return Promise.reject('获取模型列表失败');
|
||||
}
|
||||
|
|
@ -224,8 +224,7 @@ export class MirrorSelector implements SelectorTypeInfo {
|
|||
image_id: parentKey,
|
||||
page: 0,
|
||||
size: 2000,
|
||||
status: 'available',
|
||||
state: 1,
|
||||
status: MirrorVersionStatus.Available,
|
||||
});
|
||||
if (res && res.data) {
|
||||
const list = res.data.content || [];
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { clearSessionToken } from '@/access';
|
||||
import { getLabelStudioUrl } from '@/services/developmentEnvironment';
|
||||
import { setRemoteMenu } from '@/services/session';
|
||||
import { logout } from '@/services/system/auth';
|
||||
import { ClientInfo } from '@/types';
|
||||
import { sleep } from '@/utils/promise';
|
||||
import { sleep, to } from '@/utils/promise';
|
||||
import SessionStorage from '@/utils/sessionStorage';
|
||||
import { gotoLoginPage, oauthLogout } from '@/utils/ui';
|
||||
import { 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';
|
||||
|
|
@ -63,17 +64,23 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
|
|||
* 退出登录,并且将当前的 url 保存
|
||||
*/
|
||||
const loginOut = async () => {
|
||||
oauthLogout('http://172.20.32.197:31209/oauth/logout');
|
||||
const [res] = await to(getLabelStudioUrl());
|
||||
if (res && res.data) {
|
||||
oauthLogout(`${res.data}/oauth/logout`);
|
||||
}
|
||||
// 至少 1 秒后跳转,希望子系统能完成注销
|
||||
await Promise.all([logout(), sleep(1000)]);
|
||||
clearSessionToken();
|
||||
setRemoteMenu(null);
|
||||
gotoLoginPage();
|
||||
// 退出 oauth2
|
||||
const clientInfo: ClientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true);
|
||||
if (clientInfo) {
|
||||
const { logoutUri } = clientInfo;
|
||||
location.replace(logoutUri);
|
||||
}
|
||||
// setTimeout(() => {
|
||||
// gotoLoginPage();
|
||||
// }, 100);
|
||||
};
|
||||
const actionClassName = useEmotionCss(({ token }) => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -10,13 +10,21 @@ type RunDurationProps = {
|
|||
};
|
||||
function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) {
|
||||
const [now] = useServerTime();
|
||||
const [currentTime, setCurrentTime] = useState<Date>(now());
|
||||
const [currentTime, setCurrentTime] = useState<Date>(finishTime ? new Date(finishTime) : now());
|
||||
|
||||
// console.log(
|
||||
// 'currentTime',
|
||||
// new Date(createTime ?? 0),
|
||||
// currentTime,
|
||||
// (currentTime.getTime() - new Date(createTime ?? 0).getTime()) / 1000,
|
||||
// );
|
||||
|
||||
// 定时刷新耗时
|
||||
useEffect(() => {
|
||||
if (finishTime) {
|
||||
setCurrentTime(new Date(finishTime));
|
||||
} else {
|
||||
setCurrentTime(now());
|
||||
const timer = setInterval(() => {
|
||||
setCurrentTime(now());
|
||||
}, 1000);
|
||||
|
|
@ -25,6 +33,7 @@ function RunDuration({ createTime, finishTime, className, style }: RunDurationPr
|
|||
};
|
||||
}
|
||||
}, [finishTime, now]);
|
||||
|
||||
return (
|
||||
<span className={className} style={style}>
|
||||
{elapsedTime(createTime, currentTime)}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export enum TensorBoardStatus {
|
|||
Unknown = 'Unknown', // 未知
|
||||
Pending = 'Pending', // 启动中
|
||||
Running = 'Running', // 运行中
|
||||
Terminated = 'Terminated', // 未启动或者已终止
|
||||
Terminated = 'Terminated', // 未启动
|
||||
Failed = 'Failed', // 失败
|
||||
}
|
||||
|
||||
|
|
@ -95,8 +95,8 @@ export enum AutoMLType {
|
|||
|
||||
export const autoMLTypeOptions = [
|
||||
{ label: '表格', value: AutoMLType.Table },
|
||||
{ label: '文本分类', value: AutoMLType.Text },
|
||||
{ label: '视频分类', value: AutoMLType.Video },
|
||||
{ label: '文本', value: AutoMLType.Text },
|
||||
{ label: '视频', value: AutoMLType.Video },
|
||||
];
|
||||
|
||||
// 自动化任务类型
|
||||
|
|
|
|||
|
|
@ -1,11 +1,24 @@
|
|||
import { parseJsonText } from '@/utils';
|
||||
import { useEffect } from 'react';
|
||||
import { ExperimentStatus } from '@/enums';
|
||||
import { NodeStatus } from '@/types';
|
||||
import { parseJsonText } from '@/utils';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export type MessageHandler = (experimentInsId: number, status: string, finishedAt: string, nodes: Record<string, NodeStatus>) => void
|
||||
export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: string, namespace: string, onMessage: MessageHandler) => {
|
||||
const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running
|
||||
export type MessageHandler = (
|
||||
experimentId: number,
|
||||
experimentInsId: number,
|
||||
status: string,
|
||||
finishTime: string,
|
||||
nodes: Record<string, NodeStatus>,
|
||||
) => void;
|
||||
export const useSSE = (
|
||||
experimentId: number,
|
||||
experimentInsId: number,
|
||||
status: ExperimentStatus,
|
||||
name: string,
|
||||
namespace: string,
|
||||
onMessage: MessageHandler,
|
||||
) => {
|
||||
const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running;
|
||||
useEffect(() => {
|
||||
if (isRunning) {
|
||||
const { origin } = location;
|
||||
|
|
@ -22,8 +35,8 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name:
|
|||
const dataJson = parseJsonText(data);
|
||||
const statusData = dataJson?.result?.object?.status;
|
||||
if (statusData) {
|
||||
const { finishedAt, phase, nodes } = statusData;
|
||||
onMessage(experimentInsId, phase, finishedAt, nodes);
|
||||
const { finishedAt, phase, nodes } = statusData;
|
||||
onMessage(experimentId, experimentInsId, phase, finishedAt, nodes);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -33,8 +46,7 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name:
|
|||
|
||||
return () => {
|
||||
evtSource.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}, [experimentInsId, isRunning, name, namespace, onMessage]);
|
||||
}, [experimentId, experimentInsId, isRunning, name, namespace, onMessage]);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -51,13 +51,12 @@ function ActiveLearnInstance() {
|
|||
const [res] = await to(getActiveLearnInsReq(instanceId));
|
||||
if (res && res.data) {
|
||||
const info = res.data as ActiveLearnInstanceData;
|
||||
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info;
|
||||
const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
|
||||
// 解析配置参数
|
||||
const paramJson = parseJsonText(param);
|
||||
if (paramJson) {
|
||||
setExperimentInfo({
|
||||
...paramJson.data,
|
||||
create_time,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +68,7 @@ function ActiveLearnInstance() {
|
|||
return;
|
||||
}
|
||||
|
||||
// 进行节点状态
|
||||
// 设置总 workflow 状态
|
||||
const nodeStatusJson = parseJsonText(node_status);
|
||||
if (nodeStatusJson) {
|
||||
setNodes(nodeStatusJson);
|
||||
|
|
@ -106,18 +105,17 @@ function ActiveLearnInstance() {
|
|||
if (dataJson) {
|
||||
const nodes = dataJson?.result?.object?.status?.nodes;
|
||||
if (nodes) {
|
||||
// 节点
|
||||
// 设置节点
|
||||
setNodes(nodes);
|
||||
|
||||
// 设置总 workflow 状态
|
||||
const workflowStatus = Object.values(nodes).find((node: any) =>
|
||||
node.displayName.startsWith(NodePrefix),
|
||||
) as NodeStatus;
|
||||
|
||||
// 设置工作流状态
|
||||
if (workflowStatus) {
|
||||
setWorkflowStatus(workflowStatus);
|
||||
|
||||
// 实验结束,关闭 SSE
|
||||
// 实验结束,关闭 SSE,获取实验实例结果
|
||||
if (
|
||||
workflowStatus.phase !== ExperimentStatus.Pending &&
|
||||
workflowStatus.phase !== ExperimentStatus.Running
|
||||
|
|
@ -152,8 +150,8 @@ function ActiveLearnInstance() {
|
|||
<ActiveLearnBasic
|
||||
className={styles['active-learn-instance__basic']}
|
||||
info={experimentInfo}
|
||||
runStatus={workflowStatus}
|
||||
instanceStatus={instanceInfo?.status}
|
||||
workflowStatus={workflowStatus}
|
||||
instanceStatus={instanceInfo?.status as ExperimentStatus}
|
||||
isInstance
|
||||
/>
|
||||
),
|
||||
|
|
@ -181,7 +179,7 @@ function ActiveLearnInstance() {
|
|||
},
|
||||
{
|
||||
key: TabKeys.History,
|
||||
label: '训练列表',
|
||||
label: '运行列表',
|
||||
icon: <KFIcon type="icon-Trialliebiao" />,
|
||||
children: (
|
||||
<ExperimentHistory
|
||||
|
|
|
|||
|
|
@ -28,14 +28,14 @@ type BasicInfoProps = {
|
|||
info?: ActiveLearnData;
|
||||
className?: string;
|
||||
isInstance?: boolean;
|
||||
runStatus?: NodeStatus;
|
||||
workflowStatus?: NodeStatus;
|
||||
instanceStatus?: ExperimentStatus;
|
||||
};
|
||||
|
||||
function BasicInfo({
|
||||
info,
|
||||
className,
|
||||
runStatus,
|
||||
workflowStatus,
|
||||
instanceStatus,
|
||||
isInstance = false,
|
||||
}: BasicInfoProps) {
|
||||
|
|
@ -154,7 +154,7 @@ function BasicInfo({
|
|||
value: info.dataset_py,
|
||||
},
|
||||
{
|
||||
label: '数据集类名',
|
||||
label: '数据集处理类名',
|
||||
value: info.dataset_class_name,
|
||||
},
|
||||
{
|
||||
|
|
@ -212,12 +212,8 @@ function BasicInfo({
|
|||
|
||||
return (
|
||||
<div className={classNames(styles['active-learn-basic'], className)}>
|
||||
{isInstance && runStatus && (
|
||||
<ExperimentRunBasic
|
||||
create_time={info?.create_time}
|
||||
runStatus={runStatus}
|
||||
instanceStatus={instanceStatus}
|
||||
/>
|
||||
{isInstance && workflowStatus && (
|
||||
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
|
||||
)}
|
||||
{!isInstance && (
|
||||
<ConfigInfo
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ import {
|
|||
|
||||
function ExecuteConfig() {
|
||||
const form = Form.useFormInstance();
|
||||
const task_type = Form.useWatch('task_type', form);
|
||||
const queryStrategiesOptions =
|
||||
task_type === AutoMLTaskType.Classification
|
||||
? queryStrategies.slice(0, 2)
|
||||
: queryStrategies.slice(2);
|
||||
return (
|
||||
<>
|
||||
<SubAreaTitle
|
||||
|
|
@ -101,16 +106,16 @@ function ExecuteConfig() {
|
|||
<Row gutter={8}>
|
||||
<Col span={10}>
|
||||
<Form.Item
|
||||
label="数据集类名"
|
||||
label="数据集处理类名"
|
||||
name="dataset_class_name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据集类名',
|
||||
message: '请输入数据集处理类名',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入数据集类名" maxLength={64} showCount allowClear />
|
||||
<Input placeholder="请输入数据集处理类名" maxLength={64} showCount allowClear />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
@ -488,7 +493,12 @@ function ExecuteConfig() {
|
|||
},
|
||||
]}
|
||||
>
|
||||
<Select placeholder="请选择查询策略" options={queryStrategies} showSearch allowClear />
|
||||
<Select
|
||||
placeholder="请选择查询策略"
|
||||
options={queryStrategiesOptions}
|
||||
showSearch
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -87,4 +87,8 @@ export const queryStrategies = [
|
|||
label: 'upper_confidence_bound',
|
||||
value: 'upper_confidence_bound',
|
||||
},
|
||||
{
|
||||
label: 'probability_of_improvement',
|
||||
value: 'probability_of_improvement',
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { ExperimentStatus } from '@/enums';
|
||||
import { ActiveLearnInstanceData } from '@/pages/ActiveLearn/types';
|
||||
import EmptyLog from '@/pages/AutoML/components/ExperimentLog/empty';
|
||||
import LogList from '@/pages/Experiment/components/LogList';
|
||||
import { NodeStatus } from '@/types';
|
||||
import { Tabs } from 'antd';
|
||||
|
|
@ -64,7 +65,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
|
|||
// icon: <KFIcon type="icon-rizhi1" />,
|
||||
children: (
|
||||
<div className={styles['experiment-log__tabs__log']}>
|
||||
{trainCloneNodeStatus && (
|
||||
{trainCloneNodeStatus ? (
|
||||
<LogList
|
||||
instanceName={instanceInfo.argo_ins_name}
|
||||
instanceNamespace={instanceInfo.argo_ins_ns}
|
||||
|
|
@ -73,6 +74,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
|
|||
instanceNodeStartTime={trainCloneNodeStatus.startedAt}
|
||||
instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus}
|
||||
></LogList>
|
||||
) : (
|
||||
<EmptyLog />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
|
|
@ -83,7 +86,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
|
|||
// icon: <KFIcon type="icon-rizhi1" />,
|
||||
children: (
|
||||
<div className={styles['experiment-log__tabs__log']}>
|
||||
{hpoNodeStatus && (
|
||||
{hpoNodeStatus ? (
|
||||
<LogList
|
||||
instanceName={instanceInfo.argo_ins_name}
|
||||
instanceNamespace={instanceInfo.argo_ins_ns}
|
||||
|
|
@ -92,6 +95,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
|
|||
instanceNodeStartTime={hpoNodeStatus.startedAt}
|
||||
instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus}
|
||||
></LogList>
|
||||
) : (
|
||||
<EmptyLog />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ function AutoMLInstance() {
|
|||
const [res] = await to(getExperimentInsReq(instanceId));
|
||||
if (res && res.data) {
|
||||
const info = res.data as AutoMLInstanceData;
|
||||
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time, type } = info;
|
||||
const { param, node_status, argo_ins_name, argo_ins_ns, status, type } = info;
|
||||
|
||||
setType(type);
|
||||
// 解析配置参数
|
||||
|
|
@ -59,7 +59,6 @@ function AutoMLInstance() {
|
|||
if (paramJson) {
|
||||
setAutoMLInfo({
|
||||
...paramJson.data,
|
||||
create_time,
|
||||
type,
|
||||
});
|
||||
}
|
||||
|
|
@ -95,7 +94,10 @@ function AutoMLInstance() {
|
|||
};
|
||||
|
||||
const setupSSE = (name: string, namespace: string) => {
|
||||
const { origin } = location;
|
||||
let { origin } = location;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
origin = 'http://172.20.32.235:31213';
|
||||
}
|
||||
const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
|
||||
const evtSource = new EventSource(
|
||||
`${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
|
||||
|
|
@ -110,17 +112,17 @@ function AutoMLInstance() {
|
|||
if (dataJson) {
|
||||
const nodes = dataJson?.result?.object?.status?.nodes;
|
||||
if (nodes) {
|
||||
// 节点
|
||||
// 设置节点
|
||||
setNodes(nodes);
|
||||
|
||||
// 设置总 workflow 状态
|
||||
const workflowStatus = Object.values(nodes).find((node: any) =>
|
||||
node.displayName.startsWith(NodePrefix),
|
||||
) as NodeStatus;
|
||||
|
||||
if (workflowStatus) {
|
||||
setWorkflowStatus(workflowStatus);
|
||||
|
||||
// 实验结束,关闭 SSE
|
||||
// 实验结束,关闭 SSE,获取实验实例结果
|
||||
if (
|
||||
workflowStatus.phase !== ExperimentStatus.Pending &&
|
||||
workflowStatus.phase !== ExperimentStatus.Running
|
||||
|
|
@ -155,8 +157,8 @@ function AutoMLInstance() {
|
|||
<AutoMLBasic
|
||||
className={styles['auto-ml-instance__basic']}
|
||||
info={autoMLInfo}
|
||||
runStatus={workflowStatus}
|
||||
instanceStatus={instanceInfo?.status}
|
||||
workflowStatus={workflowStatus}
|
||||
instanceStatus={instanceInfo?.status as ExperimentStatus}
|
||||
isInstance
|
||||
/>
|
||||
),
|
||||
|
|
@ -202,7 +204,7 @@ function AutoMLInstance() {
|
|||
}
|
||||
: {
|
||||
key: TabKeys.History,
|
||||
label: '试验列表',
|
||||
label: '运行列表',
|
||||
icon: <KFIcon type="icon-Trialliebiao" />,
|
||||
children: (
|
||||
<ExperimentHistory
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-04-16 13:58:08
|
||||
* @Description: 自主机器学习列表
|
||||
* @Description: 自动机器学习列表
|
||||
*/
|
||||
|
||||
import ExperimentList, { ExperimentListType } from '../components/ExperimentList';
|
||||
|
|
|
|||
|
|
@ -7,10 +7,21 @@ import {
|
|||
autoMLTaskTypeOptions,
|
||||
} from '@/enums';
|
||||
import { useComputingResource } from '@/hooks/useComputingResource';
|
||||
import {
|
||||
classificationAlgorithms,
|
||||
featureAlgorithms,
|
||||
regressorAlgorithms,
|
||||
} from '@/pages/AutoML/components/CreateForm/utils';
|
||||
import { AutoMLData } from '@/pages/AutoML/types';
|
||||
import { type NodeStatus } from '@/types';
|
||||
import { parseJsonText } from '@/utils';
|
||||
import { formatBoolean, formatDataset, formatDate, formatEnum } from '@/utils/format';
|
||||
import {
|
||||
formatBoolean,
|
||||
formatDataset,
|
||||
formatDate,
|
||||
formatEnum,
|
||||
type EnumOptions,
|
||||
} from '@/utils/format';
|
||||
import classNames from 'classnames';
|
||||
import { useMemo } from 'react';
|
||||
import ExperimentRunBasic from '../ExperimentRunBasic';
|
||||
|
|
@ -21,6 +32,7 @@ const formatOptimizeMode = (value: boolean) => {
|
|||
return value ? '越大越好' : '越小越好';
|
||||
};
|
||||
|
||||
// 格式化权重
|
||||
const formatMetricsWeight = (value: string) => {
|
||||
if (!value) {
|
||||
return '--';
|
||||
|
|
@ -34,18 +46,33 @@ const formatMetricsWeight = (value: string) => {
|
|||
.join('\n');
|
||||
};
|
||||
|
||||
// 格式化算法
|
||||
const formatAlgorithm = (algorithms: EnumOptions[]) => {
|
||||
return (value: string) => {
|
||||
if (!value) {
|
||||
return '--';
|
||||
}
|
||||
const list = value
|
||||
.split(',')
|
||||
.filter((v) => v !== '')
|
||||
.map((v) => v.trim());
|
||||
return list.map((v) => formatEnum(algorithms)(v)).join(',');
|
||||
};
|
||||
};
|
||||
|
||||
type AutoMLBasicProps = {
|
||||
info?: AutoMLData;
|
||||
className?: string;
|
||||
isInstance?: boolean;
|
||||
runStatus?: NodeStatus;
|
||||
workflowStatus?: NodeStatus;
|
||||
instanceStatus?: ExperimentStatus;
|
||||
instanceCreateTime?: string;
|
||||
};
|
||||
|
||||
function AutoMLBasic({
|
||||
info,
|
||||
className,
|
||||
runStatus,
|
||||
workflowStatus,
|
||||
instanceStatus,
|
||||
isInstance = false,
|
||||
}: AutoMLBasicProps) {
|
||||
|
|
@ -95,10 +122,12 @@ function AutoMLBasic({
|
|||
{
|
||||
label: '特征预处理算法',
|
||||
value: info.include_feature_preprocessor,
|
||||
format: formatAlgorithm(featureAlgorithms),
|
||||
},
|
||||
{
|
||||
label: '排除的特征预处理算法',
|
||||
value: info.exclude_feature_preprocessor,
|
||||
format: formatAlgorithm(featureAlgorithms),
|
||||
},
|
||||
{
|
||||
label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法',
|
||||
|
|
@ -106,6 +135,11 @@ function AutoMLBasic({
|
|||
info.task_type === AutoMLTaskType.Regression
|
||||
? info.include_regressor
|
||||
: info.include_classifier,
|
||||
format: formatAlgorithm(
|
||||
info.task_type === AutoMLTaskType.Regression
|
||||
? regressorAlgorithms
|
||||
: classificationAlgorithms,
|
||||
),
|
||||
},
|
||||
{
|
||||
label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法',
|
||||
|
|
@ -113,6 +147,11 @@ function AutoMLBasic({
|
|||
info.task_type === AutoMLTaskType.Regression
|
||||
? info.exclude_regressor
|
||||
: info.exclude_classifier,
|
||||
format: formatAlgorithm(
|
||||
info.task_type === AutoMLTaskType.Regression
|
||||
? regressorAlgorithms
|
||||
: classificationAlgorithms,
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '集成方式',
|
||||
|
|
@ -292,12 +331,8 @@ function AutoMLBasic({
|
|||
|
||||
return (
|
||||
<div className={classNames(styles['auto-ml-basic'], className)}>
|
||||
{isInstance && runStatus && (
|
||||
<ExperimentRunBasic
|
||||
create_time={info?.create_time}
|
||||
runStatus={runStatus}
|
||||
instanceStatus={instanceStatus}
|
||||
/>
|
||||
{isInstance && workflowStatus && (
|
||||
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
|
||||
)}
|
||||
{!isInstance && (
|
||||
<ConfigInfo
|
||||
|
|
|
|||
|
|
@ -8,69 +8,7 @@ import {
|
|||
autoMLTaskTypeOptions,
|
||||
} from '@/enums';
|
||||
import { Col, Form, InputNumber, Radio, Row, Select, Switch } from 'antd';
|
||||
|
||||
// 分类算法
|
||||
const classificationAlgorithms = [
|
||||
'adaboost',
|
||||
'bernoulli_nb',
|
||||
'decision_tree',
|
||||
'extra_trees',
|
||||
'gaussian_nb',
|
||||
'gradient_boosting',
|
||||
'k_nearest_neighbors',
|
||||
'lda',
|
||||
'liblinear_svc',
|
||||
'libsvm_svc',
|
||||
'mlp',
|
||||
'multinomial_nb',
|
||||
'passive_aggressive',
|
||||
'qda',
|
||||
'random_forest',
|
||||
'sgd',
|
||||
'LightGBMClassification',
|
||||
'XGBoostClassification',
|
||||
'StackingClassification',
|
||||
].map((name) => ({ label: name, value: name }));
|
||||
|
||||
// 回归算法
|
||||
const regressorAlgorithms = [
|
||||
'adaboost',
|
||||
'ard_regression',
|
||||
'decision_tree',
|
||||
'extra_trees',
|
||||
'gaussian_process',
|
||||
'gradient_boosting',
|
||||
'k_nearest_neighbors',
|
||||
'liblinear_svr',
|
||||
'libsvm_svr',
|
||||
'mlp',
|
||||
'random_forest',
|
||||
'sgd',
|
||||
'LightGBMRegression',
|
||||
'XGBoostRegression',
|
||||
].map((name) => ({ label: name, value: name }));
|
||||
|
||||
// 特征预处理算法
|
||||
const featureAlgorithms = [
|
||||
'densifier',
|
||||
'extra_trees_preproc_for_classification',
|
||||
'extra_trees_preproc_for_regression',
|
||||
'fast_ica',
|
||||
'feature_agglomeration',
|
||||
'kernel_pca',
|
||||
'kitchen_sinks',
|
||||
'liblinear_svc_preprocessor',
|
||||
'no_preprocessing',
|
||||
'nystroem_sampler',
|
||||
'pca',
|
||||
'polynomial',
|
||||
'random_trees_embedding',
|
||||
'select_percentile_classification',
|
||||
'select_percentile_regression',
|
||||
'select_rates_classification',
|
||||
'select_rates_regression',
|
||||
'truncatedSVD',
|
||||
].map((name) => ({ label: name, value: name }));
|
||||
import { classificationAlgorithms, featureAlgorithms, regressorAlgorithms } from './utils';
|
||||
|
||||
// 分类指标
|
||||
export const classificationMetrics = [
|
||||
|
|
@ -280,9 +218,9 @@ function ExecuteConfig() {
|
|||
<Form.Item
|
||||
label="集成模型数量"
|
||||
name="ensemble_size"
|
||||
tooltip="集成模型数量,如果设置为0,则没有集成。默认50"
|
||||
tooltip="集成模型数量,必须是大于等于1的整数,默认50"
|
||||
>
|
||||
<InputNumber placeholder="请输入集成模型数量" min={0} precision={0} />
|
||||
<InputNumber placeholder="请输入集成模型数量" min={1} precision={0} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
@ -292,7 +230,7 @@ function ExecuteConfig() {
|
|||
<Form.Item
|
||||
label="集成最佳模型数量"
|
||||
name="ensemble_nbest"
|
||||
tooltip="仅集成最佳的N个模型"
|
||||
tooltip="仅集成最佳的N个模型,必须是大于等于1的整数"
|
||||
>
|
||||
<InputNumber placeholder="请输入集成最佳模型数量" min={1} precision={0} />
|
||||
</Form.Item>
|
||||
|
|
@ -419,6 +357,7 @@ function ExecuteConfig() {
|
|||
<Form.Item
|
||||
label="交叉验证折数"
|
||||
name="folds"
|
||||
tooltip="交叉验证折数必须是大于等于2的整数"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
|
|
@ -426,7 +365,7 @@ function ExecuteConfig() {
|
|||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber placeholder="请输入交叉验证折数" min={1} precision={0} />
|
||||
<InputNumber placeholder="请输入交叉验证折数" min={2} precision={0} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
// 分类算法
|
||||
export const classificationAlgorithms = [
|
||||
{ label: 'adaboost (自适应提升算法)', value: 'adaboost' },
|
||||
{ label: 'bernoulli_nb (伯努利朴素贝叶斯)', value: 'bernoulli_nb' },
|
||||
{ label: 'decision_tree (决策树)', value: 'decision_tree' },
|
||||
{ label: 'extra_trees (极端随机树)', value: 'extra_trees' },
|
||||
{ label: 'gaussian_nb (高斯朴素贝叶斯)', value: 'gaussian_nb' },
|
||||
{ label: 'gradient_boosting (梯度提升)', value: 'gradient_boosting' },
|
||||
{ label: 'k_nearest_neighbors (k近邻)', value: 'k_nearest_neighbors' },
|
||||
{ label: 'lda (线性判别分析)', value: 'lda' },
|
||||
{ label: 'liblinear_svc (liblinear支持向量分类)', value: 'liblinear_svc' },
|
||||
{ label: 'libsvm_svc (libsvm支持向量分类)', value: 'libsvm_svc' },
|
||||
{ label: 'mlp (多层感知器)', value: 'mlp' },
|
||||
{ label: 'multinomial_nb (多项式朴素贝叶斯)', value: 'multinomial_nb' },
|
||||
{ label: 'passive_aggressive (被动攻击算法)', value: 'passive_aggressive' },
|
||||
{ label: 'qda (二次判别式分析)', value: 'qda' },
|
||||
{ label: 'random_forest (随机森林)', value: 'random_forest' },
|
||||
{ label: 'sgd (随机梯度下降)', value: 'sgd' },
|
||||
{ label: 'tablenet (表格网络)', value: 'tablenet' },
|
||||
{ label: 'LightGBMClassification (轻量梯度提升机分类)', value: 'LightGBMClassification' },
|
||||
{ label: 'XGBoostClassification (极端梯度提升机分类)', value: 'XGBoostClassification' },
|
||||
{ label: 'StackingClassification (堆叠泛化)', value: 'StackingClassification' },
|
||||
];
|
||||
|
||||
// 回归算法
|
||||
export const regressorAlgorithms = [
|
||||
{ label: 'adaboost (自适应提升算法)', value: 'adaboost' },
|
||||
{ label: 'ard_regression (自动相关性确定回归)', value: 'ard_regression' },
|
||||
{ label: 'decision_tree (决策树)', value: 'decision_tree' },
|
||||
{ label: 'extra_trees (极端随机树)', value: 'extra_trees' },
|
||||
{ label: 'gaussian_process (高斯过程回归)', value: 'gaussian_process' },
|
||||
{ label: 'gradient_boosting (梯度提升)', value: 'gradient_boosting' },
|
||||
{ label: 'k_nearest_neighbors (梯度提升)', value: 'k_nearest_neighbors' },
|
||||
{ label: 'liblinear_svr (liblinear支持向量回归)', value: 'liblinear_svr' },
|
||||
{ label: 'libsvm_svr (libsvm支持向量回归)', value: 'libsvm_svr' },
|
||||
{ label: 'mlp (多层感知器)', value: 'mlp' },
|
||||
{ label: 'random_forest (随机森林)', value: 'random_forest' },
|
||||
{ label: 'sgd (随机梯度下降)', value: 'sgd' },
|
||||
{ label: 'LightGBMRegression (轻量梯度提升机回归)', value: 'LightGBMRegression' },
|
||||
{ label: 'XGBoostRegression (极端梯度提升机回归)', value: 'XGBoostRegression' },
|
||||
];
|
||||
|
||||
// 特征预处理算法
|
||||
export const featureAlgorithms = [
|
||||
{ label: 'densifier (特征变换-数据增稠)', value: 'densifier' },
|
||||
{
|
||||
label: 'extra_trees_preproc_for_classification (特征选择-分类任务极端随机树)',
|
||||
value: 'extra_trees_preproc_for_classification',
|
||||
},
|
||||
{
|
||||
label: 'extra_trees_preproc_for_regression (特征选择-回归任务极端随机树)',
|
||||
value: 'extra_trees_preproc_for_regression',
|
||||
},
|
||||
{ label: 'fast_ica (特征选择-快速独立成分分析)', value: 'fast_ica' },
|
||||
{ label: 'feature_agglomeration (特征变换-特征聚合)', value: 'feature_agglomeration' },
|
||||
{ label: 'kernel_pca (特征选择-核主成分分析)', value: 'kernel_pca' },
|
||||
{ label: 'kitchen_sinks (特征变换-随机特征映射)', value: 'kitchen_sinks' },
|
||||
{
|
||||
label: 'liblinear_svc_preprocessor (特征选择-线性svc预处理器)',
|
||||
value: 'liblinear_svc_preprocessor',
|
||||
},
|
||||
{ label: 'miss_value_impute (缺失值填充)', value: 'miss_value_impute' },
|
||||
{ label: 'no_preprocessing (无预处理)', value: 'no_preprocessing' },
|
||||
{ label: 'nystroem_sampler (特征变换-尼斯特罗姆采样器)', value: 'nystroem_sampler' },
|
||||
{ label: 'pca (特征选择-主成分分析)', value: 'pca' },
|
||||
{ label: 'polynomial (特征变换-多项式特征扩展)', value: 'polynomial' },
|
||||
{ label: 'random_trees_embedding (特征变换-随机森林特征嵌入)', value: 'random_trees_embedding' },
|
||||
{
|
||||
label: 'select_percentile_classification 特征选择-基于百分位的分类特征选择)',
|
||||
value: 'select_percentile_classification',
|
||||
},
|
||||
{
|
||||
label: 'select_percentile_regression (特征选择-基于百分位的回归特征选择)',
|
||||
value: 'select_percentile_regression',
|
||||
},
|
||||
{
|
||||
label: 'select_rates_classification (特征选择-基于比率的分类特征选择)',
|
||||
value: 'select_rates_classification',
|
||||
},
|
||||
{
|
||||
label: 'select_rates_regression (特征选择-基于比率的回归特征选择)',
|
||||
value: 'select_rates_regression',
|
||||
},
|
||||
{ label: 'truncatedSVD (特征变换-截断奇异值分解)', value: 'truncatedSVD' },
|
||||
];
|
||||
|
|
@ -54,10 +54,6 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
width: 200px;
|
||||
|
||||
.statusIcon {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,14 @@ function ExperimentInstanceList({
|
|||
}: ExperimentInstanceListProps) {
|
||||
const { message } = App.useApp();
|
||||
const allIntanceIds = useMemo(() => {
|
||||
return experimentInsList?.map((item) => item.id) || [];
|
||||
return (
|
||||
experimentInsList
|
||||
?.filter(
|
||||
(item) =>
|
||||
item.status !== ExperimentStatus.Running && item.status !== ExperimentStatus.Pending,
|
||||
)
|
||||
.map((item) => item.id) || []
|
||||
);
|
||||
}, [experimentInsList]);
|
||||
const [
|
||||
selectedIns,
|
||||
|
|
@ -126,7 +133,12 @@ function ExperimentInstanceList({
|
|||
<div>
|
||||
<div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
|
||||
<div className={styles.check}>
|
||||
<Checkbox checked={checked} indeterminate={indeterminate} onChange={checkAll}></Checkbox>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
indeterminate={indeterminate}
|
||||
disabled={allIntanceIds.length === 0}
|
||||
onChange={checkAll}
|
||||
></Checkbox>
|
||||
</div>
|
||||
<div className={styles.index}>序号</div>
|
||||
<div className={styles.description}>运行时长</div>
|
||||
|
|
@ -171,12 +183,8 @@ function ExperimentInstanceList({
|
|||
{index + 1}
|
||||
</a>
|
||||
<ExperimentInstanceComponent
|
||||
create_time={item.create_time}
|
||||
finish_time={item.finish_time}
|
||||
status={item.status as ExperimentStatus}
|
||||
argo_ins_name={item.argo_ins_name}
|
||||
argo_ins_ns={item.argo_ins_ns}
|
||||
experimentInsId={item.id}
|
||||
experimentId={item[config['idInsProperty'] as keyof ExperimentInstance] as number}
|
||||
instance={item}
|
||||
></ExperimentInstanceComponent>
|
||||
<div className={styles.operation}>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -2,67 +2,70 @@ import RunDuration from '@/components/RunDuration';
|
|||
import { ExperimentStatus } from '@/enums';
|
||||
import { useSSE, type MessageHandler } from '@/hooks/useSSE';
|
||||
import { experimentStatusInfo } from '@/pages/Experiment/status';
|
||||
import { ExperimentInstance, NodeStatus } from '@/types';
|
||||
import { ExperimentCompleted } from '@/utils/constant';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { getWorkflowStatus } from '@/utils/experiment';
|
||||
import { Typography } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import styles from './index.less';
|
||||
|
||||
type ExperimentInstanceProps = {
|
||||
create_time?: string;
|
||||
finish_time?: string;
|
||||
status: ExperimentStatus;
|
||||
argo_ins_name: string;
|
||||
argo_ins_ns: string;
|
||||
experimentInsId: number;
|
||||
type ExperimentInstanceComponentProps = {
|
||||
experimentId: number;
|
||||
instance: ExperimentInstance;
|
||||
};
|
||||
|
||||
function ExperimentInstance({
|
||||
create_time,
|
||||
finish_time,
|
||||
status,
|
||||
argo_ins_name,
|
||||
argo_ins_ns,
|
||||
experimentInsId,
|
||||
}: ExperimentInstanceProps) {
|
||||
function ExperimentInstanceComponent({ experimentId, instance }: ExperimentInstanceComponentProps) {
|
||||
const { id, argo_ins_name, argo_ins_ns, node_status } = instance;
|
||||
const workflowStatus = getWorkflowStatus(node_status) as NodeStatus | undefined;
|
||||
const status = instance.status as ExperimentStatus;
|
||||
const createTime = workflowStatus?.startedAt;
|
||||
const finishTime = workflowStatus?.finishedAt;
|
||||
const statusInfo = experimentStatusInfo[status];
|
||||
|
||||
const handleSSEMessage: MessageHandler = useCallback(
|
||||
(experimentInsId: number, status: string, finish_time: string) => {
|
||||
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
|
||||
window.postMessage({
|
||||
type: ExperimentCompleted,
|
||||
payload: {
|
||||
id: experimentInsId,
|
||||
experimentId,
|
||||
experimentInsId,
|
||||
status,
|
||||
finish_time,
|
||||
finishTime,
|
||||
},
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
useSSE(experimentInsId, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
|
||||
useSSE(experimentId, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={styles.description}>
|
||||
<RunDuration createTime={create_time} finishTime={finish_time} />
|
||||
<RunDuration createTime={createTime} finishTime={finishTime} />
|
||||
</div>
|
||||
<div className={styles.startTime}>
|
||||
<Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}>
|
||||
{formatDate(create_time)}
|
||||
<Typography.Text ellipsis={{ tooltip: formatDate(createTime) }}>
|
||||
{formatDate(createTime)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className={styles.statusBox}>
|
||||
<img
|
||||
style={{ width: '17px', marginRight: '7px' }}
|
||||
src={experimentStatusInfo[status]?.icon}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
<span style={{ color: experimentStatusInfo[status]?.color }} className={styles.statusIcon}>
|
||||
{experimentStatusInfo[status]?.label}
|
||||
</span>
|
||||
{statusInfo ? (
|
||||
<>
|
||||
<img
|
||||
style={{ width: '17px', marginRight: '7px' }}
|
||||
src={statusInfo.icon}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
|
||||
</>
|
||||
) : (
|
||||
'--'
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExperimentInstance;
|
||||
export default ExperimentInstanceComponent;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
batchDeleteActiveLearnInsReq,
|
||||
deleteActiveLearnInsReq,
|
||||
deleteActiveLearnReq,
|
||||
editActiveLearnInsReq,
|
||||
getActiveLearnInsListReq,
|
||||
getActiveLearnListReq,
|
||||
runActiveLearnReq,
|
||||
|
|
@ -17,6 +18,7 @@ import {
|
|||
batchDeleteExperimentInsReq,
|
||||
deleteAutoMLReq,
|
||||
deleteExperimentInsReq,
|
||||
editExperimentInsReq,
|
||||
getAutoMLListReq,
|
||||
getExperimentInsListReq,
|
||||
runAutoMLReq,
|
||||
|
|
@ -26,6 +28,7 @@ import {
|
|||
batchDeleteRayInsReq,
|
||||
deleteRayInsReq,
|
||||
deleteRayReq,
|
||||
editRayInsReq,
|
||||
getRayInsListReq,
|
||||
getRayListReq,
|
||||
runRayReq,
|
||||
|
|
@ -39,18 +42,20 @@ export enum ExperimentListType {
|
|||
}
|
||||
|
||||
type ExperimentListInfo = {
|
||||
getListReq: (params: any) => Promise<any>; // 获取列表
|
||||
getInsListReq: (params: any) => Promise<any>; // 获取实例列表
|
||||
getListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取列表
|
||||
getInsListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取实例列表
|
||||
deleteRecordReq: (params: any) => Promise<any>; // 删除
|
||||
runRecordReq: (params: any) => Promise<any>; // 运行
|
||||
deleteInsReq: (params: any) => Promise<any>; // 删除实例
|
||||
batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例
|
||||
stopInsReq: (params: any) => Promise<any>; // 终止实例
|
||||
editInsReq: (params: any) => Promise<any>; // 编辑实例
|
||||
title: string; // 标题
|
||||
pathPrefix: string; // 路由路径前缀
|
||||
idProperty: string; // ID属性
|
||||
nameProperty: string; // 名称属性
|
||||
descProperty: string; // 描述属性
|
||||
idInsProperty: string; // 实例返回的ID属性
|
||||
};
|
||||
|
||||
export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo> = {
|
||||
|
|
@ -62,11 +67,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
|
|||
deleteInsReq: deleteExperimentInsReq,
|
||||
batchDeleteInsReq: batchDeleteExperimentInsReq,
|
||||
stopInsReq: stopExperimentInsReq,
|
||||
title: '自主机器学习',
|
||||
editInsReq: editExperimentInsReq,
|
||||
title: '自动机器学习',
|
||||
pathPrefix: 'automl',
|
||||
nameProperty: 'name',
|
||||
descProperty: 'description',
|
||||
idProperty: 'machineLearnId',
|
||||
idInsProperty: 'machine_learn_id',
|
||||
},
|
||||
[ExperimentListType.HyperParameter]: {
|
||||
getListReq: getRayListReq,
|
||||
|
|
@ -76,11 +83,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
|
|||
deleteInsReq: deleteRayInsReq,
|
||||
batchDeleteInsReq: batchDeleteRayInsReq,
|
||||
stopInsReq: stopRayInsReq,
|
||||
editInsReq: editRayInsReq,
|
||||
title: '超参数自动寻优',
|
||||
pathPrefix: 'hyperparameter',
|
||||
nameProperty: 'name',
|
||||
descProperty: 'description',
|
||||
idProperty: 'rayId',
|
||||
idInsProperty: 'ray_id',
|
||||
},
|
||||
[ExperimentListType.ActiveLearn]: {
|
||||
getListReq: getActiveLearnListReq,
|
||||
|
|
@ -90,10 +99,12 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
|
|||
deleteInsReq: deleteActiveLearnInsReq,
|
||||
batchDeleteInsReq: batchDeleteActiveLearnInsReq,
|
||||
stopInsReq: stopActiveLearnInsReq,
|
||||
editInsReq: editActiveLearnInsReq,
|
||||
title: '自动学习',
|
||||
pathPrefix: 'active-learn',
|
||||
nameProperty: 'name',
|
||||
descProperty: 'description',
|
||||
idProperty: 'activeLearnId',
|
||||
idInsProperty: 'active_learn_id',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import {
|
|||
} from 'antd';
|
||||
import { type SearchProps } from 'antd/es/input';
|
||||
import classNames from 'classnames';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import ExperimentInstanceList from '../ExperimentInstanceList';
|
||||
import { ExperimentListType, experimentListConfig } from './config';
|
||||
import styles from './index.less';
|
||||
|
|
@ -52,7 +52,6 @@ function ExperimentList({ type }: ExperimentListProps) {
|
|||
const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]);
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]);
|
||||
const [experimentInsTotal, setExperimentInsTotal] = useState(0);
|
||||
const [now] = useServerTime();
|
||||
const [pagination, setPagination] = useState<TablePaginationConfig>(
|
||||
cacheState?.pagination ?? {
|
||||
current: 1,
|
||||
|
|
@ -60,38 +59,37 @@ function ExperimentList({ type }: ExperimentListProps) {
|
|||
},
|
||||
);
|
||||
const config = experimentListConfig[type];
|
||||
const timerRef = useRef<ReturnType<typeof window.setTimeout> | undefined>();
|
||||
const [now] = useServerTime();
|
||||
|
||||
// 获取自主机器学习或超参数自动优化列表
|
||||
const getAutoMLList = useCallback(async () => {
|
||||
const params: Record<string, any> = {
|
||||
page: pagination.current! - 1,
|
||||
size: pagination.pageSize,
|
||||
[config.nameProperty]: searchText || undefined,
|
||||
};
|
||||
const request = config.getListReq;
|
||||
const [res] = await to(request(params));
|
||||
if (res && res.data) {
|
||||
const { content = [], totalElements = 0 } = res.data;
|
||||
setTableData(content);
|
||||
setTotal(totalElements);
|
||||
}
|
||||
}, [pagination, searchText, config]);
|
||||
|
||||
useEffect(() => {
|
||||
getAutoMLList();
|
||||
}, [getAutoMLList]);
|
||||
// 获取实验列表
|
||||
const getExperimentList = useCallback(
|
||||
async (skipLoading: boolean = false) => {
|
||||
const params: Record<string, any> = {
|
||||
page: pagination.current! - 1,
|
||||
size: pagination.pageSize,
|
||||
[config.nameProperty]: searchText || undefined,
|
||||
};
|
||||
const request = config.getListReq;
|
||||
const [res] = await to(request(params, skipLoading));
|
||||
if (res && res.data) {
|
||||
const { content = [], totalElements = 0 } = res.data;
|
||||
setTableData(content);
|
||||
setTotal(totalElements);
|
||||
}
|
||||
},
|
||||
[pagination, searchText, config],
|
||||
);
|
||||
|
||||
// 获取实验实例列表
|
||||
const getExperimentInsList = useCallback(
|
||||
async (recordId: number, page: number, size: number) => {
|
||||
async (recordId: number, page: number, size: number, skipLoading: boolean = false) => {
|
||||
const params = {
|
||||
[config.idProperty]: recordId,
|
||||
page: page,
|
||||
size: size,
|
||||
};
|
||||
const request = config.getInsListReq;
|
||||
const [res] = await to(request(params));
|
||||
const [res] = await to(request(params, skipLoading));
|
||||
if (res && res.data) {
|
||||
const { content = [], totalElements = 0 } = res.data;
|
||||
try {
|
||||
|
|
@ -111,59 +109,115 @@ function ExperimentList({ type }: ExperimentListProps) {
|
|||
|
||||
// 刷新实验列表状态,
|
||||
// TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态
|
||||
const refreshExperimentList = useCallback(() => {
|
||||
getAutoMLList();
|
||||
}, [getAutoMLList]);
|
||||
const refreshExperimentList = useCallback(
|
||||
(skipLoading: boolean = false) => {
|
||||
getExperimentList(skipLoading);
|
||||
},
|
||||
[getExperimentList],
|
||||
);
|
||||
|
||||
// 刷新实验实例列表
|
||||
const refreshExperimentIns = useCallback(
|
||||
(experimentId: number) => {
|
||||
(experimentId: number, skipLoading: boolean = false) => {
|
||||
const length = experimentInsList.length;
|
||||
getExperimentInsList(experimentId, 0, length);
|
||||
getExperimentInsList(experimentId, 0, length, skipLoading);
|
||||
},
|
||||
[getExperimentInsList, experimentInsList],
|
||||
);
|
||||
|
||||
// 新增,删除版本时,重置分页,然后刷新版本列表
|
||||
// 更新实验实例状态
|
||||
const editExperimentIns = useCallback(
|
||||
async (
|
||||
experimentId: number,
|
||||
experimentInsId: number,
|
||||
status: ExperimentStatus,
|
||||
argo_ins_name: string,
|
||||
argo_ins_ns: string,
|
||||
) => {
|
||||
const params = {
|
||||
[config.idInsProperty]: experimentId,
|
||||
id: experimentInsId,
|
||||
status: status,
|
||||
argo_ins_name,
|
||||
argo_ins_ns,
|
||||
};
|
||||
const request = config.editInsReq;
|
||||
const [res] = await to(request(params));
|
||||
if (res && res.data) {
|
||||
refreshExperimentIns(experimentId, true);
|
||||
refreshExperimentList(true);
|
||||
}
|
||||
},
|
||||
[config, refreshExperimentIns, refreshExperimentList],
|
||||
);
|
||||
|
||||
// 获取实验列表
|
||||
useEffect(() => {
|
||||
getExperimentList();
|
||||
}, [getExperimentList]);
|
||||
|
||||
// expandedRowKeys 变化
|
||||
useEffect(() => {
|
||||
if (expandedRowKeys.length > 0) {
|
||||
getExperimentInsList(expandedRowKeys[0], 0, 5);
|
||||
refreshExperimentList();
|
||||
}
|
||||
}, [expandedRowKeys, getExperimentInsList, refreshExperimentList]);
|
||||
|
||||
// 实验实例状态变化
|
||||
useEffect(() => {
|
||||
const handleMessage = (e: MessageEvent) => {
|
||||
const { type, payload } = e.data;
|
||||
if (type === ExperimentCompleted) {
|
||||
const { id, status, finish_time } = payload;
|
||||
|
||||
// 修改实例的状态和结束时间
|
||||
setExperimentInsList((prev) =>
|
||||
prev.map((v) =>
|
||||
v.id === id
|
||||
? {
|
||||
...v,
|
||||
status: status,
|
||||
finish_time: finish_time,
|
||||
}
|
||||
: v,
|
||||
),
|
||||
const { experimentId, experimentInsId, status, finishTime } = payload;
|
||||
const currentIns = experimentInsList.find((v) => v.id === experimentInsId);
|
||||
console.log(
|
||||
'实验实例状态变化',
|
||||
currentIns?.status,
|
||||
status,
|
||||
experimentId,
|
||||
experimentInsId,
|
||||
finishTime,
|
||||
);
|
||||
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = undefined;
|
||||
if (
|
||||
!currentIns ||
|
||||
currentIns.status === ExperimentStatus.Terminated ||
|
||||
currentIns.status === status
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
timerRef.current = setTimeout(() => {
|
||||
refreshExperimentList();
|
||||
}, 10000);
|
||||
// refreshExperimentList(true);
|
||||
// refreshExperimentIns(experimentId);
|
||||
editExperimentIns(
|
||||
experimentId,
|
||||
experimentInsId,
|
||||
status,
|
||||
currentIns.argo_ins_name,
|
||||
currentIns.argo_ins_ns,
|
||||
);
|
||||
|
||||
// 修改实例的状态和结束时间
|
||||
// setExperimentInsList((prev) =>
|
||||
// prev.map((v) =>
|
||||
// v.id === experimentInsId
|
||||
// ? {
|
||||
// ...v,
|
||||
// status: status,
|
||||
// finish_time: finishTime,
|
||||
// }
|
||||
// : v,
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => {
|
||||
window.removeEventListener('message', handleMessage);
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = undefined;
|
||||
}
|
||||
};
|
||||
}, [refreshExperimentList]);
|
||||
}, [experimentInsList, editExperimentIns]);
|
||||
|
||||
// 搜索
|
||||
const onSearch: SearchProps['onSearch'] = (value) => {
|
||||
|
|
@ -207,6 +261,7 @@ function ExperimentList({ type }: ExperimentListProps) {
|
|||
setCacheState({
|
||||
pagination,
|
||||
searchText,
|
||||
expandedRowKeys,
|
||||
});
|
||||
|
||||
if (record) {
|
||||
|
|
@ -225,6 +280,7 @@ function ExperimentList({ type }: ExperimentListProps) {
|
|||
setCacheState({
|
||||
pagination,
|
||||
searchText,
|
||||
expandedRowKeys,
|
||||
});
|
||||
|
||||
navigate(`info/${record.id}`);
|
||||
|
|
@ -237,8 +293,8 @@ function ExperimentList({ type }: ExperimentListProps) {
|
|||
if (res) {
|
||||
message.success('运行成功');
|
||||
setExpandedRowKeys([record.id]);
|
||||
refreshExperimentList();
|
||||
getExperimentInsList(record.id, 0, 5);
|
||||
// getExperimentInsList(record.id, 0, 5);
|
||||
// refreshExperimentList();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -248,8 +304,8 @@ function ExperimentList({ type }: ExperimentListProps) {
|
|||
setExperimentInsList([]);
|
||||
if (expanded) {
|
||||
setExpandedRowKeys([record.id]);
|
||||
getExperimentInsList(record.id, 0, 5);
|
||||
refreshExperimentList();
|
||||
// getExperimentInsList(record.id, 0, 5);
|
||||
// refreshExperimentList();
|
||||
} else {
|
||||
setExpandedRowKeys([]);
|
||||
}
|
||||
|
|
@ -257,6 +313,11 @@ function ExperimentList({ type }: ExperimentListProps) {
|
|||
|
||||
// 跳转到实验实例详情
|
||||
const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => {
|
||||
setCacheState({
|
||||
pagination,
|
||||
searchText,
|
||||
expandedRowKeys,
|
||||
});
|
||||
navigate(`instance/${autoML.id}/${record.id}`);
|
||||
};
|
||||
|
||||
|
|
@ -269,8 +330,7 @@ function ExperimentList({ type }: ExperimentListProps) {
|
|||
|
||||
// 实验实例终止
|
||||
const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => {
|
||||
// 刷新实验列表
|
||||
refreshExperimentList();
|
||||
// 修改实例的状态和结束时间
|
||||
setExperimentInsList((prevList) => {
|
||||
return prevList.map((item) => {
|
||||
if (item.id === experimentIns.id) {
|
||||
|
|
@ -283,6 +343,11 @@ function ExperimentList({ type }: ExperimentListProps) {
|
|||
return item;
|
||||
});
|
||||
});
|
||||
// 刷新实验列表和实例列表
|
||||
refreshExperimentList(true);
|
||||
if (expandedRowKeys.length > 0) {
|
||||
refreshExperimentIns(expandedRowKeys[0]);
|
||||
}
|
||||
};
|
||||
// --------------------------- Table ---------------------------
|
||||
// 分页切换
|
||||
|
|
@ -330,7 +395,7 @@ function ExperimentList({ type }: ExperimentListProps) {
|
|||
},
|
||||
...diffColumns,
|
||||
{
|
||||
title: '创建时间',
|
||||
title: '更新时间',
|
||||
dataIndex: 'update_time',
|
||||
key: 'update_time',
|
||||
width: '20%',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import styles from './index.less';
|
||||
|
||||
function EmptyLog() {
|
||||
return <div className={styles['empty-log']}>暂无日志</div>;
|
||||
}
|
||||
|
||||
export default EmptyLog;
|
||||
|
|
@ -5,3 +5,14 @@
|
|||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-log {
|
||||
height: 100%;
|
||||
padding: 15px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
white-space: pre-line;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
background: #19253b;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { ExperimentStatus } from '@/enums';
|
|||
import { AutoMLInstanceData } from '@/pages/AutoML/types';
|
||||
import LogList from '@/pages/Experiment/components/LogList';
|
||||
import { NodeStatus } from '@/types';
|
||||
import EmptyLog from './empty';
|
||||
import styles from './index.less';
|
||||
|
||||
const NodePrefix = 'auto-ml';
|
||||
|
|
@ -19,7 +20,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
|
|||
return (
|
||||
<div className={styles['experiment-log']}>
|
||||
<div className={styles['experiment-log__log']}>
|
||||
{nodeStatus && (
|
||||
{nodeStatus ? (
|
||||
<LogList
|
||||
instanceName={instanceInfo.argo_ins_name}
|
||||
instanceNamespace={instanceInfo.argo_ins_ns}
|
||||
|
|
@ -28,6 +29,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
|
|||
instanceNodeStartTime={nodeStatus.startedAt}
|
||||
instanceNodeStatus={nodeStatus.phase as ExperimentStatus}
|
||||
></LogList>
|
||||
) : (
|
||||
<EmptyLog />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,59 +3,61 @@ import RunDuration from '@/components/RunDuration';
|
|||
import { ExperimentStatus } from '@/enums';
|
||||
import { experimentStatusInfo } from '@/pages/Experiment/status';
|
||||
import { type NodeStatus } from '@/types';
|
||||
import { getExperimentInstanceStatus } from '@/utils/experiment';
|
||||
import { formatDate } from '@/utils/format';
|
||||
import { Flex } from 'antd';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
type ExperimentRunBasicProps = {
|
||||
create_time?: string;
|
||||
runStatus?: NodeStatus;
|
||||
workflowStatus?: NodeStatus;
|
||||
instanceStatus?: ExperimentStatus;
|
||||
};
|
||||
|
||||
function ExperimentRunBasic({ create_time, runStatus, instanceStatus }: ExperimentRunBasicProps) {
|
||||
function ExperimentRunBasic({ workflowStatus, instanceStatus }: ExperimentRunBasicProps) {
|
||||
const instanceDatas = useMemo(() => {
|
||||
if (!runStatus) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const status =
|
||||
instanceStatus === ExperimentStatus.Terminated ? instanceStatus : runStatus.phase;
|
||||
const status = getExperimentInstanceStatus(instanceStatus as ExperimentStatus, workflowStatus);
|
||||
const statusInfo = experimentStatusInfo[status];
|
||||
|
||||
return [
|
||||
{
|
||||
label: '启动时间',
|
||||
value: formatDate(create_time),
|
||||
value: formatDate(workflowStatus?.startedAt),
|
||||
},
|
||||
{
|
||||
label: '执行时长',
|
||||
value: <RunDuration createTime={create_time} finishTime={runStatus.finishedAt} />,
|
||||
value: (
|
||||
<RunDuration
|
||||
createTime={workflowStatus?.startedAt}
|
||||
finishTime={workflowStatus?.finishedAt}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
value: (
|
||||
value: statusInfo ? (
|
||||
<Flex align="center">
|
||||
<img
|
||||
style={{ width: '17px', marginRight: '7px' }}
|
||||
src={statusInfo?.icon}
|
||||
src={statusInfo.icon}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
color: statusInfo?.color,
|
||||
color: statusInfo.color,
|
||||
fontSize: '15px',
|
||||
lineHeight: 1.6,
|
||||
}}
|
||||
>
|
||||
{statusInfo?.label}
|
||||
{statusInfo.label}
|
||||
</div>
|
||||
</Flex>
|
||||
) : (
|
||||
'--'
|
||||
),
|
||||
},
|
||||
];
|
||||
}, [runStatus, create_time, instanceStatus]);
|
||||
}, [workflowStatus, instanceStatus]);
|
||||
|
||||
return (
|
||||
<ConfigInfo
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
.experiment-visual {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&__empty {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,15 @@
|
|||
*/
|
||||
|
||||
import IframePage, { IframePageType } from '@/components/IFramePage';
|
||||
import { runTensorBoardReq } from '@/services/experiment/index.js';
|
||||
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
|
||||
import KFSpin from '@/components/KFSpin';
|
||||
import { TensorBoardStatus } from '@/enums';
|
||||
import { getTensorBoardStatusReq, runTensorBoardReq } from '@/services/experiment/index.js';
|
||||
import { to } from '@/utils/promise';
|
||||
import SessionStorage from '@/utils/sessionStorage';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import styles from './index.less';
|
||||
|
||||
type TensorBoardProps = {
|
||||
namespace?: string;
|
||||
|
|
@ -16,28 +21,75 @@ type TensorBoardProps = {
|
|||
};
|
||||
|
||||
function ExperimentVisualResult({ namespace, path }: TensorBoardProps) {
|
||||
const [tensorboardUrl, setTensorboardUrl] = useState('');
|
||||
useEffect(() => {
|
||||
// 运行 TensorBoard
|
||||
const runTensorBoard = async () => {
|
||||
const params = {
|
||||
namespace: namespace,
|
||||
path: path,
|
||||
};
|
||||
const [res] = await to(runTensorBoardReq(params));
|
||||
if (res && res.data) {
|
||||
const url = res.data;
|
||||
SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url);
|
||||
setTensorboardUrl(url);
|
||||
}
|
||||
};
|
||||
const [tensorboardUrl, setTensorboardUrl] = useState<string | undefined | null>(undefined);
|
||||
const [status, setStatus] = useState<TensorBoardStatus | undefined>(undefined);
|
||||
|
||||
if (namespace && path) {
|
||||
runTensorBoard();
|
||||
// 获取 TensorBoard 状态
|
||||
const getTensorBoardStatus = useCallback(async () => {
|
||||
const params = {
|
||||
namespace: namespace,
|
||||
path: path,
|
||||
};
|
||||
const [res] = await to(getTensorBoardStatusReq(params));
|
||||
if (res && res.data) {
|
||||
const status = res.data.status as TensorBoardStatus | undefined;
|
||||
setStatus(res.data.status);
|
||||
if (!status || status === TensorBoardStatus.Pending) {
|
||||
setTimeout(() => {
|
||||
getTensorBoardStatus();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
}, [namespace, path]);
|
||||
|
||||
return <>{tensorboardUrl && <IframePage type={IframePageType.TensorBoard}></IframePage>}</>;
|
||||
// 运行 TensorBoard
|
||||
const runTensorBoard = useCallback(async () => {
|
||||
const params = {
|
||||
namespace: namespace,
|
||||
path: path,
|
||||
};
|
||||
const [res] = await to(runTensorBoardReq(params));
|
||||
if (res && res.data) {
|
||||
const url = res.data;
|
||||
SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url);
|
||||
setTensorboardUrl(url);
|
||||
getTensorBoardStatus();
|
||||
} else {
|
||||
setTensorboardUrl(null);
|
||||
}
|
||||
}, [namespace, path, getTensorBoardStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (namespace && path) {
|
||||
runTensorBoard();
|
||||
}
|
||||
}, [namespace, path, runTensorBoard]);
|
||||
|
||||
if (tensorboardUrl === null || status === TensorBoardStatus.Failed) {
|
||||
return (
|
||||
<div className={styles['experiment-visual']}>
|
||||
<KFEmpty
|
||||
className={styles['experiment-visual__empty']}
|
||||
type={EmptyType.NoData}
|
||||
title="运行可视化失败"
|
||||
buttonTitle="重新运行"
|
||||
onButtonClick={runTensorBoard}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (status === TensorBoardStatus.Pending) {
|
||||
return (
|
||||
<div className={styles['experiment-visual']}>
|
||||
<KFSpin indicator={<LoadingOutlined spin />} size="large" />
|
||||
</div>
|
||||
);
|
||||
} else if (status === TensorBoardStatus.Running) {
|
||||
return (
|
||||
<div className={styles['experiment-visual']}>
|
||||
<IframePage type={IframePageType.TensorBoard}></IframePage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ExperimentVisualResult;
|
||||
|
|
|
|||
|
|
@ -118,12 +118,14 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
|
|||
},
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9._-]+$/,
|
||||
message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
|
||||
message: '数据集版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
|
||||
},
|
||||
{
|
||||
validator: (_rule, value) => {
|
||||
if (value === 'master') {
|
||||
return Promise.reject(`版本不能为 master`);
|
||||
return Promise.reject(`数据集版本不能为 master`);
|
||||
} else if (value === 'origin') {
|
||||
return Promise.reject(`数据集版本不能为 origin`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -109,12 +109,14 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
|
|||
},
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9._-]+$/,
|
||||
message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
|
||||
message: '模型版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
|
||||
},
|
||||
{
|
||||
validator: (_rule, value) => {
|
||||
if (value === 'master') {
|
||||
return Promise.reject(`版本不能为 master`);
|
||||
return Promise.reject(`模型版本不能为 master`);
|
||||
} else if (value === 'origin') {
|
||||
return Promise.reject(`模型版本不能为 origin`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
@ -126,7 +128,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
|
|||
<Form.Item label="模型框架" name="model_type">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="请选择模型类型"
|
||||
placeholder="请选择模型框架"
|
||||
options={typeList}
|
||||
fieldNames={{ label: 'name', value: 'name' }}
|
||||
optionFilterProp="name"
|
||||
|
|
@ -136,7 +138,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
|
|||
<Form.Item label="模型能力" name="model_tag">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="请选择模型标签"
|
||||
placeholder="请选择模型能力"
|
||||
options={tagList}
|
||||
fieldNames={{ label: 'name', value: 'name' }}
|
||||
optionFilterProp="name"
|
||||
|
|
|
|||
|
|
@ -132,12 +132,14 @@ function AddVersionModal({
|
|||
},
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9._-]+$/,
|
||||
message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
|
||||
message: `${name}版本只支持字母、数字、点(.)、下划线(_)、中横线(-)`,
|
||||
},
|
||||
{
|
||||
validator: (_rule, value) => {
|
||||
if (value === 'master') {
|
||||
return Promise.reject(`版本不能为 master`);
|
||||
return Promise.reject(`${name}版本不能为 master`);
|
||||
} else if (value === 'origin') {
|
||||
return Promise.reject(`${name}版本不能为 origin`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
import KFModal from '@/components/KFModal';
|
||||
import { DataSource, ResourceData, ResourceType, resourceConfig } from '@/pages/Dataset/config';
|
||||
import { to } from '@/utils/promise';
|
||||
import { Form, Input, message, type ModalProps } from 'antd';
|
||||
|
||||
interface EditVersionModalProps extends Omit<ModalProps, 'onOk'> {
|
||||
resourceType: ResourceType;
|
||||
resourceVersion: ResourceData;
|
||||
onOk: () => void;
|
||||
}
|
||||
|
||||
function EditVersionModal({ resourceType, resourceVersion, onOk, ...rest }: EditVersionModalProps) {
|
||||
const config = resourceConfig[resourceType];
|
||||
const { name: resoureName, version, version_desc } = resourceVersion;
|
||||
|
||||
// 修改请求
|
||||
const editDatasetVersion = async (params: any) => {
|
||||
const request = config.editVersion;
|
||||
const [res] = await to(request(params));
|
||||
if (res) {
|
||||
message.success('编辑成功');
|
||||
onOk?.();
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onFinish = (formData: any) => {
|
||||
const params = {
|
||||
...resourceVersion,
|
||||
...formData,
|
||||
[config.sourceParamKey]: DataSource.Create,
|
||||
};
|
||||
editDatasetVersion(params);
|
||||
};
|
||||
|
||||
const name = config.name;
|
||||
|
||||
return (
|
||||
<KFModal
|
||||
{...rest}
|
||||
title="编辑版本"
|
||||
image={require('@/assets/img/create-experiment.png')}
|
||||
width={825}
|
||||
okButtonProps={{
|
||||
htmlType: 'submit',
|
||||
form: 'form',
|
||||
}}
|
||||
>
|
||||
<Form
|
||||
name="form"
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
name: resoureName,
|
||||
version: version,
|
||||
version_desc: version_desc,
|
||||
}}
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
label={`${name}名称`}
|
||||
name="name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: `请输入${name}名称`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled placeholder={`请输入${name}名称`} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={`${name}版本`}
|
||||
name="version"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: `请输入${name}版本`,
|
||||
},
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9._-]+$/,
|
||||
message: `${name}版本只支持字母、数字、点(.)、下划线(_)、中横线(-)`,
|
||||
},
|
||||
{
|
||||
validator: (_rule, value) => {
|
||||
if (value === 'master') {
|
||||
return Promise.reject(`${name}版本不能为 master`);
|
||||
} else if (value === 'origin') {
|
||||
return Promise.reject(`${name}版本不能为 origin`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear disabled />
|
||||
</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>
|
||||
</KFModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditVersionModal;
|
||||
|
|
@ -42,6 +42,10 @@
|
|||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: .addAlpha(@primary-color, 0.5) [];
|
||||
}
|
||||
|
||||
&--praised {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { App, Button, Flex, Select, Tabs } from 'antd';
|
|||
import classNames from 'classnames';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import AddVersionModal from '../AddVersionModal';
|
||||
import EditVersionModal from '../EditVersionModal';
|
||||
import ResourceIntro from '../ResourceIntro';
|
||||
import ResourceVersion from '../ResourceVersion';
|
||||
import VersionCompareModal from '../VersionCompareModal';
|
||||
|
|
@ -132,6 +133,18 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
|
|||
});
|
||||
};
|
||||
|
||||
// 版本编辑
|
||||
const showEditVersionModal = () => {
|
||||
const { close } = openAntdModal(EditVersionModal, {
|
||||
resourceType: resourceType,
|
||||
resourceVersion: info,
|
||||
onOk: () => {
|
||||
getResourceDetail();
|
||||
close();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 选择版本
|
||||
const showVersionSelector = () => {
|
||||
const { close } = openAntdModal(VersionSelectorModal, {
|
||||
|
|
@ -291,6 +304,14 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
|
|||
<Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}>
|
||||
创建新版本
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
style={{ marginLeft: '20px' }}
|
||||
icon={<KFIcon type="icon-bianji" />}
|
||||
onClick={showEditVersionModal}
|
||||
>
|
||||
版本编辑
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
style={{ marginLeft: '20px' }}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ type ResourceItemProps = {
|
|||
};
|
||||
|
||||
function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) {
|
||||
const timeAgo = `更新于${
|
||||
item.update_time ? formatDate(item.update_time, 'YYYY-MM-DD') : item.time_ago ?? ''
|
||||
}`;
|
||||
const timeAgo = `最近更新:${formatDate(item.full_last_update_time, 'YYYY-MM-DD HH:mm')}`;
|
||||
const create_by = item.create_by ?? '';
|
||||
return (
|
||||
<div className={styles['resource-item']} onClick={() => onClick(item)}>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import styles from './index.less';
|
|||
|
||||
export type ResourceListRef = {
|
||||
reset: () => void;
|
||||
resetPage: () => void;
|
||||
};
|
||||
|
||||
type ResourceListProps = {
|
||||
|
|
@ -97,6 +98,12 @@ function ResourceList(
|
|||
setDataList(undefined);
|
||||
setTotal(0);
|
||||
},
|
||||
resetPage: () => {
|
||||
setPagination((prev) => ({
|
||||
...prev,
|
||||
current: 1,
|
||||
}));
|
||||
},
|
||||
};
|
||||
},
|
||||
[],
|
||||
|
|
|
|||
|
|
@ -56,11 +56,13 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
|
|||
|
||||
// 选择类型
|
||||
const chooseType = (record: CategoryData) => {
|
||||
dataListRef.current?.resetPage();
|
||||
setActiveType((prev) => (prev === record.name ? undefined : record.name));
|
||||
};
|
||||
|
||||
// 选择 Tag
|
||||
const chooseTag = (record: CategoryData) => {
|
||||
dataListRef.current?.resetPage();
|
||||
setActiveTag((prev) => (prev === record.name ? undefined : record.name));
|
||||
};
|
||||
|
||||
|
|
@ -96,7 +98,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
|
|||
dataType={activeType}
|
||||
dataTag={activeTag}
|
||||
initialSearchText={cacheState?.searchText}
|
||||
initialPagination={cacheState?.initialPagination}
|
||||
initialPagination={cacheState?.pagination}
|
||||
setCacheState={setCacheState}
|
||||
></ResourceList>
|
||||
</Flex>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import {
|
|||
deleteDatasetVersion,
|
||||
deleteModel,
|
||||
deleteModelVersion,
|
||||
editDatasetVersion,
|
||||
editModelVersion,
|
||||
getDatasetInfo,
|
||||
getDatasetList,
|
||||
getDatasetVersionList,
|
||||
|
|
@ -36,6 +38,7 @@ type ResourceTypeInfo = {
|
|||
getVersions: (params: any) => Promise<any>; // 获取版本列表
|
||||
deleteRecord: (params: any) => Promise<any>; // 删除
|
||||
addVersion: (params: any) => Promise<any>; // 新增版本
|
||||
editVersion: (params: any) => Promise<any>; // 编辑版本
|
||||
deleteVersion: (params: any) => Promise<any>; // 删除版本
|
||||
getInfo: (params: any) => Promise<any>; // 获取详情
|
||||
compareVersion: (params: any) => Promise<any>; // 版本对比
|
||||
|
|
@ -65,6 +68,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
|
|||
getVersions: getDatasetVersionList,
|
||||
deleteRecord: deleteDataset,
|
||||
addVersion: addDatasetVersion,
|
||||
editVersion: editDatasetVersion,
|
||||
deleteVersion: deleteDatasetVersion,
|
||||
getInfo: getDatasetInfo,
|
||||
compareVersion: compareDatasetVersion,
|
||||
|
|
@ -103,6 +107,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
|
|||
getVersions: getModelVersionList,
|
||||
deleteRecord: deleteModel,
|
||||
addVersion: addModelVersion,
|
||||
editVersion: editModelVersion,
|
||||
deleteVersion: deleteModelVersion,
|
||||
getInfo: getModelInfo,
|
||||
compareVersion: compareModelVersion,
|
||||
|
|
@ -164,6 +169,7 @@ export interface ResourceData {
|
|||
train_task?: TrainTask; // 训练任务
|
||||
praises_count: number; // 点赞数
|
||||
praised: boolean; // 是否点赞
|
||||
full_last_update_time: string; // 完整的更新时间
|
||||
}
|
||||
|
||||
// 数据集数据
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ const Docs = () => {
|
|||
return (
|
||||
<iframe
|
||||
style={{ width: '100%', height: '100%', border: 0 }}
|
||||
src={'/assets/材料科研软件平台使用文档.pdf'}
|
||||
src={'/assets/材料科研软件平台使用文档-v1.0.pdf'}
|
||||
></iframe>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ function ExperimentComparison() {
|
|||
const url = res.data;
|
||||
// window.open(url, '_blank');
|
||||
SessionStorage.setItem(SessionStorage.aimUrlKey, url);
|
||||
navigate('../compare-visual');
|
||||
navigate('compare-visual');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import { ExperimentStatus } from '@/enums';
|
|||
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';
|
||||
import { fittingString, parseJsonText } from '@/utils';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { getExperimentInstanceStatus } from '@/utils/experiment';
|
||||
import { to } from '@/utils/promise';
|
||||
import G6, { Util } from '@antv/g6';
|
||||
import { Button } from 'antd';
|
||||
|
|
@ -18,10 +18,12 @@ import { experimentStatusInfo } from '../status';
|
|||
import styles from './index.less';
|
||||
|
||||
let graph = null;
|
||||
const NodePrefix = 'workflow';
|
||||
|
||||
function ExperimentText() {
|
||||
const [experimentIns, setExperimentIns] = useState(undefined);
|
||||
const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined);
|
||||
const [workflowStatus, setWorkflowStatus] = useState(undefined);
|
||||
const graphRef = useRef();
|
||||
const workflowRef = useRef();
|
||||
const locationParams = useParams(); // 新版本获取路由参数接口
|
||||
|
|
@ -32,10 +34,12 @@ function ExperimentText() {
|
|||
const evtSourceRef = useRef();
|
||||
const width = 110;
|
||||
const height = 36;
|
||||
const status = getExperimentInstanceStatus(experimentIns?.status, workflowStatus);
|
||||
const statusInfo = experimentStatusInfo[status];
|
||||
|
||||
useEffect(() => {
|
||||
initGraph();
|
||||
getWorkflow();
|
||||
getExperimentInstance();
|
||||
|
||||
return () => {
|
||||
if (evtSourceRef.current) {
|
||||
|
|
@ -61,54 +65,83 @@ function ExperimentText() {
|
|||
}, []);
|
||||
|
||||
// 获取流水线模版
|
||||
const getWorkflow = async () => {
|
||||
const [res] = await to(getWorkflowById(locationParams.workflowId));
|
||||
if (res && res.data && res.data.dag) {
|
||||
try {
|
||||
const dag = JSON.parse(res.data.dag);
|
||||
dag.nodes.forEach((item) => {
|
||||
item.in_parameters = JSON.parse(item.in_parameters);
|
||||
item.out_parameters = JSON.parse(item.out_parameters);
|
||||
item.control_strategy = JSON.parse(item.control_strategy);
|
||||
item.imgName = item.img.slice(0, item.img.length - 4);
|
||||
});
|
||||
workflowRef.current = dag;
|
||||
getExperimentInstance();
|
||||
} catch (error) {
|
||||
// JSON.parse 错误
|
||||
console.error('JSON.parse error: ', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
// const getWorkflow = async () => {
|
||||
// const [res] = await to(getWorkflowById(locationParams.workflowId));
|
||||
// if (res && res.data && res.data.dag) {
|
||||
// try {
|
||||
// const dag = JSON.parse(res.data.dag);
|
||||
// dag.nodes.forEach((item) => {
|
||||
// item.in_parameters = JSON.parse(item.in_parameters);
|
||||
// item.out_parameters = JSON.parse(item.out_parameters);
|
||||
// item.control_strategy = JSON.parse(item.control_strategy);
|
||||
// item.imgName = item.img.slice(0, item.img.length - 4);
|
||||
// });
|
||||
// workflowRef.current = dag;
|
||||
// getExperimentInstance();
|
||||
// } catch (error) {
|
||||
// // JSON.parse 错误
|
||||
// console.error('JSON.parse error: ', error);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
// 获取实验实例
|
||||
const getExperimentInstance = async () => {
|
||||
const [res] = await to(getExperimentIns(locationParams.id));
|
||||
if (res && res.data && workflowRef.current) {
|
||||
if (res && res.data) {
|
||||
setExperimentIns(res.data);
|
||||
const { status, nodes_status, argo_ins_ns, argo_ins_name, finish_time } = res.data;
|
||||
const workflowData = workflowRef.current;
|
||||
const { status, nodes_status, argo_ins_ns, argo_ins_name, finish_time, dag } = res.data;
|
||||
if (!dag) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workflow = parseJsonText(dag);
|
||||
const experimentStatusObjs = parseJsonText(nodes_status);
|
||||
workflowData.nodes.forEach((item) => {
|
||||
const experimentNode = experimentStatusObjs?.[item.id];
|
||||
updateWorkflowNode(item, experimentNode);
|
||||
if (!workflow || !workflow.nodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
workflow.nodes.forEach((item) => {
|
||||
item.in_parameters = parseJsonText(item.in_parameters);
|
||||
item.out_parameters = parseJsonText(item.out_parameters);
|
||||
item.control_strategy = parseJsonText(item.control_strategy);
|
||||
item.imgName = item.img.slice(0, item.img.length - 4);
|
||||
});
|
||||
workflowRef.current = workflow;
|
||||
|
||||
if (experimentStatusObjs) {
|
||||
// 更新各个节点
|
||||
workflow.nodes.forEach((item) => {
|
||||
const experimentNode = experimentStatusObjs[item.id];
|
||||
updateWorkflowNode(item, experimentNode);
|
||||
});
|
||||
|
||||
// 设置 workflow 总状态
|
||||
Object.keys(experimentStatusObjs).some((key) => {
|
||||
if (key.startsWith(NodePrefix)) {
|
||||
const tempWorkflowStatus = experimentStatusObjs[key];
|
||||
setWorkflowStatus(tempWorkflowStatus);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// 绘制图
|
||||
getGraphData(workflowData, true);
|
||||
getGraphData(workflow, true);
|
||||
|
||||
if (status === ExperimentStatus.Pending) {
|
||||
// 如果状态是 Pending, 打开第一个节点
|
||||
const node = workflowData.nodes[0];
|
||||
const node = workflow.nodes[0];
|
||||
if (node) {
|
||||
setExperimentNodeData(node);
|
||||
openPropsDrawer();
|
||||
}
|
||||
} else if (status === ExperimentStatus.Running) {
|
||||
// 如果状态是 Running,打开第一个运行中的节点,如果没有运行中的节点,则打开第一个节点
|
||||
// 如果状态是 Running,打开第一个 Running 或者 pending 的节点,如果没有,则打开第一个节点
|
||||
const node =
|
||||
workflowData.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running) ??
|
||||
workflowData.nodes[0];
|
||||
workflow.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running || item.experimentStatus === ExperimentStatus.Pending) ??
|
||||
workflow.nodes[0];
|
||||
if (node) {
|
||||
setExperimentNodeData(node);
|
||||
openPropsDrawer();
|
||||
|
|
@ -135,23 +168,36 @@ function ExperimentText() {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
const dataJson = JSON.parse(data);
|
||||
const dataJson = parseJsonText(data);
|
||||
const statusData = dataJson?.result?.object?.status;
|
||||
if (!statusData) {
|
||||
return;
|
||||
}
|
||||
const { startedAt, finishedAt, phase, nodes = {} } = statusData;
|
||||
setExperimentIns((prev) => ({
|
||||
...prev,
|
||||
finish_time: finishedAt,
|
||||
status: phase,
|
||||
}));
|
||||
const { finishedAt, phase, nodes = {} } = statusData;
|
||||
|
||||
// 更新实验实例状态和结束时间
|
||||
// setExperimentIns((prev) => ({
|
||||
// ...prev,
|
||||
// finish_time: finishedAt,
|
||||
// status: phase,
|
||||
// }));
|
||||
|
||||
// 设置总 workflow 状态
|
||||
const tempWorkflowStatus = Object.values(nodes).find((node) =>
|
||||
node.displayName.startsWith(NodePrefix),
|
||||
);
|
||||
if (tempWorkflowStatus) {
|
||||
setWorkflowStatus(tempWorkflowStatus);
|
||||
}
|
||||
|
||||
// 更新各个节点
|
||||
const workflowData = workflowRef.current;
|
||||
workflowData.nodes.forEach((item) => {
|
||||
const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id);
|
||||
updateWorkflowNode(item, experimentNode);
|
||||
});
|
||||
|
||||
// 绘制图
|
||||
getGraphData(workflowData, false);
|
||||
|
||||
// 更新打开的抽屉数据
|
||||
|
|
@ -177,6 +223,7 @@ function ExperimentText() {
|
|||
evtSourceRef.current = evtSource;
|
||||
};
|
||||
|
||||
// 更新各个节点
|
||||
function updateWorkflowNode(workflowNode, statusNode) {
|
||||
if (!statusNode) {
|
||||
return;
|
||||
|
|
@ -471,29 +518,30 @@ function ExperimentText() {
|
|||
<div className={styles['pipeline-container']}>
|
||||
<div className={styles['pipeline-container__top']}>
|
||||
<div className={styles['pipeline-container__top__info']}>
|
||||
启动时间:{formatDate(experimentIns?.create_time)}
|
||||
启动时间:{formatDate(workflowStatus?.startedAt)}
|
||||
</div>
|
||||
<div className={styles['pipeline-container__top__info']}>
|
||||
执行时长:
|
||||
<RunDuration
|
||||
createTime={experimentIns?.create_time}
|
||||
finishTime={experimentIns?.finish_time}
|
||||
createTime={workflowStatus?.startedAt}
|
||||
finishTime={workflowStatus?.finishedAt}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles['pipeline-container__top__info']}>
|
||||
状态:
|
||||
<div
|
||||
style={{
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
marginRight: '6px',
|
||||
backgroundColor: experimentStatusInfo[experimentIns?.status]?.color,
|
||||
}}
|
||||
></div>
|
||||
<span style={{ color: experimentStatusInfo[experimentIns?.status]?.color }}>
|
||||
{experimentStatusInfo[experimentIns?.status]?.label}
|
||||
</span>
|
||||
{statusInfo ? (
|
||||
<>
|
||||
<img
|
||||
style={{ width: '17px', marginRight: '7px' }}
|
||||
src={statusInfo.icon}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
|
||||
</>
|
||||
) : (
|
||||
'--'
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
className={styles['pipeline-container__top__param-button']}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2025-03-31 16:38:59
|
||||
* @Description: 实验可视化 Tensorboard
|
||||
*/
|
||||
|
||||
import IframePage, { IframePageType } from '@/components/IFramePage';
|
||||
|
||||
function TensorboardPage() {
|
||||
return <IframePage type={IframePageType.TensorBoard}></IframePage>;
|
||||
}
|
||||
export default TensorboardPage;
|
||||
|
|
@ -30,7 +30,7 @@ interface Workflow {
|
|||
}
|
||||
|
||||
// 根据参数设置输入组件
|
||||
export const getParamComponent = (paramType: number, isSensitive?: number): JSX.Element => {
|
||||
export const getParamComponent = (paramType: number): JSX.Element => {
|
||||
// 防止后台返回不是 number 类型
|
||||
if (Number(paramType) === 3) {
|
||||
return (
|
||||
|
|
@ -40,9 +40,9 @@ export const getParamComponent = (paramType: number, isSensitive?: number): JSX.
|
|||
</Radio.Group>
|
||||
);
|
||||
}
|
||||
if (isSensitive && Number(isSensitive) === 1) {
|
||||
return <Input.Password placeholder="请输入值" visibilityToggle={false} allowClear />;
|
||||
}
|
||||
// if (isSensitive && Number(isSensitive) === 1) {
|
||||
// return <Input.Password placeholder="请输入值" visibilityToggle={false} allowClear />;
|
||||
// }
|
||||
return <Input placeholder="请输入值" allowClear />;
|
||||
};
|
||||
|
||||
|
|
@ -95,8 +95,8 @@ function AddExperimentModal({
|
|||
};
|
||||
|
||||
const layout = {
|
||||
labelCol: { span: 4 },
|
||||
wrapperCol: { span: 20 },
|
||||
labelCol: { span: 5 },
|
||||
wrapperCol: { span: 19 },
|
||||
};
|
||||
|
||||
const paramLayout = {
|
||||
|
|
@ -181,13 +181,13 @@ function AddExperimentModal({
|
|||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="选择流水线"
|
||||
label="选择流水线模板"
|
||||
name="workflow_id"
|
||||
rules={[{ required: true, message: '请选择流水线' }]}
|
||||
rules={[{ required: true, message: '请选择流水线模板' }]}
|
||||
>
|
||||
<Select
|
||||
disabled={workflowDisabled}
|
||||
placeholder="请选择流水线"
|
||||
placeholder="请选择流水线模板"
|
||||
onChange={handleWorkflowChange}
|
||||
>
|
||||
{Array.isArray(workflowList)
|
||||
|
|
@ -202,11 +202,7 @@ function AddExperimentModal({
|
|||
</Select>
|
||||
</Form.Item>
|
||||
{globalParam.length > 0 && (
|
||||
<Form.Item
|
||||
label="运行参数"
|
||||
tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示"
|
||||
{...tailLayout}
|
||||
>
|
||||
<Form.Item label="运行参数" tooltip="展示关联的流水线的参数" {...tailLayout}>
|
||||
<div className={styles.global_param_item}>
|
||||
<Form.List name="global_param">
|
||||
{(fields) =>
|
||||
|
|
@ -219,10 +215,7 @@ function AddExperimentModal({
|
|||
name={[name, 'param_value']}
|
||||
rules={getParamRules(globalParam[name]['param_type'], true)}
|
||||
>
|
||||
{getParamComponent(
|
||||
globalParam[name]['param_type'],
|
||||
globalParam[name]['is_sensitive'],
|
||||
)}
|
||||
{getParamComponent(globalParam[name]['param_type'])}
|
||||
</Form.Item>
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,10 +55,6 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
width: 160px;
|
||||
|
||||
.statusIcon {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,14 @@ function ExperimentInstanceList({
|
|||
}: ExperimentInstanceListProps) {
|
||||
const { message } = App.useApp();
|
||||
const allIntanceIds = useMemo(() => {
|
||||
return experimentInsList?.map((item) => item.id) || [];
|
||||
return (
|
||||
experimentInsList
|
||||
?.filter(
|
||||
(item) =>
|
||||
item.status !== ExperimentStatus.Running && item.status !== ExperimentStatus.Pending,
|
||||
)
|
||||
.map((item) => item.id) || []
|
||||
);
|
||||
}, [experimentInsList]);
|
||||
const [
|
||||
selectedIns,
|
||||
|
|
@ -127,7 +134,12 @@ function ExperimentInstanceList({
|
|||
<div>
|
||||
<div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
|
||||
<div className={styles.check}>
|
||||
<Checkbox checked={checked} indeterminate={indeterminate} onChange={checkAll}></Checkbox>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
indeterminate={indeterminate}
|
||||
disabled={allIntanceIds.length === 0}
|
||||
onChange={checkAll}
|
||||
></Checkbox>
|
||||
</div>
|
||||
<div className={styles.index}>序号</div>
|
||||
<div className={styles.tensorBoard}>可视化</div>
|
||||
|
|
@ -185,14 +197,7 @@ function ExperimentInstanceList({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ExperimentInstanceComponent
|
||||
create_time={item.create_time}
|
||||
finish_time={item.finish_time}
|
||||
status={item.status as ExperimentStatus}
|
||||
argo_ins_name={item.argo_ins_name}
|
||||
argo_ins_ns={item.argo_ins_ns}
|
||||
experimentInsId={item.id}
|
||||
></ExperimentInstanceComponent>
|
||||
<ExperimentInstanceComponent instance={item}></ExperimentInstanceComponent>
|
||||
|
||||
<div className={styles.operation}>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -2,69 +2,72 @@ import RunDuration from '@/components/RunDuration';
|
|||
import { ExperimentStatus } from '@/enums';
|
||||
import { useSSE, type MessageHandler } from '@/hooks/useSSE';
|
||||
import { experimentStatusInfo } from '@/pages/Experiment/status';
|
||||
import { ExperimentInstance, NodeStatus } from '@/types';
|
||||
import { ExperimentCompleted } from '@/utils/constant';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { getWorkflowStatus } from '@/utils/experiment';
|
||||
import { Typography } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import styles from './index.less';
|
||||
|
||||
type ExperimentInstanceProps = {
|
||||
create_time?: string;
|
||||
finish_time?: string;
|
||||
status: ExperimentStatus;
|
||||
argo_ins_name: string;
|
||||
argo_ins_ns: string;
|
||||
experimentInsId: number;
|
||||
type ExperimentInstanceComponentProps = {
|
||||
instance: ExperimentInstance;
|
||||
};
|
||||
|
||||
function ExperimentInstance({
|
||||
create_time,
|
||||
finish_time,
|
||||
status,
|
||||
argo_ins_name,
|
||||
argo_ins_ns,
|
||||
experimentInsId,
|
||||
}: ExperimentInstanceProps) {
|
||||
function ExperimentInstanceComponent({ instance }: ExperimentInstanceComponentProps) {
|
||||
const { id, experiment_id, argo_ins_name, argo_ins_ns, nodes_status, create_time, finish_time } =
|
||||
instance;
|
||||
const workflowStatus = getWorkflowStatus(nodes_status) as NodeStatus | undefined;
|
||||
const status = instance.status as ExperimentStatus;
|
||||
const createTime = workflowStatus?.startedAt ?? create_time;
|
||||
const finishTime = workflowStatus?.finishedAt ?? finish_time;
|
||||
const statusInfo = experimentStatusInfo[status];
|
||||
|
||||
const handleSSEMessage: MessageHandler = useCallback(
|
||||
(experimentInsId: number, status: string, finish_time: string) => {
|
||||
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
|
||||
window.postMessage({
|
||||
type: ExperimentCompleted,
|
||||
payload: {
|
||||
id: experimentInsId,
|
||||
experimentId,
|
||||
experimentInsId,
|
||||
status,
|
||||
finish_time,
|
||||
finishTime,
|
||||
},
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
useSSE(experimentInsId, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
|
||||
useSSE(experiment_id, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={styles.description}>
|
||||
<div style={{ width: '50%' }}>
|
||||
<RunDuration createTime={create_time} finishTime={finish_time} />
|
||||
<RunDuration createTime={createTime} finishTime={finishTime} />
|
||||
</div>
|
||||
<div style={{ width: '50%' }} className={styles.startTime}>
|
||||
<Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}>
|
||||
{formatDate(create_time)}
|
||||
<Typography.Text ellipsis={{ tooltip: formatDate(createTime) }}>
|
||||
{formatDate(createTime)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.statusBox}>
|
||||
<img
|
||||
style={{ width: '17px', marginRight: '7px' }}
|
||||
src={experimentStatusInfo[status]?.icon}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
<span style={{ color: experimentStatusInfo[status]?.color }} className={styles.statusIcon}>
|
||||
{experimentStatusInfo[status]?.label}
|
||||
</span>
|
||||
{statusInfo ? (
|
||||
<>
|
||||
<img
|
||||
style={{ width: '17px', marginRight: '7px' }}
|
||||
src={statusInfo.icon}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
|
||||
</>
|
||||
) : (
|
||||
'--'
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExperimentInstance;
|
||||
export default ExperimentInstanceComponent;
|
||||
|
|
|
|||
|
|
@ -11,9 +11,15 @@ type ExperimentParameterProps = {
|
|||
|
||||
function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
|
||||
// 控制策略
|
||||
const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map(
|
||||
([key, value]) => ({ key, value }),
|
||||
);
|
||||
// const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map(
|
||||
// ([key, value]) => ({ key, value }),
|
||||
// );
|
||||
const nodeId = nodeData.id;
|
||||
const hasTaskInfo =
|
||||
nodeId &&
|
||||
!nodeId.startsWith('git-clone') &&
|
||||
!nodeId.startsWith('dataset-export') &&
|
||||
!nodeId.startsWith('model-export');
|
||||
|
||||
// 输入参数
|
||||
const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({
|
||||
|
|
@ -74,54 +80,58 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
|
|||
>
|
||||
<FormInfo />
|
||||
</Form.Item>
|
||||
<div className={styles['experiment-parameter__title']}>
|
||||
<SubAreaTitle
|
||||
image={require('@/assets/img/duty-message.png')}
|
||||
title="任务信息"
|
||||
></SubAreaTitle>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="镜像"
|
||||
name="image"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入镜像',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormInfo />
|
||||
</Form.Item>
|
||||
<Form.Item label="工作目录" name="working_directory">
|
||||
<FormInfo />
|
||||
</Form.Item>
|
||||
{hasTaskInfo && (
|
||||
<>
|
||||
<div className={styles['experiment-parameter__title']}>
|
||||
<SubAreaTitle
|
||||
image={require('@/assets/img/duty-message.png')}
|
||||
title="任务信息"
|
||||
></SubAreaTitle>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="镜像"
|
||||
name="image"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入镜像',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormInfo />
|
||||
</Form.Item>
|
||||
<Form.Item label="工作目录" name="working_directory">
|
||||
<FormInfo />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="启动命令" name="command">
|
||||
<FormInfo textArea />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="资源规格"
|
||||
name="resources_standard"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入资源规格',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ParameterSelect dataType="resource" placeholder="请选择资源规格" display />
|
||||
</Form.Item>
|
||||
<Form.Item label="挂载路径" name="mount_path">
|
||||
<FormInfo />
|
||||
</Form.Item>
|
||||
<Form.Item label="环境变量" name="env_variables">
|
||||
<FormInfo textArea />
|
||||
</Form.Item>
|
||||
{controlStrategyList.map((item) => (
|
||||
<Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}>
|
||||
<FormInfo valuePropName="showValue" />
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item label="启动命令" name="command">
|
||||
<FormInfo textArea />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="资源规格"
|
||||
name="resources_standard"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入资源规格',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ParameterSelect dataType="resource" placeholder="请选择资源规格" display />
|
||||
</Form.Item>
|
||||
{/* <Form.Item label="挂载路径" name="mount_path">
|
||||
<FormInfo />
|
||||
</Form.Item> */}
|
||||
<Form.Item label="环境变量" name="env_variables">
|
||||
<FormInfo textArea />
|
||||
</Form.Item>
|
||||
{/* {controlStrategyList.map((item) => (
|
||||
<Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}>
|
||||
<FormInfo valuePropName="showValue" />
|
||||
</Form.Item>
|
||||
))} */}
|
||||
</>
|
||||
)}
|
||||
<div className={styles['experiment-parameter__title']}>
|
||||
<SubAreaTitle
|
||||
image={require('@/assets/img/duty-message.png')}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ function ExportModelModal({
|
|||
const params = {
|
||||
...formData,
|
||||
identifier: resource?.identifier,
|
||||
owner: resource?.owner,
|
||||
is_public: resource?.is_public,
|
||||
name: resource?.name,
|
||||
[config.sourceParamKey]: DataSource.HandExport,
|
||||
train_task: {
|
||||
|
|
@ -174,6 +176,8 @@ function ExportModelModal({
|
|||
onChange={handleResourceChange}
|
||||
options={resources}
|
||||
fieldNames={{ label: 'name', value: 'id' }}
|
||||
optionFilterProp="name"
|
||||
showSearch
|
||||
allowClear
|
||||
></Select>
|
||||
</Form.Item>
|
||||
|
|
@ -191,9 +195,17 @@ function ExportModelModal({
|
|||
}
|
||||
rules={[
|
||||
{ required: true, message: `请输入${config.name}版本` },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9._-]+$/,
|
||||
message: `${config.name}版本只支持字母、数字、点(.)、下划线(_)、中横线(-)`,
|
||||
},
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (value && versions.map((item) => item.name).includes(value)) {
|
||||
if (value === 'master') {
|
||||
return Promise.reject(`${config.name}版本不能为 master`);
|
||||
} else if (value === 'origin') {
|
||||
return Promise.reject(`${config.name}版本不能为 origin`);
|
||||
} else if (value && versions.map((item) => item.name).includes(value)) {
|
||||
return Promise.reject(`${config.name}版本已存在`);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
|
|
|
|||
|
|
@ -54,7 +54,10 @@ function LogGroup({
|
|||
useEffect(() => {
|
||||
// 建立 socket 连接
|
||||
const setupSockect = () => {
|
||||
const { host } = location;
|
||||
let { host } = location;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
host = '172.20.32.235:31213';
|
||||
}
|
||||
const socket = new WebSocket(
|
||||
`ws://${host}/newlog/realtimeLog?start=${start_time}&query={pod="${pod_name}"}`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -47,10 +47,7 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) {
|
|||
name={[name, 'param_value']}
|
||||
label={getParamLabel(globalParam[name])}
|
||||
>
|
||||
{getParamComponent(
|
||||
globalParam[name]['param_type'],
|
||||
globalParam[name]['is_sensitive'],
|
||||
)}
|
||||
{getParamComponent(globalParam[name]['param_type'])}
|
||||
</Form.Item>
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import KFIcon from '@/components/KFIcon';
|
|||
import PageTitle from '@/components/PageTitle';
|
||||
import { ExperimentStatus, TensorBoardStatus } from '@/enums';
|
||||
import { useCacheState } from '@/hooks/useCacheState';
|
||||
import { useServerTime } from '@/hooks/useServerTime';
|
||||
import {
|
||||
deleteExperimentById,
|
||||
editExperimentInsReq,
|
||||
getExperiment,
|
||||
getExperimentById,
|
||||
getQueryByExperimentId,
|
||||
|
|
@ -17,6 +19,7 @@ import { getWorkflow } from '@/services/pipeline/index.js';
|
|||
import themes from '@/styles/theme.less';
|
||||
import { ExperimentCompleted } from '@/utils/constant';
|
||||
import { to } from '@/utils/promise';
|
||||
import SessionStorage from '@/utils/sessionStorage';
|
||||
import tableCellRender, { TableCellValueType } from '@/utils/table';
|
||||
import { modalConfirm } from '@/utils/ui';
|
||||
import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd';
|
||||
|
|
@ -28,7 +31,6 @@ import AddExperimentModal from './components/AddExperimentModal';
|
|||
import ExperimentInstanceList from './components/ExperimentInstanceList';
|
||||
import styles from './index.less';
|
||||
import { experimentStatusInfo } from './status';
|
||||
import { useServerTime } from '@/hooks/useServerTime';
|
||||
|
||||
// 定时器
|
||||
const timerIds = new Map();
|
||||
|
|
@ -39,7 +41,7 @@ function Experiment() {
|
|||
const [workflowList, setWorkflowList] = useState([]);
|
||||
const [experimentId, setExperimentId] = useState(null);
|
||||
const [experimentInsList, setExperimentInsList] = useState([]);
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState(null);
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [isAdd, setIsAdd] = useState(true);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
|
@ -59,166 +61,38 @@ function Experiment() {
|
|||
const timerRef = useRef();
|
||||
|
||||
// 获取实验列表
|
||||
const getExperimentList = useCallback(async () => {
|
||||
const params = {
|
||||
page: pagination.current - 1,
|
||||
size: pagination.pageSize,
|
||||
name: searchText || undefined,
|
||||
};
|
||||
const [res] = await to(getExperiment(params));
|
||||
if (res && res.data && Array.isArray(res.data.content)) {
|
||||
setExperimentList(
|
||||
res.data.content.map((item) => {
|
||||
return { ...item, key: item.id };
|
||||
}),
|
||||
);
|
||||
const getExperimentList = useCallback(
|
||||
async (skipLoading = false) => {
|
||||
const params = {
|
||||
page: pagination.current - 1,
|
||||
size: pagination.pageSize,
|
||||
name: searchText || undefined,
|
||||
};
|
||||
const [res] = await to(getExperiment(params, skipLoading));
|
||||
if (res && res.data && Array.isArray(res.data.content)) {
|
||||
setExperimentList(
|
||||
res.data.content.map((item) => {
|
||||
return { ...item, key: item.id };
|
||||
}),
|
||||
);
|
||||
|
||||
setTotal(res.data.totalElements);
|
||||
}
|
||||
}, [pagination, searchText]);
|
||||
setTotal(res.data.totalElements);
|
||||
}
|
||||
},
|
||||
[pagination, searchText],
|
||||
);
|
||||
|
||||
// 刷新实验列表状态,
|
||||
// 目前是直接刷新实验列表,后续需要优化,只刷新状态
|
||||
const refreshExperimentList = useCallback(() => {
|
||||
getExperimentList();
|
||||
}, [getExperimentList]);
|
||||
|
||||
// 获取流水线列表
|
||||
useEffect(() => {
|
||||
// 获取流水线列表
|
||||
const getWorkflowList = async () => {
|
||||
const queryFlow = {
|
||||
offset: 1,
|
||||
page: 0,
|
||||
size: 10000,
|
||||
name: null,
|
||||
};
|
||||
const [res] = await to(getWorkflow(queryFlow));
|
||||
if (res && res.data && res.data.content) {
|
||||
setWorkflowList(res.data.content);
|
||||
}
|
||||
};
|
||||
|
||||
getWorkflowList();
|
||||
return () => {
|
||||
clearExperimentInTimers();
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 获取实验列表
|
||||
useEffect(() => {
|
||||
getExperimentList();
|
||||
}, [getExperimentList]);
|
||||
|
||||
// 新增,删除版本时,重置分页,然后刷新版本列表
|
||||
useEffect(() => {
|
||||
const handleMessage = (e) => {
|
||||
const { type, payload } = e.data;
|
||||
if (type === ExperimentCompleted) {
|
||||
const { id, status, finish_time } = payload;
|
||||
|
||||
// 修改实例的状态和结束时间
|
||||
setExperimentInsList((prev) =>
|
||||
prev.map((v) =>
|
||||
v.id === id
|
||||
? {
|
||||
...v,
|
||||
status: status,
|
||||
finish_time: finish_time,
|
||||
}
|
||||
: v,
|
||||
),
|
||||
);
|
||||
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = undefined;
|
||||
}
|
||||
|
||||
timerRef.current = setTimeout(() => {
|
||||
refreshExperimentList();
|
||||
}, 10000);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => {
|
||||
window.removeEventListener('message', handleMessage);
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = undefined;
|
||||
}
|
||||
};
|
||||
}, [refreshExperimentList]);
|
||||
|
||||
// 搜索
|
||||
const onSearch = (value) => {
|
||||
setSearchText(value);
|
||||
setPagination((prev) => ({
|
||||
...prev,
|
||||
current: 1,
|
||||
}));
|
||||
};
|
||||
|
||||
// 获取实验实例列表
|
||||
const getQueryByExperiment = async (experimentId, page, size = 5) => {
|
||||
const params = {
|
||||
experimentId: experimentId,
|
||||
page: page,
|
||||
size: size,
|
||||
};
|
||||
const [res, error] = await to(getQueryByExperimentId(params));
|
||||
if (res && res.data) {
|
||||
const { content = [], totalElements = 0 } = res.data;
|
||||
setExpandedRowKeys(experimentId);
|
||||
try {
|
||||
const list = content.map((v) => {
|
||||
const nodes_result = v.nodes_result ? JSON.parse(v.nodes_result) : {};
|
||||
return {
|
||||
...v,
|
||||
nodes_result,
|
||||
};
|
||||
});
|
||||
if (page === 0) {
|
||||
setExperimentInsList(list);
|
||||
clearExperimentInTimers();
|
||||
} else {
|
||||
setExperimentInsList((prev) => [...prev, ...list]);
|
||||
}
|
||||
setExperimentInsTotal(totalElements);
|
||||
// 获取 TensorBoard 状态
|
||||
list.forEach((item) => {
|
||||
if (item.nodes_result?.tensorboard_log) {
|
||||
getTensorBoardStatus(item);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('JSON parse error: ', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 运行 TensorBoard
|
||||
const runTensorBoard = async (experimentIn) => {
|
||||
const params = {
|
||||
namespace: experimentIn.nodes_result.tensorboard_log.namespace,
|
||||
path: experimentIn.nodes_result.tensorboard_log.path,
|
||||
pvc_name: experimentIn.nodes_result.tensorboard_log.pvc_name,
|
||||
};
|
||||
const [res] = await to(runTensorBoardReq(params));
|
||||
if (res) {
|
||||
experimentIn.tensorboardUrl = res.data;
|
||||
const timerId = timerIds.get(experimentIn.id);
|
||||
if (timerId) {
|
||||
clearTimeout(timerId);
|
||||
timerIds.delete(experimentIn.id);
|
||||
getTensorBoardStatus(experimentIn);
|
||||
}
|
||||
}
|
||||
};
|
||||
const refreshExperimentList = useCallback(
|
||||
(skipLoading = false) => {
|
||||
getExperimentList(skipLoading);
|
||||
},
|
||||
[getExperimentList],
|
||||
);
|
||||
|
||||
// 获取 TensorBoard 状态
|
||||
const getTensorBoardStatus = async (experimentIn) => {
|
||||
const getTensorBoardStatus = useCallback(async (experimentIn) => {
|
||||
const params = {
|
||||
namespace: experimentIn.nodes_result.tensorboard_log.namespace,
|
||||
path: experimentIn.nodes_result.tensorboard_log.path,
|
||||
|
|
@ -249,17 +123,196 @@ function Experiment() {
|
|||
}, 10 * 1000);
|
||||
timerIds.set(experimentIn.id, timerId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 获取实验实例列表
|
||||
const getExperimentInsList = useCallback(
|
||||
async (experimentId, page, size = 5, skipLoading = false) => {
|
||||
const params = {
|
||||
experimentId: experimentId,
|
||||
page: page,
|
||||
size: size,
|
||||
};
|
||||
const [res, error] = await to(getQueryByExperimentId(params, skipLoading));
|
||||
if (res && res.data) {
|
||||
const { content = [], totalElements = 0 } = res.data;
|
||||
try {
|
||||
const list = content.map((v) => {
|
||||
const nodes_result = v.nodes_result ? JSON.parse(v.nodes_result) : {};
|
||||
return {
|
||||
...v,
|
||||
nodes_result,
|
||||
};
|
||||
});
|
||||
if (page === 0) {
|
||||
setExperimentInsList(list);
|
||||
clearExperimentInTimers();
|
||||
} else {
|
||||
setExperimentInsList((prev) => [...prev, ...list]);
|
||||
}
|
||||
setExperimentInsTotal(totalElements);
|
||||
// 获取 TensorBoard 状态
|
||||
list.forEach((item) => {
|
||||
if (item.nodes_result?.tensorboard_log) {
|
||||
getTensorBoardStatus(item);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('JSON parse error: ', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
[getTensorBoardStatus],
|
||||
);
|
||||
|
||||
// 刷新实验实例列表
|
||||
const refreshExperimentIns = useCallback(
|
||||
(experimentId, skipLoading = false) => {
|
||||
const length = experimentInsList.length;
|
||||
getExperimentInsList(experimentId, 0, length, skipLoading);
|
||||
},
|
||||
[experimentInsList, getExperimentInsList],
|
||||
);
|
||||
|
||||
// 更新实验状态
|
||||
const editExperimentIns = useCallback(
|
||||
async (experimentId, experimentInsId, status, argo_ins_name, argo_ins_ns) => {
|
||||
const params = {
|
||||
experiment_id: experimentId,
|
||||
id: experimentInsId,
|
||||
status: status,
|
||||
argo_ins_name,
|
||||
argo_ins_ns,
|
||||
};
|
||||
const [res, error] = await to(editExperimentInsReq(params));
|
||||
if (res && res.data) {
|
||||
refreshExperimentIns(experimentId, true);
|
||||
refreshExperimentList(true);
|
||||
}
|
||||
},
|
||||
[refreshExperimentIns, refreshExperimentList],
|
||||
);
|
||||
|
||||
// 获取流水线列表
|
||||
useEffect(() => {
|
||||
// 获取流水线列表
|
||||
const getWorkflowList = async () => {
|
||||
const queryFlow = {
|
||||
offset: 1,
|
||||
page: 0,
|
||||
size: 10000,
|
||||
name: null,
|
||||
};
|
||||
const [res] = await to(getWorkflow(queryFlow));
|
||||
if (res && res.data && res.data.content) {
|
||||
setWorkflowList(res.data.content);
|
||||
}
|
||||
};
|
||||
|
||||
getWorkflowList();
|
||||
return () => {
|
||||
clearExperimentInTimers();
|
||||
};
|
||||
}, []);
|
||||
// 获取实验列表
|
||||
useEffect(() => {
|
||||
getExperimentList();
|
||||
}, [getExperimentList]);
|
||||
|
||||
// 更新实验实例状态
|
||||
useEffect(() => {
|
||||
const handleMessage = (e) => {
|
||||
const { type, payload } = e.data;
|
||||
if (type === ExperimentCompleted) {
|
||||
const { experimentId, experimentInsId, status, finishTime } = payload;
|
||||
const currentIns = experimentInsList.find((v) => v.id === experimentInsId);
|
||||
console.log(
|
||||
'实验实例状态变化',
|
||||
currentIns?.status,
|
||||
status,
|
||||
experimentId,
|
||||
experimentInsId,
|
||||
finishTime,
|
||||
);
|
||||
|
||||
if (
|
||||
!currentIns ||
|
||||
currentIns.status === ExperimentStatus.Terminated ||
|
||||
currentIns.status === status
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
editExperimentIns(
|
||||
experimentId,
|
||||
experimentInsId,
|
||||
status,
|
||||
currentIns.argo_ins_name,
|
||||
currentIns.argo_ins_ns,
|
||||
);
|
||||
|
||||
// refreshExperimentList(true);
|
||||
// refreshExperimentIns(experimentId);
|
||||
|
||||
// 修改实例的状态和结束时间
|
||||
// setExperimentInsList((prev) =>
|
||||
// prev.map((v) =>
|
||||
// v.id === experimentInsId
|
||||
// ? {
|
||||
// ...v,
|
||||
// status: status,
|
||||
// finish_time: finishTime,
|
||||
// }
|
||||
// : v,
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => {
|
||||
window.removeEventListener('message', handleMessage);
|
||||
};
|
||||
}, [experimentInsList, editExperimentIns]);
|
||||
|
||||
// 搜索
|
||||
const onSearch = (value) => {
|
||||
setSearchText(value);
|
||||
setPagination((prev) => ({
|
||||
...prev,
|
||||
current: 1,
|
||||
}));
|
||||
};
|
||||
|
||||
// 运行 TensorBoard
|
||||
const runTensorBoard = async (experimentIn) => {
|
||||
const params = {
|
||||
namespace: experimentIn.nodes_result.tensorboard_log.namespace,
|
||||
path: experimentIn.nodes_result.tensorboard_log.path,
|
||||
pvc_name: experimentIn.nodes_result.tensorboard_log.pvc_name,
|
||||
};
|
||||
const [res] = await to(runTensorBoardReq(params));
|
||||
if (res) {
|
||||
experimentIn.tensorboardUrl = res.data;
|
||||
const timerId = timerIds.get(experimentIn.id);
|
||||
if (timerId) {
|
||||
clearTimeout(timerId);
|
||||
timerIds.delete(experimentIn.id);
|
||||
getTensorBoardStatus(experimentIn);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 展开实例
|
||||
const expandChange = (e, record) => {
|
||||
const expandChange = (expanded, record) => {
|
||||
clearExperimentInTimers();
|
||||
setExperimentInsList([]);
|
||||
if (record.id === expandedRowKeys) {
|
||||
setExpandedRowKeys(null);
|
||||
} else {
|
||||
getQueryByExperiment(record.id, 0, 5);
|
||||
if (expanded) {
|
||||
setExpandedRowKeys([record.id]);
|
||||
getExperimentInsList(record.id, 0, 5);
|
||||
refreshExperimentList();
|
||||
} else {
|
||||
setExpandedRowKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -337,8 +390,9 @@ function Experiment() {
|
|||
const [res] = await to(runExperiments(id));
|
||||
if (res) {
|
||||
message.success('运行成功');
|
||||
setExpandedRowKeys([id]);
|
||||
refreshExperimentList();
|
||||
getQueryByExperiment(id, 0, 5);
|
||||
getExperimentInsList(id, 0, 5);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -372,14 +426,16 @@ function Experiment() {
|
|||
experimentIn.tensorBoardStatus === TensorBoardStatus.Running &&
|
||||
experimentIn.tensorboardUrl
|
||||
) {
|
||||
window.open(experimentIn.tensorboardUrl, '_blank');
|
||||
const url = experimentIn.tensorboardUrl;
|
||||
SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url);
|
||||
navigateToUrl(`/pipeline/experiment/visual`);
|
||||
// window.open(experimentIn.tensorboardUrl, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
// 实验实例终止
|
||||
const handleInstanceTerminate = async (experimentIn) => {
|
||||
// 刷新实验列表
|
||||
refreshExperimentList();
|
||||
// 修改实例的状态和结束时间
|
||||
setExperimentInsList((prevList) => {
|
||||
return prevList.map((item) => {
|
||||
if (item.id === experimentIn.id) {
|
||||
|
|
@ -392,6 +448,9 @@ function Experiment() {
|
|||
return item;
|
||||
});
|
||||
});
|
||||
// 刷新实验列表和实例列表
|
||||
refreshExperimentList(true);
|
||||
refreshExperimentIns(experimentIn.experiment_id);
|
||||
};
|
||||
|
||||
// 实验对比菜单
|
||||
|
|
@ -413,16 +472,10 @@ function Experiment() {
|
|||
};
|
||||
};
|
||||
|
||||
// 刷新实验实例列表
|
||||
const refreshExperimentIns = (experimentId) => {
|
||||
const length = experimentInsList.length;
|
||||
getQueryByExperiment(experimentId, 0, length);
|
||||
};
|
||||
|
||||
// 加载更多实验实例
|
||||
const loadMoreExperimentIns = () => {
|
||||
const page = Math.round(experimentInsList.length / 5);
|
||||
getQueryByExperiment(expandedRowKeys, page, 5);
|
||||
getExperimentInsList(expandedRowKeys[0], page, 5);
|
||||
};
|
||||
|
||||
// 处理删除
|
||||
|
|
@ -607,7 +660,7 @@ function Experiment() {
|
|||
></ExperimentInstanceList>
|
||||
),
|
||||
onExpand: expandChange,
|
||||
expandedRowKeys: [expandedRowKeys],
|
||||
expandedRowKeys: expandedRowKeys,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-04-16 13:58:08
|
||||
* @Description: 自主机器学习详情
|
||||
* @Description: 自动机器学习详情
|
||||
*/
|
||||
import PageTitle from '@/components/PageTitle';
|
||||
import { getRayInfoReq } from '@/services/hyperParameter';
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ function HyperParameterInstance() {
|
|||
const [res] = await to(getRayInsReq(instanceId));
|
||||
if (res && res.data) {
|
||||
const info = res.data as HyperParameterInstanceData;
|
||||
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info;
|
||||
const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
|
||||
// 解析配置参数
|
||||
const paramJson = parseJsonText(param).data;
|
||||
if (paramJson) {
|
||||
|
|
@ -72,7 +72,6 @@ function HyperParameterInstance() {
|
|||
}
|
||||
setExperimentInfo({
|
||||
...paramJson,
|
||||
create_time,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -121,18 +120,17 @@ function HyperParameterInstance() {
|
|||
if (dataJson) {
|
||||
const nodes = dataJson?.result?.object?.status?.nodes;
|
||||
if (nodes) {
|
||||
// 节点
|
||||
// 设置节点
|
||||
setNodes(nodes);
|
||||
|
||||
// 设置总 workflow 状态
|
||||
const workflowStatus = Object.values(nodes).find((node: any) =>
|
||||
node.displayName.startsWith(NodePrefix),
|
||||
) as NodeStatus;
|
||||
|
||||
// 设置工作流状态
|
||||
if (workflowStatus) {
|
||||
setWorkflowStatus(workflowStatus);
|
||||
|
||||
// 实验结束,关闭 SSE
|
||||
// 实验结束,关闭 SSE,获取实验实例结果
|
||||
if (
|
||||
workflowStatus.phase !== ExperimentStatus.Pending &&
|
||||
workflowStatus.phase !== ExperimentStatus.Running
|
||||
|
|
@ -167,8 +165,8 @@ function HyperParameterInstance() {
|
|||
<HyperParameterBasic
|
||||
className={styles['hyper-parameter-instance__basic']}
|
||||
info={experimentInfo}
|
||||
runStatus={workflowStatus}
|
||||
instanceStatus={instanceInfo?.status}
|
||||
workflowStatus={workflowStatus}
|
||||
instanceStatus={instanceInfo?.status as ExperimentStatus}
|
||||
isInstance
|
||||
/>
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ import TrialStatusCell from '@/pages/HyperParameter/components/TrialStatusCell';
|
|||
import { HyperParameterTrial } from '@/pages/HyperParameter/types';
|
||||
import { getExpMetricsReq } from '@/services/hyperParameter';
|
||||
import { to } from '@/utils/promise';
|
||||
import SessionStorage from '@/utils/sessionStorage';
|
||||
import tableCellRender, { TableCellValueType } from '@/utils/table';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import { App, Button, Table, type TableProps } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
|
@ -36,6 +38,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {
|
|||
const metricAnalysis: Record<string, any> = first?.metric_analysis ?? {};
|
||||
const paramsNames = Object.keys(config);
|
||||
const metricNames = Object.keys(metricAnalysis);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const trialColumns: TableProps<HyperParameterTrial>['columns'] = [
|
||||
{
|
||||
|
|
@ -160,7 +163,8 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {
|
|||
const [res] = await to(getExpMetricsReq(selectedRowKeys));
|
||||
if (res && res.data) {
|
||||
const url = res.data;
|
||||
window.open(url, '_blank');
|
||||
SessionStorage.setItem(SessionStorage.aimUrlKey, url);
|
||||
navigate('compare-visual');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ExperimentStatus } from '@/enums';
|
||||
import EmptyLog from '@/pages/AutoML/components/ExperimentLog/empty';
|
||||
import LogList from '@/pages/Experiment/components/LogList';
|
||||
import { HyperParameterInstanceData } from '@/pages/HyperParameter/types';
|
||||
import { NodeStatus } from '@/types';
|
||||
|
|
@ -64,7 +65,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
|
|||
// icon: <KFIcon type="icon-rizhi1" />,
|
||||
children: (
|
||||
<div className={styles['experiment-log__tabs__log']}>
|
||||
{trainCloneNodeStatus && (
|
||||
{trainCloneNodeStatus ? (
|
||||
<LogList
|
||||
instanceName={instanceInfo.argo_ins_name}
|
||||
instanceNamespace={instanceInfo.argo_ins_ns}
|
||||
|
|
@ -73,6 +74,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
|
|||
instanceNodeStartTime={trainCloneNodeStatus.startedAt}
|
||||
instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus}
|
||||
></LogList>
|
||||
) : (
|
||||
<EmptyLog />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
|
|
@ -83,7 +86,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
|
|||
// icon: <KFIcon type="icon-rizhi1" />,
|
||||
children: (
|
||||
<div className={styles['experiment-log__tabs__log']}>
|
||||
{hpoNodeStatus && (
|
||||
{hpoNodeStatus ? (
|
||||
<LogList
|
||||
instanceName={instanceInfo.argo_ins_name}
|
||||
instanceNamespace={instanceInfo.argo_ins_ns}
|
||||
|
|
@ -92,6 +95,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
|
|||
instanceNodeStartTime={hpoNodeStatus.startedAt}
|
||||
instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus}
|
||||
></LogList>
|
||||
) : (
|
||||
<EmptyLog />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo';
|
||||
import RunDuration from '@/components/RunDuration';
|
||||
import { ExperimentStatus, hyperParameterOptimizedMode } from '@/enums';
|
||||
import { useComputingResource } from '@/hooks/useComputingResource';
|
||||
import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic';
|
||||
import { experimentStatusInfo } from '@/pages/Experiment/status';
|
||||
import {
|
||||
schedulerAlgorithms,
|
||||
searchAlgorithms,
|
||||
|
|
@ -18,7 +16,6 @@ import {
|
|||
formatMirror,
|
||||
formatModel,
|
||||
} from '@/utils/format';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { useMemo } from 'react';
|
||||
import ParameterInfo from '../ParameterInfo';
|
||||
|
|
@ -33,14 +30,14 @@ type HyperParameterBasicProps = {
|
|||
info?: HyperParameterData;
|
||||
className?: string;
|
||||
isInstance?: boolean;
|
||||
runStatus?: NodeStatus;
|
||||
workflowStatus?: NodeStatus;
|
||||
instanceStatus?: ExperimentStatus;
|
||||
};
|
||||
|
||||
function HyperParameterBasic({
|
||||
info,
|
||||
className,
|
||||
runStatus,
|
||||
workflowStatus,
|
||||
instanceStatus,
|
||||
isInstance = false,
|
||||
}: HyperParameterBasicProps) {
|
||||
|
|
@ -83,7 +80,7 @@ function HyperParameterBasic({
|
|||
}
|
||||
return [
|
||||
{
|
||||
label: '代码',
|
||||
label: '代码配置',
|
||||
value: info.code_config,
|
||||
format: formatCodeConfig,
|
||||
},
|
||||
|
|
@ -145,56 +142,10 @@ function HyperParameterBasic({
|
|||
];
|
||||
}, [info, getResourceDescription]);
|
||||
|
||||
const instanceDatas = useMemo(() => {
|
||||
if (!info || !runStatus) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: '启动时间',
|
||||
value: formatDate(info.create_time),
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
label: '执行时长',
|
||||
value: <RunDuration createTime={info.create_time} finishTime={runStatus.finishedAt} />,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
value: (
|
||||
<Flex align="center">
|
||||
<img
|
||||
style={{ width: '17px', marginRight: '7px' }}
|
||||
src={experimentStatusInfo[runStatus.phase]?.icon}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
color: experimentStatusInfo[runStatus?.phase]?.color,
|
||||
fontSize: '15px',
|
||||
lineHeight: 1.6,
|
||||
}}
|
||||
>
|
||||
{experimentStatusInfo[runStatus?.phase]?.label}
|
||||
</div>
|
||||
</Flex>
|
||||
),
|
||||
ellipsis: true,
|
||||
},
|
||||
];
|
||||
}, [runStatus, info]);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles['hyper-parameter-basic'], className)}>
|
||||
{isInstance && runStatus && (
|
||||
<ExperimentRunBasic
|
||||
create_time={info?.create_time}
|
||||
runStatus={runStatus}
|
||||
instanceStatus={instanceStatus}
|
||||
/>
|
||||
{isInstance && workflowStatus && (
|
||||
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
|
||||
)}
|
||||
{!isInstance && (
|
||||
<ConfigInfo
|
||||
|
|
|
|||
|
|
@ -9,6 +9,14 @@
|
|||
background-color: white;
|
||||
border-radius: 10px;
|
||||
|
||||
&__name-row {
|
||||
:global {
|
||||
.ant-form-item-row {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__type {
|
||||
color: @text-color;
|
||||
font-size: @font-size-input-lg;
|
||||
|
|
|
|||
|
|
@ -123,8 +123,6 @@ function MirrorCreate() {
|
|||
return true;
|
||||
};
|
||||
|
||||
const descTitle = isAddVersion ? '版本描述' : '镜像描述';
|
||||
|
||||
return (
|
||||
<div className={styles['mirror-create']}>
|
||||
<PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle>
|
||||
|
|
@ -161,6 +159,7 @@ function MirrorCreate() {
|
|||
message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)',
|
||||
},
|
||||
]}
|
||||
className={styles['mirror-create__content__name-row']}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入镜像名称"
|
||||
|
|
@ -193,21 +192,45 @@ function MirrorCreate() {
|
|||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
{!isAddVersion && (
|
||||
<Row gutter={10}>
|
||||
<Col span={20}>
|
||||
<Form.Item
|
||||
label="镜像描述"
|
||||
name="description"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入镜像描述',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.TextArea
|
||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
||||
placeholder="请输入镜像描述"
|
||||
maxLength={128}
|
||||
showCount
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<Row gutter={10}>
|
||||
<Col span={20}>
|
||||
<Form.Item
|
||||
label={descTitle}
|
||||
name="description"
|
||||
label="版本描述"
|
||||
name="version_description"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: `请输入${descTitle}`,
|
||||
message: '请输入版本描述',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.TextArea
|
||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
||||
placeholder={`请输入${descTitle}`}
|
||||
placeholder="请输入版本描述"
|
||||
maxLength={128}
|
||||
showCount
|
||||
allowClear
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ function MirrorInfo() {
|
|||
current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
|
||||
};
|
||||
});
|
||||
getMirrorInfo();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -164,20 +165,21 @@ function MirrorInfo() {
|
|||
title: '镜像版本',
|
||||
dataIndex: 'tag_name',
|
||||
key: 'tag_name',
|
||||
width: '25%',
|
||||
width: '30%',
|
||||
render: tableCellRender(),
|
||||
},
|
||||
{
|
||||
title: '镜像地址',
|
||||
dataIndex: 'url',
|
||||
key: 'url',
|
||||
width: '25%',
|
||||
width: '40%',
|
||||
render: tableCellRender('auto', TableCellValueType.Text, { copyable: true }),
|
||||
},
|
||||
{
|
||||
title: '版本描述',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
width: '30%',
|
||||
render: tableCellRender(true),
|
||||
},
|
||||
{
|
||||
|
|
@ -209,7 +211,7 @@ function MirrorInfo() {
|
|||
hidden: isPublic,
|
||||
render: (_: any, record: MirrorVersionData) => (
|
||||
<div>
|
||||
{!isPublic && (
|
||||
{!isPublic && record.status && record.status !== MirrorVersionStatus.Building && (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
|
|
|
|||
|
|
@ -65,14 +65,18 @@ export type MetricsChartProps = {
|
|||
function MetricsChart({ name, chartData }: MetricsChartProps) {
|
||||
const chartRef = useRef<HTMLDivElement>(null);
|
||||
const xAxisData = chartData[0]?.iters;
|
||||
const seriesData = chartData.map((item) => {
|
||||
return {
|
||||
name: item.version,
|
||||
type: 'line' as const,
|
||||
smooth: true,
|
||||
data: item.values,
|
||||
};
|
||||
});
|
||||
const seriesData = useMemo(
|
||||
() =>
|
||||
chartData.map((item) => {
|
||||
return {
|
||||
name: item.version,
|
||||
type: 'line' as const,
|
||||
smooth: true,
|
||||
data: item.values,
|
||||
};
|
||||
}),
|
||||
[chartData],
|
||||
);
|
||||
|
||||
const options: echarts.EChartsOption = useMemo(
|
||||
() => ({
|
||||
|
|
@ -158,7 +162,7 @@ function MetricsChart({ name, chartData }: MetricsChartProps) {
|
|||
|
||||
// 组件卸载
|
||||
return () => {
|
||||
// myChart.dispose() 销毁实例
|
||||
// 销毁实例
|
||||
chart.dispose();
|
||||
};
|
||||
}, [options]);
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ function CreateServiceVersion() {
|
|||
},
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9._-]+$/,
|
||||
message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
|
||||
message: '服务支持字母、数字、点(.)、下划线(_)、中横线(-)',
|
||||
},
|
||||
]}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@
|
|||
* @Date: 2024-04-16 13:58:08
|
||||
* @Description: 服务版本详情
|
||||
*/
|
||||
import FullScreenFrame from '@/components/FullScreenFrame';
|
||||
import IframePage from '@/components/IFramePage';
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import PageTitle from '@/components/PageTitle';
|
||||
import { ServiceRunStatus } from '@/enums';
|
||||
import { getServiceVersionInfoReq } from '@/services/modelDeployment';
|
||||
import { to } from '@/utils/promise';
|
||||
import { useParams } from '@umijs/max';
|
||||
import { Tabs } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import ServerLog from '../components/ServerLog';
|
||||
import UserGuide from '../components/UserGuide';
|
||||
import VersionBasicInfo from '../components/VersionBasicInfo';
|
||||
import { ServiceVersionData } from '../types';
|
||||
import styles from './index.less';
|
||||
|
|
@ -50,32 +50,35 @@ function ServiceVersionInfo() {
|
|||
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,
|
||||
label: '调用指南',
|
||||
icon: <KFIcon type="icon-tiaoyongzhinan" />,
|
||||
children: <UserGuide info={versionInfo}></UserGuide>,
|
||||
},
|
||||
{
|
||||
key: ModelDeploymentTabKey.Log,
|
||||
label: '服务日志',
|
||||
icon: <KFIcon type="icon-fuwurizhi" />,
|
||||
children: <ServerLog info={versionInfo}></ServerLog>,
|
||||
},
|
||||
];
|
||||
|
||||
if (versionInfo?.run_state === ServiceRunStatus.Running) {
|
||||
if (versionInfo?.page_path) {
|
||||
tabItems.push({
|
||||
key: ModelDeploymentTabKey.Predict,
|
||||
label: '预测',
|
||||
icon: <KFIcon type="icon-yuce" />,
|
||||
children: <IframePage url={versionInfo?.page_path} showLoading={false}></IframePage>,
|
||||
});
|
||||
}
|
||||
|
||||
if (versionInfo?.doc_path) {
|
||||
tabItems.push({
|
||||
key: ModelDeploymentTabKey.Guide,
|
||||
label: '调用指南',
|
||||
icon: <KFIcon type="icon-tiaoyongzhinan" />,
|
||||
children: <IframePage url={versionInfo?.doc_path}></IframePage>,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tabItems.push({
|
||||
key: ModelDeploymentTabKey.Log,
|
||||
label: '服务日志',
|
||||
icon: <KFIcon type="icon-fuwurizhi" />,
|
||||
children: <ServerLog info={versionInfo}></ServerLog>,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles['service-version-info']}>
|
||||
<PageTitle title="服务版本详情"></PageTitle>
|
||||
|
|
|
|||
|
|
@ -11,10 +11,18 @@
|
|||
font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
align-items: left;
|
||||
|
||||
&--empty {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__more {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { getServiceVersionLogReq } from '@/services/modelDeployment';
|
|||
import { to } from '@/utils/promise';
|
||||
import { DoubleRightOutlined } from '@ant-design/icons';
|
||||
import { Button, DatePicker, type TimeRangePickerProps } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from './index.less';
|
||||
|
|
@ -106,6 +107,8 @@ function ServerLog({ info }: ServerLogProps) {
|
|||
}
|
||||
};
|
||||
|
||||
const logContent = logData.map((v) => v.log_content).join('');
|
||||
|
||||
return (
|
||||
<div className={styles['server-log']}>
|
||||
<div>
|
||||
|
|
@ -122,9 +125,9 @@ function ServerLog({ info }: ServerLogProps) {
|
|||
查询
|
||||
</Button>
|
||||
</div>
|
||||
{logData.length > 0 && (
|
||||
{logContent ? (
|
||||
<div className={styles['server-log__data']} id="server-log">
|
||||
<div>{logData.map((v) => v.log_content).join('') || '暂无日志'}</div>
|
||||
<div>{logContent}</div>
|
||||
{hasMore && (
|
||||
<Button
|
||||
type="text"
|
||||
|
|
@ -137,6 +140,10 @@ function ServerLog({ info }: ServerLogProps) {
|
|||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className={classNames(styles['server-log__data'], styles['server-log__data--empty'])}>
|
||||
暂无日志
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ const EditPipeline = () => {
|
|||
// console.log(data);
|
||||
const errorNode = data.nodes.find((item) => item.formError === true);
|
||||
if (errorNode) {
|
||||
message.error(`【${errorNode.label}】节点必填项必须配置`);
|
||||
message.error(`【${errorNode.label}】节点配置验证失败`);
|
||||
const graphNode = graph.findById(errorNode.id);
|
||||
if (graphNode) {
|
||||
openNodeDrawer(graphNode, true);
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ const GlobalParamsDrawer = forwardRef(
|
|||
>
|
||||
{getParamComponent(type)}
|
||||
</Form.Item>
|
||||
{type !== 3 && (
|
||||
{/* {type !== 3 && (
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'is_sensitive']}
|
||||
|
|
@ -161,7 +161,7 @@ const GlobalParamsDrawer = forwardRef(
|
|||
<Radio value={0}>否</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
)}
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { openAntdModal } from '@/utils/modal';
|
|||
import { to } from '@/utils/promise';
|
||||
import { INode } from '@antv/g6';
|
||||
import { Button, Drawer, Form, Input, MenuProps } from 'antd';
|
||||
import { RuleObject } from 'antd/es/form';
|
||||
import { NamePath } from 'antd/es/form/interface';
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import PropsLabel from '../PropsLabel';
|
||||
|
|
@ -34,6 +35,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
|
|||
const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>(
|
||||
{} as PipelineNodeModelSerialize,
|
||||
);
|
||||
const nodeId = Form.useWatch('id', form) as string;
|
||||
const hasTaskInfo =
|
||||
nodeId &&
|
||||
!nodeId.startsWith('git-clone') &&
|
||||
!nodeId.startsWith('dataset-export') &&
|
||||
!nodeId.startsWith('model-export');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [menuItems, setMenuItems] = useState<MenuProps['items']>([]);
|
||||
|
||||
|
|
@ -45,10 +52,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
|
|||
const fields = form.getFieldsValue();
|
||||
|
||||
// 保存字段顺序
|
||||
const control_strategy = {
|
||||
...stagingItem.control_strategy,
|
||||
...fields.control_strategy,
|
||||
};
|
||||
// const control_strategy = {
|
||||
// ...stagingItem.control_strategy,
|
||||
// ...fields.control_strategy,
|
||||
// };
|
||||
|
||||
const in_parameters = {
|
||||
...stagingItem.in_parameters,
|
||||
...fields.in_parameters,
|
||||
|
|
@ -63,7 +71,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
|
|||
const res = {
|
||||
...stagingItem,
|
||||
...fields,
|
||||
control_strategy: JSON.stringify(control_strategy),
|
||||
// control_strategy: JSON.stringify(control_strategy),
|
||||
in_parameters: JSON.stringify(in_parameters),
|
||||
out_parameters: JSON.stringify(out_parameters),
|
||||
formError: !!error,
|
||||
|
|
@ -90,7 +98,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
|
|||
...model,
|
||||
in_parameters: JSON.parse(model.in_parameters),
|
||||
out_parameters: JSON.parse(model.out_parameters),
|
||||
control_strategy: JSON.parse(model.control_strategy),
|
||||
// control_strategy: JSON.parse(model.control_strategy),
|
||||
};
|
||||
// console.log('model', nodeData);
|
||||
setStagingItem({
|
||||
|
|
@ -309,22 +317,47 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
|
|||
);
|
||||
};
|
||||
|
||||
// 模型部署-服务版本验证
|
||||
const serviceVersionValidator = (_rule: RuleObject, value: any) => {
|
||||
// 输入参数值都是对象,如果是手动输入,要求 /^[a-zA-Z0-9._-]+$/
|
||||
if (
|
||||
typeof value === 'object' &&
|
||||
value &&
|
||||
!value.fromSelect &&
|
||||
value.value &&
|
||||
!/^[a-zA-Z0-9._-]+$/.test(value.value)
|
||||
) {
|
||||
return Promise.reject('服务版本只支持字母、数字、点(.)、下划线(_)、中横线(-)');
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
// 必填项校验规则
|
||||
const getFormRules = (item: { key: string; value: PipelineNodeModelParameter }) => {
|
||||
return item.value.require
|
||||
const id = form.getFieldValue('id') as string;
|
||||
const rules = item.value.require
|
||||
? [
|
||||
{
|
||||
validator: requiredValidator,
|
||||
message: '必填项',
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
// 模型部署-服务版本验证
|
||||
if (id.startsWith('model-deploy') && item.key === '--version') {
|
||||
rules.push({
|
||||
validator: serviceVersionValidator,
|
||||
});
|
||||
}
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
// 控制策略
|
||||
const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map(
|
||||
([key, value]) => ({ key, value }),
|
||||
);
|
||||
// const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map(
|
||||
// ([key, value]) => ({ key, value }),
|
||||
// );
|
||||
|
||||
// 输入参数
|
||||
const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({
|
||||
|
|
@ -396,71 +429,73 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
|
|||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<div className={styles['pipeline-drawer__title']}>
|
||||
<SubAreaTitle
|
||||
image={require('@/assets/img/duty-message.png')}
|
||||
title="任务信息"
|
||||
></SubAreaTitle>
|
||||
</div>
|
||||
<Form.Item label="镜像" required>
|
||||
<div className={styles['pipeline-drawer__ref-row']}>
|
||||
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
|
||||
<Input placeholder="请输入或选择镜像" allowClear />
|
||||
{hasTaskInfo && (
|
||||
<>
|
||||
<div className={styles['pipeline-drawer__title']}>
|
||||
<SubAreaTitle
|
||||
image={require('@/assets/img/duty-message.png')}
|
||||
title="任务信息"
|
||||
></SubAreaTitle>
|
||||
</div>
|
||||
<Form.Item label="镜像" required>
|
||||
<div className={styles['pipeline-drawer__ref-row']}>
|
||||
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
|
||||
<Input placeholder="请输入或选择镜像" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item noStyle>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={getSelectBtnIcon({ item_type: 'image' })}
|
||||
onClick={() => selectResource('image', { item_type: 'image' })}
|
||||
className={styles['pipeline-drawer__ref-row__select-button']}
|
||||
>
|
||||
选择镜像
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item noStyle>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={getSelectBtnIcon({ item_type: 'image' })}
|
||||
onClick={() => selectResource('image', { item_type: 'image' })}
|
||||
className={styles['pipeline-drawer__ref-row__select-button']}
|
||||
>
|
||||
选择镜像
|
||||
</Button>
|
||||
<Form.Item
|
||||
name="working_directory"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="工作目录"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('working_directory', value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Input placeholder="请输入工作目录" allowClear />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="working_directory"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="工作目录"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('working_directory', value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Input placeholder="请输入工作目录" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="command"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="启动命令"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('command', value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TextArea placeholder="请输入启动命令" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="资源规格"
|
||||
name="resources_standard"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择资源规格',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ParameterSelect dataType="resource" placeholder="请选择资源规格" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
<Form.Item
|
||||
name="command"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="启动命令"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('command', value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TextArea placeholder="请输入启动命令" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="资源规格"
|
||||
name="resources_standard"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择资源规格',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ParameterSelect dataType="resource" placeholder="请选择资源规格" />
|
||||
</Form.Item>
|
||||
{/* <Form.Item
|
||||
name="mount_path"
|
||||
label={
|
||||
<PropsLabel
|
||||
|
|
@ -473,23 +508,23 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
|
|||
}
|
||||
>
|
||||
<Input placeholder="请输入挂载路径" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="env_variables"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="环境变量"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('env_variables', value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TextArea placeholder="请输入环境变量" allowClear />
|
||||
</Form.Item>
|
||||
{/* 控制参数 */}
|
||||
{controlStrategyList.map((item) => (
|
||||
</Form.Item> */}
|
||||
<Form.Item
|
||||
name="env_variables"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="环境变量"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('env_variables', value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TextArea placeholder="请输入环境变量" allowClear />
|
||||
</Form.Item>
|
||||
{/* 控制参数 */}
|
||||
{/* {controlStrategyList.map((item) => (
|
||||
<Form.Item
|
||||
key={item.key}
|
||||
name={['control_strategy', item.key]}
|
||||
|
|
@ -499,7 +534,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
|
|||
>
|
||||
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
|
||||
</Form.Item>
|
||||
))}
|
||||
))} */}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 输入参数 */}
|
||||
{inParametersList.length > 0 && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ const Pipeline = () => {
|
|||
}),
|
||||
},
|
||||
{
|
||||
title: '流水线名称',
|
||||
title: '流水线模板名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '50%',
|
||||
|
|
@ -199,7 +199,7 @@ const Pipeline = () => {
|
|||
}),
|
||||
},
|
||||
{
|
||||
title: '流水线描述',
|
||||
title: '流水线模板描述',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
width: '50%',
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ const AuthUserTableList: React.FC = () => {
|
|||
dataIndex: 'createTime',
|
||||
valueType: 'dateRange',
|
||||
render: (_, record) => {
|
||||
return <span>{record.createTime.toString()} </span>;
|
||||
return <span>{record.createTime?.toString()} </span>;
|
||||
},
|
||||
hideInSearch: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => {
|
|||
valueType: 'dateRange',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => {
|
||||
return <span>{record.createTime.toString()} </span>;
|
||||
return <span>{record.createTime?.toString()} </span>;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ const RoleTableList: React.FC = () => {
|
|||
confirm({
|
||||
title: `确认要${text}${record.roleName}角色吗?`,
|
||||
onOk() {
|
||||
changeRoleStatus(record.roleId, newStatus).then((resp) => {
|
||||
changeRoleStatus(record.roleId, record.roleKey, newStatus).then((resp) => {
|
||||
if (resp.code === 200) {
|
||||
messageApi.open({
|
||||
type: 'success',
|
||||
|
|
@ -240,7 +240,7 @@ const RoleTableList: React.FC = () => {
|
|||
dataIndex: 'createTime',
|
||||
valueType: 'dateRange',
|
||||
render: (_, record) => {
|
||||
return <span>{record.createTime.toString()} </span>;
|
||||
return <span>{record.createTime?.toString()} </span>;
|
||||
},
|
||||
search: {
|
||||
transform: (value) => {
|
||||
|
|
|
|||
|
|
@ -28,8 +28,11 @@ const DeptTree: React.FC<TreeProps> = (props) => {
|
|||
const res = await getDeptTree({});
|
||||
const treeData = res.map((item: any) => ({ ...item, key: item.id }));
|
||||
setTreeData(treeData);
|
||||
setExpandedKeys([treeData[0].key]);
|
||||
setSelectedKeys([treeData[0].key]);
|
||||
if (treeData.length > 0) {
|
||||
onSelect(treeData[0]);
|
||||
setExpandedKeys([treeData[0].key]);
|
||||
setSelectedKeys([treeData[0].key]);
|
||||
}
|
||||
hide();
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
|
@ -41,12 +44,6 @@ const DeptTree: React.FC<TreeProps> = (props) => {
|
|||
fetchDeptList();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (treeData.length > 0) {
|
||||
onSelect(treeData[0]);
|
||||
}
|
||||
}, [treeData, onSelect]);
|
||||
|
||||
const handleSelect = (keys: React.Key[], info: any) => {
|
||||
setSelectedKeys(keys);
|
||||
onSelect(info.node);
|
||||
|
|
|
|||
|
|
@ -203,9 +203,8 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
|||
required: true,
|
||||
},
|
||||
{
|
||||
pattern: /^[a-zA-Z](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/,
|
||||
message:
|
||||
'只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以字母开头,数字或字母结尾',
|
||||
pattern: /^[a-zA-Z][a-zA-Z0-9]{3,14}$/,
|
||||
message: '用户账号长度为4 ~ 15位,且必须以字母开头,只支持字母和数字',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -37,7 +37,18 @@
|
|||
&__summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 33.33%;
|
||||
width: 40%;
|
||||
text-align: left;
|
||||
|
||||
&:nth-child(3n+2) {
|
||||
text-align: center;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(3n) {
|
||||
text-align: right;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin-bottom: 12px;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ function AssetsManagement() {
|
|||
};
|
||||
const [res] = await to(getWorkspaceAssetCountReq(params));
|
||||
if (res && res.data) {
|
||||
const { component, dataset, image, model, workflow } = res.data;
|
||||
const { dataset, image, model, workflow, codeConfig, service } = res.data;
|
||||
const items = [
|
||||
{
|
||||
title: '数据集',
|
||||
|
|
@ -30,10 +30,10 @@ function AssetsManagement() {
|
|||
title: '镜像',
|
||||
value: image,
|
||||
},
|
||||
{
|
||||
title: '组件',
|
||||
value: component,
|
||||
},
|
||||
// {
|
||||
// title: '组件',
|
||||
// value: component,
|
||||
// },
|
||||
// {
|
||||
// title: '代码配置',
|
||||
// value: 0,
|
||||
|
|
@ -42,6 +42,14 @@ function AssetsManagement() {
|
|||
title: '流水线模版',
|
||||
value: workflow,
|
||||
},
|
||||
{
|
||||
title: '代码配置',
|
||||
value: codeConfig,
|
||||
},
|
||||
{
|
||||
title: '服务',
|
||||
value: service,
|
||||
},
|
||||
];
|
||||
setAssetCounts(items);
|
||||
}
|
||||
|
|
@ -53,7 +61,7 @@ function AssetsManagement() {
|
|||
return (
|
||||
<div className={styles['assets-management']}>
|
||||
<Flex justify="space-between">
|
||||
<div className={styles['assets-management__title']}>AI资产</div>
|
||||
<div className={styles['assets-management__title']}>多形态资源库</div>
|
||||
<Select
|
||||
size="small"
|
||||
value={type}
|
||||
|
|
@ -67,11 +75,11 @@ function AssetsManagement() {
|
|||
/>
|
||||
</Flex>
|
||||
{/* <div className={styles['assets-management__increase']}>今日新增数量:5</div> */}
|
||||
<Flex gap="22px 0" wrap="wrap" style={{ marginTop: '40px' }}>
|
||||
<Flex gap="22px 0" wrap="wrap" style={{ marginTop: '40px', padding: '0 8px' }}>
|
||||
{assetCounts.map((item, index) => (
|
||||
<div className={styles['assets-management__summary']} key={index}>
|
||||
<div className={styles['assets-management__summary__title']}>{item.title}</div>
|
||||
<div className={styles['assets-management__summary__value']}>{item.value}</div>
|
||||
<div className={styles['assets-management__summary__value']}>{item.value ?? '-'}</div>
|
||||
</div>
|
||||
))}
|
||||
</Flex>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.experiment-table {
|
||||
flex: 1;
|
||||
min-width: 378px;
|
||||
min-width: 460px;
|
||||
height: 140px;
|
||||
padding: 12px;
|
||||
background: @workspace-background;
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
}
|
||||
|
||||
&__status {
|
||||
width: 15%;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
&__duration {
|
||||
|
|
@ -40,11 +40,11 @@
|
|||
}
|
||||
|
||||
&__date {
|
||||
width: calc(60% - 60px);
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
&__operation {
|
||||
width: 60px;
|
||||
width: 10%;
|
||||
|
||||
:global {
|
||||
.ant-btn-link {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import KFIcon from '@/components/KFIcon';
|
||||
import { ExperimentStatus, experimentStatusInfo } from '@/pages/Experiment/status';
|
||||
import { ExperimentInstance } from '@/types';
|
||||
import { elapsedTime, formatDate } from '@/utils/date';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import { Button, Empty } from 'antd';
|
||||
import { Empty } from 'antd';
|
||||
import styles from './index.less';
|
||||
import ExperimentInstanceComponent from './instance';
|
||||
|
||||
type ExperimentTableProps = {
|
||||
tableData: ExperimentInstance[];
|
||||
style?: React.CSSProperties;
|
||||
|
|
@ -26,31 +25,11 @@ function ExperimentTable({ tableData = [], style }: ExperimentTableProps) {
|
|||
</div>
|
||||
{Array.isArray(tableData) && tableData.length > 0 ? (
|
||||
tableData.map((item) => (
|
||||
<div className={styles['experiment-table__content']} key={item.id}>
|
||||
<div className={styles['experiment-table__status']} style={{ paddingLeft: '6.5px' }}>
|
||||
<img
|
||||
src={experimentStatusInfo[item.status as ExperimentStatus]?.icon}
|
||||
width={17}
|
||||
height={17}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div className={styles['experiment-table__duration']}>
|
||||
{elapsedTime(item.create_time, item.finish_time)}
|
||||
</div>
|
||||
<div className={styles['experiment-table__date']}>{formatDate(item.create_time)}</div>
|
||||
<div className={styles['experiment-table__operation']}>
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
icon={<KFIcon type="icon-xiangqing2" font={14} />}
|
||||
onClick={() => gotoExperiment(item)}
|
||||
>
|
||||
详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<ExperimentInstanceComponent
|
||||
instance={item}
|
||||
key={item.id}
|
||||
onClick={() => gotoExperiment(item)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Empty
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
import KFIcon from '@/components/KFIcon';
|
||||
import RunDuration from '@/components/RunDuration';
|
||||
import { ExperimentStatus } from '@/enums';
|
||||
import { useSSE, type MessageHandler } from '@/hooks/useSSE';
|
||||
import { experimentStatusInfo } from '@/pages/Experiment/status';
|
||||
import { ExperimentInstance, NodeStatus } from '@/types';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { getExperimentInstanceStatus, getWorkflowStatus } from '@/utils/experiment';
|
||||
import { Button } from 'antd';
|
||||
import { useCallback, useState } from 'react';
|
||||
import styles from './index.less';
|
||||
|
||||
const NodePrefix = 'workflow';
|
||||
|
||||
type ExperimentInstanceComponentProps = {
|
||||
instance: ExperimentInstance;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
function ExperimentInstanceComponent({ instance, onClick }: ExperimentInstanceComponentProps) {
|
||||
const { experiment_id, id, argo_ins_name, argo_ins_ns, nodes_status } = instance;
|
||||
const initialWorkflowStatus = getWorkflowStatus(nodes_status) as NodeStatus | undefined;
|
||||
const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(
|
||||
initialWorkflowStatus,
|
||||
);
|
||||
const status = getExperimentInstanceStatus(instance.status as ExperimentStatus, workflowStatus);
|
||||
const createTime = workflowStatus?.startedAt;
|
||||
const finishTime = workflowStatus?.finishedAt;
|
||||
const statusInfo = experimentStatusInfo[status];
|
||||
|
||||
const handleSSEMessage: MessageHandler = useCallback(
|
||||
(
|
||||
_experimentId: number,
|
||||
_experimentInsId: number,
|
||||
_status: string,
|
||||
_finishTime: string,
|
||||
nodes,
|
||||
) => {
|
||||
if (nodes) {
|
||||
// 设置总 workflow 状态
|
||||
const workflowStatus = Object.values(nodes).find((node: any) =>
|
||||
node.displayName.startsWith(NodePrefix),
|
||||
) as NodeStatus;
|
||||
if (workflowStatus) {
|
||||
setWorkflowStatus(workflowStatus);
|
||||
}
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
useSSE(experiment_id, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
|
||||
|
||||
return (
|
||||
<div className={styles['experiment-table__content']} key={id}>
|
||||
<div className={styles['experiment-table__status']} style={{ paddingLeft: '6.5px' }}>
|
||||
<img src={statusInfo?.icon} width={17} height={17} draggable={false} alt="" />
|
||||
</div>
|
||||
<div className={styles['experiment-table__duration']}>
|
||||
<RunDuration createTime={createTime} finishTime={finishTime} />
|
||||
</div>
|
||||
<div className={styles['experiment-table__date']}>{formatDate(createTime)}</div>
|
||||
<div className={styles['experiment-table__operation']}>
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
icon={<KFIcon type="icon-xiangqing2" font={14} />}
|
||||
onClick={onClick}
|
||||
>
|
||||
详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExperimentInstanceComponent;
|
||||
|
|
@ -16,13 +16,13 @@ function QuickStart() {
|
|||
const changeScale = () => {
|
||||
// body 的宽度 - 菜单的宽度 - 两个 padding - 右边用户管理的宽度 - 右边用户管理的 marginLeft - 滚动条的宽度,
|
||||
const width = document.body.offsetWidth - 256 - 80 - 60 - 326 - 15 - 8;
|
||||
if (width >= 1223) {
|
||||
const spaceX = (width - 192 * 5 - 60) / 7;
|
||||
if (width >= 1002) {
|
||||
const spaceX = (width - 192 * 4 - 60) / 6;
|
||||
setSpace(spaceX);
|
||||
setCanvasWidth('100%');
|
||||
setScale(1.0);
|
||||
} else {
|
||||
const ratio = width / 1223;
|
||||
const ratio = width / 1002;
|
||||
setCanvasWidth('1223px');
|
||||
setSpace(29);
|
||||
setScale(ratio);
|
||||
|
|
@ -54,56 +54,56 @@ function QuickStart() {
|
|||
>
|
||||
<WorkFlow
|
||||
content="为开发者提供数据智能标注与数据回流服务"
|
||||
buttonText="数据准备"
|
||||
buttonTop={40}
|
||||
buttonText="数据标注"
|
||||
buttonTop={20}
|
||||
x={left}
|
||||
y={309}
|
||||
onClick={() => navigate('/datasetPreparation/datasetAnnotation')}
|
||||
/>
|
||||
<WorkFlow
|
||||
{/* <WorkFlow
|
||||
content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用"
|
||||
buttonText="开发环境"
|
||||
buttonTop={20}
|
||||
x={left + 192 + space}
|
||||
y={301}
|
||||
onClick={() => navigate('/developmentEnvironment')}
|
||||
/>
|
||||
/> */}
|
||||
<WorkFlow
|
||||
content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用"
|
||||
tips="可视化建模Designer"
|
||||
buttonText="流水线"
|
||||
buttonText="流水线模板"
|
||||
buttonTop={20}
|
||||
x={left + 2 * (192 + space)}
|
||||
x={left + 1 * (192 + space)}
|
||||
y={276}
|
||||
onClick={() => navigate('/pipeline/template')}
|
||||
/>
|
||||
<WorkFlow
|
||||
content="开发者可以在这里运行流水线模板,产生实验实例,对比实验训练过程与产生的实验训练数据"
|
||||
buttonText="实验"
|
||||
buttonTop={40}
|
||||
x={left + 3 * (192 + space)}
|
||||
buttonTop={20}
|
||||
x={left + 2 * (192 + space)}
|
||||
y={295}
|
||||
onClick={() => navigate('/pipeline/experiment')}
|
||||
/>
|
||||
<WorkFlow
|
||||
content="支持异构硬件(CPU/GPU)的模型加载,高吞吐,低延迟;支持大规模复杂模型的一键部署,实时弹性扩缩容;提供完整的运维监控体系。"
|
||||
tips="模型在线服务"
|
||||
buttonText="模型在线部署"
|
||||
buttonText="服务"
|
||||
buttonTop={20}
|
||||
x={left + 4 * (192 + space) + 60 + space}
|
||||
x={left + 3 * (192 + space) + 60 + space}
|
||||
y={263}
|
||||
onClick={() => navigate('/dataset/modelDeployment')}
|
||||
/>
|
||||
<div
|
||||
className={styles['quick-start__content__canvas__model']}
|
||||
style={{ top: '358px', left: left + 4 * (192 + space) + 'px' }}
|
||||
style={{ top: '358px', left: left + 3 * (192 + space) + 'px' }}
|
||||
>
|
||||
<KFIcon type="icon-moxingguanli" font={38} />
|
||||
<span>模型管理</span>
|
||||
</div>
|
||||
<div
|
||||
className={styles['quick-start__content__canvas__task']}
|
||||
style={{ top: '110px', left: left + 2 * (192 + space) + 56 + taskLeftArrowWidth + 16 }}
|
||||
style={{ top: '110px', left: left + 1 * (192 + space) + 56 + taskLeftArrowWidth + 16 }}
|
||||
>
|
||||
<KFIcon type="icon-tiaoduguanli" font={13} style={{ marginRight: '5px' }} />
|
||||
<span>任务自动调度</span>
|
||||
|
|
@ -117,7 +117,7 @@ function QuickStart() {
|
|||
arrorwTop={-4}
|
||||
borderBottom={1}
|
||||
/>
|
||||
<WorkArrow
|
||||
{/* <WorkArrow
|
||||
x={left + 2 * 192 + space + 1}
|
||||
y={378}
|
||||
width={arrowWidth}
|
||||
|
|
@ -125,9 +125,9 @@ function QuickStart() {
|
|||
arrowLeft={arrowWidth}
|
||||
arrorwTop={-4}
|
||||
borderBottom={1}
|
||||
/>
|
||||
/> */}
|
||||
<WorkArrow
|
||||
x={left + 4 * 192 + 3 * space + 1}
|
||||
x={left + 3 * 192 + 2 * space + 1}
|
||||
y={378}
|
||||
width={arrowWidth + 10}
|
||||
height={1}
|
||||
|
|
@ -136,7 +136,7 @@ function QuickStart() {
|
|||
borderBottom={1}
|
||||
/>
|
||||
<WorkArrow
|
||||
x={left + 4 * 192 + 60 + 4 * space + 1 - 10}
|
||||
x={left + 3 * 192 + 60 + 3 * space + 1 - 10}
|
||||
y={378}
|
||||
width={arrowWidth + 10}
|
||||
height={1}
|
||||
|
|
@ -145,7 +145,7 @@ function QuickStart() {
|
|||
borderBottom={1}
|
||||
/>
|
||||
<WorkArrow
|
||||
x={left + 2 * (192 + space) + 56}
|
||||
x={left + 1 * (192 + space) + 56}
|
||||
y={139}
|
||||
width={taskLeftArrowWidth}
|
||||
height={120}
|
||||
|
|
@ -155,7 +155,7 @@ function QuickStart() {
|
|||
borderTop={1}
|
||||
/>
|
||||
<WorkArrow
|
||||
x={left + 2 * (192 + space) + 56 + taskLeftArrowWidth + 16 + 131 + 4}
|
||||
x={left + 1 * (192 + space) + 56 + taskLeftArrowWidth + 16 + 131 + 4}
|
||||
y={127}
|
||||
width={taskRightArrowWidth}
|
||||
height={156}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 140px;
|
||||
padding: 0 16px;
|
||||
padding: 0 35px;
|
||||
|
||||
// 媒体查询
|
||||
@media screen and (max-width: 1600px) {
|
||||
flex: auto;
|
||||
}
|
||||
// // 媒体查询
|
||||
// @media screen and (max-width: 1600px) {
|
||||
// flex: auto;
|
||||
// }
|
||||
|
||||
&__icon {
|
||||
width: 63px;
|
||||
margin-right: 16px;
|
||||
width: 80px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
height: 228px;
|
||||
padding: 0 20px;
|
||||
.backgroundFullImage(url(@/assets/img/user-points-bg.png));
|
||||
margin-bottom: 16px;
|
||||
|
||||
&__label {
|
||||
margin-top: 60px;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 0 30px;
|
||||
padding: 20px 30px;
|
||||
background-image: url(@/assets/img/workspace-intro.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top right;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { Button } from 'antd';
|
||||
import styles from './index.less';
|
||||
|
||||
function WorkspaceIntro() {
|
||||
return (
|
||||
<div className={styles['workspace-intro']}>
|
||||
<div className={styles['workspace-intro__left']}>
|
||||
<div className={styles['workspace-intro__title']}>自主实验平台</div>
|
||||
<div className={styles['workspace-intro__title']}>智能材料科研平台</div>
|
||||
<div className={styles['workspace-intro__content']}>
|
||||
材料领域的自主实验系统是一种用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能,
|
||||
智能材料科研平台是用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能,
|
||||
以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作
|
||||
</div>
|
||||
<div className={styles['workspace-intro__buttons']}>
|
||||
{/* <div className={styles['workspace-intro__buttons']}>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: '20px' }}
|
||||
|
|
@ -40,7 +39,7 @@ function WorkspaceIntro() {
|
|||
>
|
||||
分子材料自主实验系统
|
||||
</Button>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className={styles['workspace-intro__right']}>
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
&__statistics {
|
||||
flex: none;
|
||||
min-width: 431px;
|
||||
// min-width: 500px;
|
||||
background: linear-gradient(
|
||||
123.08deg,
|
||||
rgba(138, 138, 138, 0.06) 1.32%,
|
||||
|
|
@ -35,10 +35,10 @@
|
|||
);
|
||||
border-radius: 4px;
|
||||
|
||||
// 媒体查询
|
||||
@media screen and (max-width: 1600px) {
|
||||
flex: 1;
|
||||
}
|
||||
// // 媒体查询
|
||||
// @media screen and (max-width: 1600px) {
|
||||
// flex: 1;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
import { useDraggable } from '@/hooks/useDraggable';
|
||||
// import { useDraggable } from '@/hooks/useDraggable';
|
||||
import { getWorkspaceOverviewReq } from '@/services/workspace';
|
||||
import { ExperimentInstance } from '@/types';
|
||||
import { to } from '@/utils/promise';
|
||||
import { Divider, Flex } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Draggable from 'react-draggable';
|
||||
// import Draggable from 'react-draggable';
|
||||
import AssetsManagement from './components/AssetsManagement';
|
||||
import ExperimentChart, { type ExperimentStatistics } from './components/ExperimentChart';
|
||||
import ExperitableTable from './components/ExperimentTable';
|
||||
import QuickStart from './components/QuickStart';
|
||||
import RobotFrame from './components/RobotFrame';
|
||||
// import RobotFrame from './components/RobotFrame';
|
||||
import TotalStatistics from './components/TotalStatistics';
|
||||
import UserPoints from './components/UserPoints';
|
||||
import UserSpace from './components/UserSpace';
|
||||
import WorkspaceIntro from './components/WorkspaceIntro';
|
||||
import styles from './index.less';
|
||||
|
||||
|
|
@ -25,10 +24,10 @@ type OverviewData = {
|
|||
|
||||
function Workspace() {
|
||||
const [overviewData, setOverviewData] = useState<OverviewData>();
|
||||
const [robotFrameVisible, setRobotFrameVisible] = useState(false);
|
||||
const { handleStart, handleStop, handleDrag, handleClick } = useDraggable(() =>
|
||||
setRobotFrameVisible((prev) => !prev),
|
||||
);
|
||||
// const [robotFrameVisible, setRobotFrameVisible] = useState(false);
|
||||
// const { handleStart, handleStop, handleDrag, handleClick } = useDraggable(() =>
|
||||
// setRobotFrameVisible((prev) => !prev),
|
||||
// );
|
||||
const users: number[] = new Array(8).fill(0);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -67,7 +66,6 @@ function Workspace() {
|
|||
count={overviewData?.runningExperimentInsCount}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<ExperitableTable
|
||||
tableData={overviewData?.latestExperimentInsList || []}
|
||||
></ExperitableTable>
|
||||
|
|
@ -76,16 +74,16 @@ function Workspace() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<UserPoints />
|
||||
</Flex>
|
||||
<div className={styles['workspace__quick-start']}>
|
||||
<QuickStart></QuickStart>
|
||||
<div className={styles['workspace__user']}>
|
||||
<UserSpace users={users}></UserSpace>
|
||||
<UserPoints />
|
||||
{/* <UserSpace users={users}></UserSpace> */}
|
||||
<AssetsManagement></AssetsManagement>
|
||||
</div>
|
||||
</div>
|
||||
<Draggable onStart={handleStart} onStop={handleStop} onDrag={handleDrag} bounds="body">
|
||||
{/* <Draggable onStart={handleStart} onStop={handleStop} onDrag={handleDrag} bounds="body">
|
||||
<img
|
||||
className={styles['workspace__robot-img']}
|
||||
src={require('@/assets/img/robot.png')}
|
||||
|
|
@ -98,7 +96,7 @@ function Workspace() {
|
|||
<RobotFrame
|
||||
visible={robotFrameVisible}
|
||||
onClose={() => setRobotFrameVisible(false)}
|
||||
></RobotFrame>
|
||||
></RobotFrame> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@
|
|||
import { request } from '@umijs/max';
|
||||
|
||||
// 分页查询超参数自动寻优
|
||||
export function getActiveLearnListReq(params) {
|
||||
export function getActiveLearnListReq(params, skipLoading) {
|
||||
return request(`/api/mmp/activeLearn`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
skipLoading
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -54,10 +55,11 @@ export function runActiveLearnReq(id) {
|
|||
|
||||
// ----------------------- 实验实例 -----------------------
|
||||
// 获取实验实例列表
|
||||
export function getActiveLearnInsListReq(params) {
|
||||
export function getActiveLearnInsListReq(params, skipLoading) {
|
||||
return request(`/api/mmp/activeLearnIns`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
skipLoading
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -99,3 +101,12 @@ export function getExpMetricsReq(data) {
|
|||
});
|
||||
}
|
||||
|
||||
// 编辑实验实例
|
||||
export function editActiveLearnInsReq(data) {
|
||||
return request(`/api/mmp/activeLearnIns`, {
|
||||
method: 'PUT',
|
||||
data,
|
||||
skipLoading: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@
|
|||
import { request } from '@umijs/max';
|
||||
|
||||
// 分页查询自动学习
|
||||
export function getAutoMLListReq(params) {
|
||||
export function getAutoMLListReq(params, skipLoading) {
|
||||
return request(`/api/mmp/machineLearn`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
skipLoading,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -54,10 +55,11 @@ export function runAutoMLReq(id) {
|
|||
|
||||
// ----------------------- 实验实例 -----------------------
|
||||
// 获取实验实例列表
|
||||
export function getExperimentInsListReq(params) {
|
||||
export function getExperimentInsListReq(params, skipLoading) {
|
||||
return request(`/api/mmp/machineLearnIns`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
skipLoading,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -89,3 +91,12 @@ export function batchDeleteExperimentInsReq(data) {
|
|||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑实验实例
|
||||
export function editExperimentInsReq(data) {
|
||||
return request(`/api/mmp/machineLearnIns`, {
|
||||
method: 'PUT',
|
||||
data,
|
||||
skipLoading: true,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,14 @@ export function addDatasetVersion(data) {
|
|||
});
|
||||
}
|
||||
|
||||
// 编辑数据集版本
|
||||
export function editDatasetVersion(data) {
|
||||
return request(`/api/mmp/newdataset/updateVersionDesc`, {
|
||||
method: 'PUT',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 下载数据集所有文件
|
||||
export function downloadAllFiles(params) {
|
||||
return request(`/api/mmp/newdataset/downloadAllFiles`, {
|
||||
|
|
@ -132,6 +140,15 @@ export function addModelVersion(data) {
|
|||
});
|
||||
}
|
||||
|
||||
// 编辑模型版本
|
||||
export function editModelVersion(data) {
|
||||
return request(`/api/mmp/newmodel/updateVersionDesc`, {
|
||||
method: 'PUT',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 删除模型版本
|
||||
export function deleteModelVersion(params) {
|
||||
return request(`/api/mmp/newmodel/deleteVersion`, {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { request } from '@umijs/max';
|
||||
// 查询实验列表
|
||||
export function getExperiment(params) {
|
||||
export function getExperiment(params, skipLoading) {
|
||||
return request(`/api/mmp/experiment`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
skipLoading,
|
||||
});
|
||||
}
|
||||
// 运行实验
|
||||
|
|
@ -28,10 +29,11 @@ export function deleteExperimentById(id) {
|
|||
});
|
||||
}
|
||||
// 根据id查询实验实例
|
||||
export function getQueryByExperimentId(params) {
|
||||
export function getQueryByExperimentId(params, skipLoading) {
|
||||
return request(`/api/mmp/experimentIns`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
skipLoading
|
||||
});
|
||||
}
|
||||
// 根据id删除实验实例
|
||||
|
|
@ -53,6 +55,16 @@ export function putQueryByExperimentInsId(id) {
|
|||
method: 'PUT',
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑实验实例
|
||||
export function editExperimentInsReq(data) {
|
||||
return request(`/api/mmp/experimentIns`, {
|
||||
method: 'PUT',
|
||||
data,
|
||||
skipLoading: true,
|
||||
});
|
||||
}
|
||||
|
||||
// 查询实验实例实时日志
|
||||
export function getQueryByExperimentLog(data) {
|
||||
return request('/api/mmp/experimentIns/realTimeLog/', {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@
|
|||
import { request } from '@umijs/max';
|
||||
|
||||
// 分页查询超参数自动寻优
|
||||
export function getRayListReq(params) {
|
||||
export function getRayListReq(params, skipLoading) {
|
||||
return request(`/api/mmp/ray`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
skipLoading
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -54,10 +55,11 @@ export function runRayReq(id) {
|
|||
|
||||
// ----------------------- 实验实例 -----------------------
|
||||
// 获取实验实例列表
|
||||
export function getRayInsListReq(params) {
|
||||
export function getRayInsListReq(params, skipLoading) {
|
||||
return request(`/api/mmp/rayIns`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
skipLoading,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -97,3 +99,13 @@ export function getExpMetricsReq(data) {
|
|||
data,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 编辑实验实例
|
||||
export function editRayInsReq(data) {
|
||||
return request(`/api/mmp/rayIns`, {
|
||||
method: 'PUT',
|
||||
data,
|
||||
skipLoading: true,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,9 +70,10 @@ export function updateRoleDataScope(data: Record<string, any>) {
|
|||
}
|
||||
|
||||
// 角色状态修改
|
||||
export function changeRoleStatus(roleId: number, status: string) {
|
||||
export function changeRoleStatus(roleId: number, roleKey: string, status: string) {
|
||||
const data = {
|
||||
roleId,
|
||||
roleKey,
|
||||
status,
|
||||
};
|
||||
return request<API.Result>('/api/system/role/changeStatus', {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ export type ExperimentInstance = {
|
|||
nodes_result: {
|
||||
[key: string]: any;
|
||||
};
|
||||
node_status: string;
|
||||
nodes_status: string;
|
||||
global_param: PipelineGlobalParam[];
|
||||
tensorBoardStatus?: TensorBoardStatus;
|
||||
|
|
@ -111,9 +112,9 @@ export type KeysToCamelCase<T> = {
|
|||
// 序列化后的流水线节点
|
||||
export type PipelineNodeModelSerialize = Omit<
|
||||
PipelineNodeModel,
|
||||
'control_strategy' | 'in_parameters' | 'out_parameters'
|
||||
'in_parameters' | 'out_parameters'
|
||||
> & {
|
||||
control_strategy: Record<string, PipelineNodeModelParameter>;
|
||||
// control_strategy: Record<string, PipelineNodeModelParameter>;
|
||||
in_parameters: Record<string, PipelineNodeModelParameter>;
|
||||
out_parameters: Record<string, PipelineNodeModelParameter>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { now } from '@/hooks/useServerTime';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
/**
|
||||
|
|
@ -13,8 +12,12 @@ export const elapsedTime = (begin?: string | Date | null, end?: string | Date |
|
|||
return '--';
|
||||
}
|
||||
|
||||
if (end === undefined || end === null) {
|
||||
return '--';
|
||||
}
|
||||
|
||||
const beginDate = dayjs(begin);
|
||||
const endDate = end === undefined || end === null ? dayjs(now()) : dayjs(end);
|
||||
const endDate = dayjs(end); // end === undefined || end === null ? dayjs(now()) : dayjs(end);
|
||||
if (!beginDate.isValid() || !endDate.isValid()) {
|
||||
return '--';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
import { ExperimentStatus } from '@/enums';
|
||||
import { NodeStatus } from '@/types';
|
||||
import { parseJsonText } from './index';
|
||||
|
||||
/**
|
||||
* 获取工作流节点
|
||||
*
|
||||
* @param node_status - 流水线workflow节点,json字符串
|
||||
* @return workflow 节点
|
||||
*/
|
||||
export const getWorkflowStatus = (node_status?: string | null) => {
|
||||
if (!node_status) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const nodeStatusJson = parseJsonText(node_status);
|
||||
if (!nodeStatusJson) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const key in nodeStatusJson) {
|
||||
if (key.startsWith('workflow')) {
|
||||
return nodeStatusJson[key];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取实例状态
|
||||
* 终止或者 workflowStatus 不存在时,取实例状态,否则取流水线状态
|
||||
*
|
||||
* @param instanceStatus - 实例状态
|
||||
* @param workflowStatus - 流水线workflow节点
|
||||
* @return 实例状态
|
||||
*/
|
||||
export const getExperimentInstanceStatus = (
|
||||
instanceStatus: ExperimentStatus,
|
||||
workflowStatus?: NodeStatus,
|
||||
): ExperimentStatus => {
|
||||
return instanceStatus === ExperimentStatus.Terminated || !workflowStatus
|
||||
? instanceStatus
|
||||
: workflowStatus?.phase;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取实例状态
|
||||
* 终止或者 workflowStatus 不存在时,取实例状态,否则取流水线状态
|
||||
*
|
||||
* @param instanceStatus - 实例状态
|
||||
* @param workflowStatus - 流水线workflow节点
|
||||
* @return 实例状态
|
||||
*/
|
||||
export const getInstanceStatusInList = (
|
||||
instanceStatus: ExperimentStatus,
|
||||
node_status?: string | null,
|
||||
): ExperimentStatus => {
|
||||
const workflowStatus = getWorkflowStatus(node_status);
|
||||
return getExperimentInstanceStatus(instanceStatus, workflowStatus);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue